Add the tour logic, introduction and homepage tours.
This commit is contained in:
		
							parent
							
								
									8c62714b9b
								
							
						
					
					
						commit
						97cb3a5603
					
				| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
import {homepageSteps, homepageEventHandlers} from "./interface/exports.js";
 | 
			
		||||
import {introductionSteps} from "./introduction.js";
 | 
			
		||||
 | 
			
		||||
export const tourIds = ["introduction", "interface-homepage"] as const;
 | 
			
		||||
 | 
			
		||||
export const tourIdsAndSteps: Array<
 | 
			
		||||
  [TourId, TourStepOptions[], TourStepEventHandler[]]
 | 
			
		||||
> = [
 | 
			
		||||
  ["introduction", introductionSteps, []],
 | 
			
		||||
  ["interface-homepage", homepageSteps, homepageEventHandlers],
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,4 @@
 | 
			
		|||
export {
 | 
			
		||||
  eventHandlers as homepageEventHandlers,
 | 
			
		||||
  steps as homepageSteps,
 | 
			
		||||
} from "./homepage.js";
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,499 @@
 | 
			
		|||
import type Shepherd from "shepherd.js";
 | 
			
		||||
import {
 | 
			
		||||
  addDatasetCounter,
 | 
			
		||||
  encapsulateElements,
 | 
			
		||||
  removeAllDatasetCounters,
 | 
			
		||||
  renderInContainer,
 | 
			
		||||
} from "../utilities.js";
 | 
			
		||||
 | 
			
		||||
const step01 = renderInContainer(
 | 
			
		||||
  <>
 | 
			
		||||
    <h1>The Homepage</h1>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      If you plan on staying a while, this is likely the place you'll see the
 | 
			
		||||
      most. So let's work our way top to bottom, left to right.
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <p>Starting with...</p>
 | 
			
		||||
  </>,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const step02 = renderInContainer(
 | 
			
		||||
  <>
 | 
			
		||||
    <h1>The Main Header</h1>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      On the left there is the Tildes logo and title, which you can click to get
 | 
			
		||||
      back to the homepage, or refresh it if you're already there. If you are in
 | 
			
		||||
      a group, the group name will also be shown.
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      On the right is the notifications section and your username. When you have
 | 
			
		||||
      unread messages and/or are mentioned, they will show up next to your
 | 
			
		||||
      username. If you don't have any notifications right now, you can{" "}
 | 
			
		||||
      <a href="#" onClick={toggleMockNotifications}>
 | 
			
		||||
        click here
 | 
			
		||||
      </a>{" "}
 | 
			
		||||
      to show a preview of what they look like.
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      At the moment mentions only work in comments, so if you get mentioned in a
 | 
			
		||||
      topic's text body and don't get a notification,{" "}
 | 
			
		||||
      <a target="_blank" href="https://gitlab.com/tildes/tildes/-/issues/195">
 | 
			
		||||
        that's why
 | 
			
		||||
      </a>
 | 
			
		||||
      .
 | 
			
		||||
    </p>
 | 
			
		||||
  </>,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// Toggle mock notifications, based on the code in the following link.
 | 
			
		||||
// If the logic for this needs to be updated, also update the permalink to the
 | 
			
		||||
// actual Tildes HTML code.
 | 
			
		||||
// https://gitlab.com/tildes/tildes/-/blob/0dbb031562cd9297968fec0049af4b833ef77301/tildes/tildes/templates/macros/user.jinja2#L9-20
 | 
			
		||||
function toggleMockNotifications(event: Event): void {
 | 
			
		||||
  // Prevent the click from going through so the anchor isn't removed.
 | 
			
		||||
  event.preventDefault();
 | 
			
		||||
 | 
			
		||||
  const root = document.querySelector("#site-header .logged-in-user-info");
 | 
			
		||||
  function toggle(existingSelector: string, href: string, text: string): void {
 | 
			
		||||
    const existing =
 | 
			
		||||
      root?.querySelector<HTMLElement>(existingSelector) ?? undefined;
 | 
			
		||||
    if (existing === undefined) {
 | 
			
		||||
      // If no notifiaction exists, render our mock.
 | 
			
		||||
      const messageNotification = document.createElement("a");
 | 
			
		||||
      const count = 1 + Math.ceil(Math.random() * 10);
 | 
			
		||||
      messageNotification.classList.add("logged-in-user-alert");
 | 
			
		||||
      messageNotification.dataset.tildesShepherdMock = "true";
 | 
			
		||||
      messageNotification.href = href;
 | 
			
		||||
      messageNotification.textContent = `${count} ${text}`;
 | 
			
		||||
      root?.insertAdjacentElement("beforeend", messageNotification);
 | 
			
		||||
    } else if (existing.dataset.tildesShepherdMock === "true") {
 | 
			
		||||
      // If a notification exists and it has our mock attribute, remove it.
 | 
			
		||||
      existing.remove();
 | 
			
		||||
    } else {
 | 
			
		||||
      // A real message notification already exists, so we do nothing.
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toggle('[href="/messages/unread"]', "/messages/unread", "new messages");
 | 
			
		||||
  toggle(
 | 
			
		||||
    '[href="/notifications/unread"]',
 | 
			
		||||
    "/notifications/unread",
 | 
			
		||||
    "new comments",
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const step03 = renderInContainer(
 | 
			
		||||
  <>
 | 
			
		||||
    <h1>The Listing Options</h1>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      Right below the main header are the topic listing options. These determine
 | 
			
		||||
      how the topics in the listing are sorted and{" "}
 | 
			
		||||
      <em>
 | 
			
		||||
        <b>from</b> what period
 | 
			
		||||
      </em>{" "}
 | 
			
		||||
      topics should be shown.
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      The non-activity sorts are the easiest to explain, so let's start with
 | 
			
		||||
      them:
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <ul>
 | 
			
		||||
      <li>
 | 
			
		||||
        <b>Votes</b> sorts topics with the most votes first.
 | 
			
		||||
      </li>
 | 
			
		||||
      <li>
 | 
			
		||||
        <b>Comments</b> sorts them with the most comments first.
 | 
			
		||||
      </li>
 | 
			
		||||
      <li>
 | 
			
		||||
        And <b>New</b> sorts them by their date, so newest topics first.
 | 
			
		||||
      </li>
 | 
			
		||||
    </ul>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      Then the first <b>Activity</b> will sort topics by bumping topics up as
 | 
			
		||||
      they receive comments, however this sort makes use of comment labels.
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      If you don't know what comment labels are yet, don't worry, there is a
 | 
			
		||||
      tour that explains them. But in short they are a community tool to
 | 
			
		||||
      emphasize and de-emphasize comments, similar to votes but with more
 | 
			
		||||
      specific intention. If you'd like to read more, check out{" "}
 | 
			
		||||
      <a
 | 
			
		||||
        target="_blank"
 | 
			
		||||
        href="https://docs.tildes.net/instructions/commenting-on-tildes#labelling-comments"
 | 
			
		||||
      >
 | 
			
		||||
        the documentation
 | 
			
		||||
      </a>
 | 
			
		||||
      .
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      And finally the <b>All activity</b> sorts topics the same as Activity, but
 | 
			
		||||
      without taking into account any comment labels.
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      If you only want to see topics from a certain period, click on the "from"
 | 
			
		||||
      dropdown and select your period. Aside from a set of predefined options,
 | 
			
		||||
      you can also set a custom one by clicking the "other period" option, after
 | 
			
		||||
      which you'll be prompted for it.
 | 
			
		||||
    </p>
 | 
			
		||||
  </>,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const step04 = renderInContainer(
 | 
			
		||||
  <>
 | 
			
		||||
    <h1>The Topic Listing</h1>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      Next up, just below the listing options is the topic listing itself. We've
 | 
			
		||||
      only highlighted the first topic here, but you can probably see it
 | 
			
		||||
      continues all the way down the page.
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <p>Here, we've marked the main elements of the topic:</p>
 | 
			
		||||
 | 
			
		||||
    <ol>
 | 
			
		||||
      <li>
 | 
			
		||||
        The topic title, when the topic is a "text topic" links to the same
 | 
			
		||||
        place as the comments link does. Otherwise when it's a "link topic", it
 | 
			
		||||
        will link to an external site.
 | 
			
		||||
      </li>
 | 
			
		||||
 | 
			
		||||
      <li>
 | 
			
		||||
        The topic metadata, which by default includes the group it's in. If a
 | 
			
		||||
        topic has specific notable tags and you have the "show tags" setting
 | 
			
		||||
        enabled in your user settings, they will be shown after the group name.
 | 
			
		||||
        A "topic type" may also be shown to indicate whether the topic is a text
 | 
			
		||||
        topic, a video, an "ask" topic for recommendations or a survey, etc.
 | 
			
		||||
      </li>
 | 
			
		||||
 | 
			
		||||
      <li>
 | 
			
		||||
        The amount of comments the topic has received. If any new comments have
 | 
			
		||||
        been posted since you last viewed the topic, in orange a "(X new)" will
 | 
			
		||||
        be added.
 | 
			
		||||
      </li>
 | 
			
		||||
 | 
			
		||||
      <li>
 | 
			
		||||
        The topic source. For text topics this is always the poster of the
 | 
			
		||||
        topic, but for link topics in certain groups it will be the domain name
 | 
			
		||||
        together with that website's icon. For topics posted automatically by
 | 
			
		||||
        Tildes itself, it will be "Scheduled topic".
 | 
			
		||||
      </li>
 | 
			
		||||
    </ol>
 | 
			
		||||
 | 
			
		||||
    <p>The list continues on the next page.</p>
 | 
			
		||||
  </>,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const step05 = renderInContainer(
 | 
			
		||||
  <>
 | 
			
		||||
    <h1>The Topic Listing continued</h1>
 | 
			
		||||
 | 
			
		||||
    <ol start={5}>
 | 
			
		||||
      <li>
 | 
			
		||||
        The date when the topic was posted. For dates that are pretty recent it
 | 
			
		||||
        will show something like "X days ago" while for longer dates it will be
 | 
			
		||||
        the exact date, like "April 26th, 2021".
 | 
			
		||||
      </li>
 | 
			
		||||
 | 
			
		||||
      <li>
 | 
			
		||||
        The voting button, clicking it will add your vote to the topic. On
 | 
			
		||||
        Tildes, once the topic is older than 30 days you can no longer vote on
 | 
			
		||||
        it and the individual voting data for is removed, with only the total
 | 
			
		||||
        count kept. You can read more about it in{" "}
 | 
			
		||||
        <a target="_blank" href="https://tild.es/jhm">
 | 
			
		||||
          this announcement topic
 | 
			
		||||
        </a>
 | 
			
		||||
        .
 | 
			
		||||
      </li>
 | 
			
		||||
 | 
			
		||||
      <li>
 | 
			
		||||
        And finally the topic actions. Clicking on the "Actions" button will
 | 
			
		||||
        show a dropdown including "Bookmark", "Ignore this topic" and if you
 | 
			
		||||
        have been granted the permission for it, "Edit title". Ignoring the
 | 
			
		||||
        topic will remove it from the topic listing and prevent any future
 | 
			
		||||
        notifications from happening. Both your bookmarked and ignored topics
 | 
			
		||||
        can be found in their respective sections on your profile.
 | 
			
		||||
      </li>
 | 
			
		||||
    </ol>
 | 
			
		||||
  </>,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const step06 = renderInContainer(
 | 
			
		||||
  <>
 | 
			
		||||
    <h1>The Sidebar</h1>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      Let's take a look at the sidebar, where the first thing we're greeted with
 | 
			
		||||
      is the search bar.
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      A quick note for mobile use, the sidebar can opened via a button that
 | 
			
		||||
      appears in the very top-right of the page.
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      When searching from the homepage, topics will be included from any group.
 | 
			
		||||
      However, searching when inside a group will only include topics from that
 | 
			
		||||
      group and any sub-groups.
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      Below the search bar you will also find the title of the page, or name of
 | 
			
		||||
      the group, and a small description. The sidebar has more elements when in
 | 
			
		||||
      you're in a group page, but those are touched upon in the groups tour.
 | 
			
		||||
    </p>
 | 
			
		||||
  </>,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const step07 = renderInContainer(
 | 
			
		||||
  <>
 | 
			
		||||
    <h1>The Sidebar Subscriptions</h1>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      Moving on, you'll find the list of groups you are subscribed to and a
 | 
			
		||||
      button below it to go to the groups listing, where you can find all the
 | 
			
		||||
      that are available.
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      To post a topic, subscribe or unsubscribe from any group, go to the group
 | 
			
		||||
      in question and in the sidebar there will be buttons to do so.
 | 
			
		||||
    </p>
 | 
			
		||||
  </>,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const step08 = renderInContainer(
 | 
			
		||||
  <>
 | 
			
		||||
    <h1>The Sidebar User Settings</h1>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      At the bottom of the sidebar you will find some user settings.
 | 
			
		||||
      Specifically the filtered topics tags you have set and a link to the
 | 
			
		||||
      settings page.
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      When you have filtered topics tags set, any topics with any of the tags
 | 
			
		||||
      present will be removed from your listing. You can read more about it by
 | 
			
		||||
      going to the dedicated page for it by clicking the "Edit filtered tags"
 | 
			
		||||
      button.
 | 
			
		||||
    </p>
 | 
			
		||||
  </>,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const step09 = renderInContainer(
 | 
			
		||||
  <>
 | 
			
		||||
    <h1>The Footer</h1>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      And finally, the very last part of the homepage is the footer. Here you
 | 
			
		||||
      will find a theme selector (more on that in a moment) and various links to
 | 
			
		||||
      places, such as the Tildes Documentation, Contact page, the source code,
 | 
			
		||||
      etc. We highly recommend reading through the documentation as it explains
 | 
			
		||||
      a lot of the how and why Tildes does certain things.
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      As for the theme selector, you can change your theme with a number of
 | 
			
		||||
      options. When you change it here, it will only change for the current
 | 
			
		||||
      device, if you'd like to set an account default for all devices, head to
 | 
			
		||||
      your account settings.
 | 
			
		||||
    </p>
 | 
			
		||||
  </>,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const step10 = renderInContainer(
 | 
			
		||||
  <>
 | 
			
		||||
    <h1>This is the end, beautiful friend</h1>
 | 
			
		||||
 | 
			
		||||
    <p>And that's the end of the Homepage tour!</p>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      As always, if you find any bugs, have feature requests or simply want to
 | 
			
		||||
      ask a question. Please send us a message at{" "}
 | 
			
		||||
      <a target="_blank" href="https://tildes.net/user/Community">
 | 
			
		||||
        @Community
 | 
			
		||||
      </a>
 | 
			
		||||
      .
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <p>Happy Tildying!~</p>
 | 
			
		||||
  </>,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export const steps: Shepherd.Step.StepOptions[] = [
 | 
			
		||||
  {
 | 
			
		||||
    id: "homepage-01",
 | 
			
		||||
    text: step01,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    attachTo: {
 | 
			
		||||
      element: "#site-header",
 | 
			
		||||
      on: "bottom",
 | 
			
		||||
    },
 | 
			
		||||
    canClickTarget: false,
 | 
			
		||||
    id: "homepage-02",
 | 
			
		||||
    scrollTo: true,
 | 
			
		||||
    text: step02,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    attachTo: {
 | 
			
		||||
      element: "main > .listing-options",
 | 
			
		||||
      on: "bottom",
 | 
			
		||||
    },
 | 
			
		||||
    canClickTarget: false,
 | 
			
		||||
    id: "homepage-03",
 | 
			
		||||
    text: step03,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    attachTo: {
 | 
			
		||||
      element: ".topic-listing > li:first-child",
 | 
			
		||||
      on: "bottom",
 | 
			
		||||
    },
 | 
			
		||||
    canClickTarget: false,
 | 
			
		||||
    id: "homepage-04",
 | 
			
		||||
    text: step04,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    attachTo: {
 | 
			
		||||
      element: ".topic-listing > li:first-child",
 | 
			
		||||
      on: "bottom",
 | 
			
		||||
    },
 | 
			
		||||
    canClickTarget: false,
 | 
			
		||||
    id: "homepage-05",
 | 
			
		||||
    text: step05,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    attachTo: {
 | 
			
		||||
      element: '#sidebar [data-tildes-shepherd-encapsulated="homepage-06"]',
 | 
			
		||||
      on: "left-start",
 | 
			
		||||
    },
 | 
			
		||||
    canClickTarget: false,
 | 
			
		||||
    id: "homepage-06",
 | 
			
		||||
    text: step06,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    attachTo: {
 | 
			
		||||
      element: '#sidebar [data-tildes-shepherd-encapsulated="homepage-07"]',
 | 
			
		||||
      on: "left-start",
 | 
			
		||||
    },
 | 
			
		||||
    canClickTarget: false,
 | 
			
		||||
    id: "homepage-07",
 | 
			
		||||
    scrollTo: true,
 | 
			
		||||
    text: step07,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    attachTo: {
 | 
			
		||||
      element: "#sidebar .divider + .nav",
 | 
			
		||||
      on: "left-start",
 | 
			
		||||
    },
 | 
			
		||||
    canClickTarget: false,
 | 
			
		||||
    id: "homepage-08",
 | 
			
		||||
    scrollTo: true,
 | 
			
		||||
    text: step08,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    attachTo: {
 | 
			
		||||
      element: "#site-footer",
 | 
			
		||||
      on: "top",
 | 
			
		||||
    },
 | 
			
		||||
    canClickTarget: false,
 | 
			
		||||
    id: "homepage-09",
 | 
			
		||||
    scrollTo: true,
 | 
			
		||||
    text: step09,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    canClickTarget: false,
 | 
			
		||||
    id: "homepage-10",
 | 
			
		||||
    text: step10,
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const eventHandlers: TourStepEventHandler[] = [
 | 
			
		||||
  [
 | 
			
		||||
    "homepage-04",
 | 
			
		||||
    [
 | 
			
		||||
      "show",
 | 
			
		||||
      () => {
 | 
			
		||||
        const topic = ".topic-listing > li:first-child";
 | 
			
		||||
        const counters = [
 | 
			
		||||
          ".topic-title",
 | 
			
		||||
          ".topic-metadata",
 | 
			
		||||
          ".topic-info-comments",
 | 
			
		||||
          ".topic-info-source",
 | 
			
		||||
          "time",
 | 
			
		||||
          ".topic-voting",
 | 
			
		||||
          ".topic-actions",
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        for (const [count, selector] of counters.entries()) {
 | 
			
		||||
          addDatasetCounter(`${topic} ${selector}`, count + 1);
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
  ],
 | 
			
		||||
  [
 | 
			
		||||
    "homepage-05",
 | 
			
		||||
    [
 | 
			
		||||
      "destroy",
 | 
			
		||||
      () => {
 | 
			
		||||
        removeAllDatasetCounters();
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
  ],
 | 
			
		||||
  [
 | 
			
		||||
    "homepage-05",
 | 
			
		||||
    [
 | 
			
		||||
      "show",
 | 
			
		||||
      () => {
 | 
			
		||||
        encapsulateElements(
 | 
			
		||||
          "homepage-06",
 | 
			
		||||
          "#sidebar .sidebar-controls",
 | 
			
		||||
          "afterend",
 | 
			
		||||
          ["#sidebar .form-search", "#sidebar h2", "#sidebar p"],
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
  ],
 | 
			
		||||
  [
 | 
			
		||||
    "homepage-06",
 | 
			
		||||
    [
 | 
			
		||||
      "show",
 | 
			
		||||
      () => {
 | 
			
		||||
        encapsulateElements("homepage-07", "#sidebar .divider", "beforebegin", [
 | 
			
		||||
          "#sidebar .nav",
 | 
			
		||||
          '#sidebar [href="/groups"',
 | 
			
		||||
        ]);
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
  ],
 | 
			
		||||
  [
 | 
			
		||||
    "homepage-08",
 | 
			
		||||
    [
 | 
			
		||||
      "show",
 | 
			
		||||
      () => {
 | 
			
		||||
        const filteredTags =
 | 
			
		||||
          document.querySelector<HTMLDetailsElement>("#sidebar details") ??
 | 
			
		||||
          undefined;
 | 
			
		||||
        if (filteredTags === undefined) {
 | 
			
		||||
          console.warn("Element is unexpectedly undefined");
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        filteredTags.open = true;
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
  ],
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,122 @@
 | 
			
		|||
import type Shepherd from "shepherd.js";
 | 
			
		||||
import {createIntroductionUnderstood} from "../storage/common.js";
 | 
			
		||||
import {openOptionsPageFromBackground, renderInContainer} from "./utilities.js";
 | 
			
		||||
 | 
			
		||||
const stepOne = renderInContainer(
 | 
			
		||||
  <>
 | 
			
		||||
    <h1>Thank you for installing Tildes Shepherd!</h1>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      Tildes Shepherd is a set of interactive guided tours for Tildes to show
 | 
			
		||||
      you around and introduce you to the concepts that make this website great.
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      To see and start the tours that are available, click on the extension icon
 | 
			
		||||
      to go to the options page or{" "}
 | 
			
		||||
      <a href="#" onClick={openOptionsPageFromBackground}>
 | 
			
		||||
        click here now
 | 
			
		||||
      </a>
 | 
			
		||||
      .
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      Each tour will start with a pop-up like this one and have "Continue",
 | 
			
		||||
      "Back" and "Exit" buttons at the bottom. They will progress the tour
 | 
			
		||||
      forward, backward or exit it.
 | 
			
		||||
    </p>
 | 
			
		||||
  </>,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const stepTwo = renderInContainer(
 | 
			
		||||
  <>
 | 
			
		||||
    <h1>Tour Mechanics</h1>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      During the tours, at various points we will want to highlight specific
 | 
			
		||||
      areas. When that happens you won't be able to click anything inside them,
 | 
			
		||||
      mainly to prevent you from accidentally going to a different page and
 | 
			
		||||
      interrupting the tour. But also to prevent you from taking actions you
 | 
			
		||||
      maybe don't want to take, like voting on something you don't necessarily
 | 
			
		||||
      want to vote on.
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      Depending on your selected theme, the highlighted areas will have a yellow
 | 
			
		||||
      or orange border and some extra added whitespace. As you can see in this
 | 
			
		||||
      example where the main site header has been highlighted.
 | 
			
		||||
    </p>
 | 
			
		||||
  </>,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const stepThree = renderInContainer(
 | 
			
		||||
  <>
 | 
			
		||||
    <p>
 | 
			
		||||
      If you find any bugs, have feature requests or simply want to ask a
 | 
			
		||||
      question. Please send us a message at{" "}
 | 
			
		||||
      <a target="_blank" href="https://tildes.net/user/Community">
 | 
			
		||||
        @Community
 | 
			
		||||
      </a>
 | 
			
		||||
      .
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      Also, big shoutout to the people at{" "}
 | 
			
		||||
      <a target="_blank" href="https://shipshape.io">
 | 
			
		||||
        Ship Shape
 | 
			
		||||
      </a>{" "}
 | 
			
		||||
      for making{" "}
 | 
			
		||||
      <a target="_blank" href="https://shepherdjs.dev">
 | 
			
		||||
        Shepherd.js
 | 
			
		||||
      </a>
 | 
			
		||||
      , the software library making this entire project so much easier to
 | 
			
		||||
      create. And the namesake of it, too.
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
      Once you click the "I understand" button below, this message won't pop up
 | 
			
		||||
      again, so remember the extension icon is how you get to the tours.
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <p>Happy Tildying!~</p>
 | 
			
		||||
  </>,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export const introductionSteps: Shepherd.Step.StepOptions[] = [
 | 
			
		||||
  {
 | 
			
		||||
    id: "introduction-1",
 | 
			
		||||
    text: stepOne,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    attachTo: {
 | 
			
		||||
      element: "#site-header",
 | 
			
		||||
      on: "bottom",
 | 
			
		||||
    },
 | 
			
		||||
    canClickTarget: false,
 | 
			
		||||
    id: "introduction-2",
 | 
			
		||||
    text: stepTwo,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    buttons: [
 | 
			
		||||
      {
 | 
			
		||||
        classes: "btn",
 | 
			
		||||
        text: "I understand",
 | 
			
		||||
        async action() {
 | 
			
		||||
          const introductionUnderstood = await createIntroductionUnderstood();
 | 
			
		||||
          introductionUnderstood.value = true;
 | 
			
		||||
          await introductionUnderstood.save();
 | 
			
		||||
          this.complete();
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        classes: "btn",
 | 
			
		||||
        text: "Back",
 | 
			
		||||
        action() {
 | 
			
		||||
          this.back();
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
    id: "introduction-3",
 | 
			
		||||
    text: stepThree,
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,106 @@
 | 
			
		|||
import {type JSX, render} from "preact";
 | 
			
		||||
import browser from "webextension-polyfill";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Adds a `[data-tildes-shepherd-counter]` attribute to a specified element. For
 | 
			
		||||
 * the associated CSS, see `source/content-scripts/scss/main.scss`.
 | 
			
		||||
 *
 | 
			
		||||
 * @param selector The selector of element to apply the counter to, if the
 | 
			
		||||
 * target element can't be selected an error will be thrown.
 | 
			
		||||
 * @param count The number to display in the counter.
 | 
			
		||||
 */
 | 
			
		||||
export function addDatasetCounter(selector: string, count: number) {
 | 
			
		||||
  const element = document.querySelector<HTMLElement>(selector) ?? undefined;
 | 
			
		||||
  if (element === undefined) {
 | 
			
		||||
    throw new Error(`Target element for "${selector}" is undefined`);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  element.dataset.tildesShepherdCounter = count.toString();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Create a new `div` element and put all the specified elements inside it. Used
 | 
			
		||||
 * for highlighting groups of elements that have sibling elements we may not
 | 
			
		||||
 * want to highlight. Like highlighting only the search bar, title and
 | 
			
		||||
 * description in the homepage sidebar, but not all the rest of the sidebar.
 | 
			
		||||
 * @param stepId The unique step ID to set as
 | 
			
		||||
 * `data-tildes-shepherd-encapsulated="<id>"`. Used for attaching the Shepherd
 | 
			
		||||
 * step to the container.
 | 
			
		||||
 * @param rootSelector The selector for the root element to insert the container
 | 
			
		||||
 * near. This doesn't need to be the parent element necessarily, since
 | 
			
		||||
 * {@link Element.insertAdjacentElement} is used to do the inserting it can also
 | 
			
		||||
 * be inserted before or after the root element.
 | 
			
		||||
 * @param position The insert position for {@link Element.insertAdjacentElement}.
 | 
			
		||||
 * @param selectors The list of element selectors to include in the new
 | 
			
		||||
 * container. Note that you should make sure any elements included don't break
 | 
			
		||||
 * their CSS when contained by a new `div` element and ideally they should all
 | 
			
		||||
 * be adjacent to one another and specified in the order they appear in.
 | 
			
		||||
 */
 | 
			
		||||
export function encapsulateElements(
 | 
			
		||||
  stepId: string,
 | 
			
		||||
  rootSelector: string,
 | 
			
		||||
  position: InsertPosition,
 | 
			
		||||
  selectors: string[],
 | 
			
		||||
) {
 | 
			
		||||
  const container = document.createElement("div");
 | 
			
		||||
  container.dataset.tildesShepherdEncapsulated = stepId;
 | 
			
		||||
 | 
			
		||||
  for (const selector of selectors) {
 | 
			
		||||
    const element = document.querySelector<HTMLElement>(selector) ?? undefined;
 | 
			
		||||
    if (element === undefined) {
 | 
			
		||||
      console.warn(`Unexpected undefined element: ${selector}`);
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Don't encapsulate anything if the parent is already one of our elements.
 | 
			
		||||
    if (
 | 
			
		||||
      element.parentElement?.dataset.tildesShepherdEncapsulated !== undefined
 | 
			
		||||
    ) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    container.append(element);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const root = document.querySelector(rootSelector) ?? undefined;
 | 
			
		||||
  if (root === undefined) {
 | 
			
		||||
    throw new Error(`Root selector returned undefined: ${rootSelector}`);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  root.insertAdjacentElement(position, container);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Content scripts can't open the options page themselves, so send a message to
 | 
			
		||||
 * the background script and open it there.
 | 
			
		||||
 * @param event The mouse click event to prevent from happening.
 | 
			
		||||
 */
 | 
			
		||||
export async function openOptionsPageFromBackground(
 | 
			
		||||
  event: MouseEvent,
 | 
			
		||||
): Promise<void> {
 | 
			
		||||
  event.preventDefault();
 | 
			
		||||
  await browser.runtime.sendMessage("open-options-page");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Removes all elements with a `data-tildes-shepherd-counter` set.
 | 
			
		||||
 */
 | 
			
		||||
export function removeAllDatasetCounters() {
 | 
			
		||||
  const elements = document.querySelectorAll<HTMLElement>(
 | 
			
		||||
    "[data-tildes-shepherd-counter]",
 | 
			
		||||
  );
 | 
			
		||||
  for (const element of Array.from(elements)) {
 | 
			
		||||
    delete element.dataset.tildesShepherdCounter;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Render JSX inside a `div` and return the div. This is mostly used so we can
 | 
			
		||||
 * pass JSX more easily to Shepherd, which doesn't accept JSX directly but does
 | 
			
		||||
 * accept {@link HTMLElement}. So this bit of indirection gets us there.
 | 
			
		||||
 */
 | 
			
		||||
export function renderInContainer(root: JSX.Element): HTMLElement {
 | 
			
		||||
  const container = document.createElement("div");
 | 
			
		||||
  render(root, container);
 | 
			
		||||
  return container;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,8 +1,14 @@
 | 
			
		|||
// Export something so TypeScript doesn't see this file as an ambient module.
 | 
			
		||||
export {};
 | 
			
		||||
import type Shepherd from "shepherd.js";
 | 
			
		||||
import type {tourIds} from "./tours/exports.js";
 | 
			
		||||
 | 
			
		||||
declare global {
 | 
			
		||||
  const $browser: "chromium" | "firefox";
 | 
			
		||||
  const $dev: boolean;
 | 
			
		||||
  const $test: boolean;
 | 
			
		||||
 | 
			
		||||
  type TourId = (typeof tourIds)[number];
 | 
			
		||||
  type TourStepEvent = "show" | "destroy";
 | 
			
		||||
  type TourStepEventFunction = Parameters<Shepherd.Step["on"]>[1];
 | 
			
		||||
  type TourStepEventHandler = [string, [TourStepEvent, TourStepEventFunction]];
 | 
			
		||||
  type TourStepOptions = Shepherd.Step.StepOptions;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue