Add the content scripts and styling.
This commit is contained in:
parent
97cb3a5603
commit
5c3d61679c
|
@ -0,0 +1,22 @@
|
|||
@use "shepherd-defaults";
|
||||
@use "shepherd-custom";
|
||||
|
||||
[data-tildes-shepherd-counter] {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
align-items: center;
|
||||
border: 1px solid var(--alert-color);
|
||||
border-radius: 100%;
|
||||
color: var(--alert-color);
|
||||
content: attr(data-tildes-shepherd-counter);
|
||||
display: inline-flex;
|
||||
font-family: sans-serif;
|
||||
font-size: 0.8rem;
|
||||
font-weight: normal;
|
||||
height: 1rem;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
width: 1rem;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
// Extra styles for Shepherd's elements and all the elements we use inside.
|
||||
|
||||
.shepherd-element {
|
||||
background-color: var(--background-primary-color);
|
||||
border: 1px dashed var(--foreground-primary-color);
|
||||
max-width: 600px;
|
||||
|
||||
// For the data-popper-placement styling, since the element here has
|
||||
// `position: absolute` with `top` and `left` set, margin-right and
|
||||
// margin-bottom will have no effect. So to push them away slightly from the
|
||||
// highlighted element, use margin-top and margin-left with negative margins.
|
||||
&[data-popper-placement="bottom"] {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
&[data-popper-placement="top"] {
|
||||
margin-top: -8px;
|
||||
}
|
||||
|
||||
&[data-popper-placement="left-start"] {
|
||||
margin-left: -8px;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
margin-left: 1rem;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: disc;
|
||||
}
|
||||
|
||||
.highlight-red {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
.highlight-green {
|
||||
color: var(--success-color);
|
||||
}
|
||||
}
|
||||
|
||||
.shepherd-text {
|
||||
color: var(--foreground-primary-color);
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.shepherd-button {
|
||||
margin-right: 0.4rem;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.shepherd-target:not(body) {
|
||||
border: 2px solid var(--alert-color);
|
||||
margin: 8px;
|
||||
padding: 8px !important;
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
// Styles copied almost verbatim from Shepherd.js's default CSS.
|
||||
// Since we have the Tildes CSS available in the content scripts anyway, we can
|
||||
// use its classes for a lot of what we need. So instead of importing the full
|
||||
// default styling from Shepherd.js and then overriding everything, only copy
|
||||
// what we want and trim the rest.
|
||||
|
||||
.shepherd-modal-overlay-container {
|
||||
height: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
transition: all 0.3s ease-out, height 0ms 0.3s, opacity 0.3s 0ms;
|
||||
width: 100vw;
|
||||
z-index: 9997;
|
||||
|
||||
&.shepherd-modal-is-visible {
|
||||
height: 100vh;
|
||||
opacity: 0.5;
|
||||
transform: translateZ(0);
|
||||
transition: all 0.3s ease-out, height 0s 0s, opacity 0.3s 0s;
|
||||
|
||||
path {
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.shepherd-element {
|
||||
opacity: 0;
|
||||
outline: none;
|
||||
padding: 16px;
|
||||
transition: opacity 0.3s, visibility 0.3s;
|
||||
visibility: hidden;
|
||||
width: 100%;
|
||||
z-index: 9999;
|
||||
|
||||
&.shepherd-enabled {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.shepherd-target {
|
||||
&.shepherd-target-click-disabled {
|
||||
&,
|
||||
& * {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
import Shepherd from "shepherd.js";
|
||||
import {
|
||||
addCompletedTour,
|
||||
createIntroductionUnderstood,
|
||||
} from "../storage/common.js";
|
||||
import {introductionSteps} from "../tours/introduction.js";
|
||||
import {tourIdsAndSteps} from "../tours/exports.js";
|
||||
|
||||
/** The main entry point for the content script. */
|
||||
async function main(): Promise<void> {
|
||||
// Automatically start the introduction tour if the person hasn't already been
|
||||
// through it and only when on the Tildes homepage.
|
||||
const introductionUnderstood = await createIntroductionUnderstood();
|
||||
if (!introductionUnderstood.value && window.location.pathname === "/") {
|
||||
startTour("introduction", introductionSteps, []);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the anchor without the leading #.
|
||||
const anchor = window.location.hash.slice(1);
|
||||
|
||||
// We only care about anchors with our prefix.
|
||||
const prefix = "tildes-shepherd-tour=";
|
||||
if (!anchor.startsWith(prefix)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the tour ID from the anchor by removing the prefix.
|
||||
const anchorTourId = anchor.slice(prefix.length);
|
||||
|
||||
// Then run through all of the tours we have and start the first match for the
|
||||
// ID.
|
||||
for (const [id, steps, eventHandlers] of tourIdsAndSteps) {
|
||||
if (anchorTourId === id) {
|
||||
startTour(id, steps, eventHandlers);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.error(`Unknown anchor tour id: ${anchorTourId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new Shepherd.js Tour with the specific steps and event handlers.
|
||||
* @param tourId A unique ID for this tour.
|
||||
* @param steps All the steps of the tour.
|
||||
* @param eventHandlers Event handlers to attach to specific steps.
|
||||
*/
|
||||
function startTour(
|
||||
tourId: TourId,
|
||||
steps: TourStepOptions[],
|
||||
eventHandlers: TourStepEventHandler[],
|
||||
): void {
|
||||
const tour = new Shepherd.Tour({
|
||||
defaultStepOptions: {
|
||||
buttons: [
|
||||
{
|
||||
classes: "btn",
|
||||
text: "Continue",
|
||||
action() {
|
||||
this.next();
|
||||
},
|
||||
},
|
||||
{
|
||||
classes: "btn",
|
||||
text: "Back",
|
||||
action() {
|
||||
this.back();
|
||||
},
|
||||
},
|
||||
{
|
||||
classes: "btn",
|
||||
text: "Exit",
|
||||
action() {
|
||||
this.complete();
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
useModalOverlay: true,
|
||||
});
|
||||
|
||||
// Add an event handler for when the tour completes.
|
||||
tour.on("complete", async () => {
|
||||
// Remove all mock elements that were added in the tour, just in case any
|
||||
// weren't removed after their respective steps.
|
||||
const mockSelector = '[data-tildes-shepherd-mock="true"]';
|
||||
const mockElements = document.querySelectorAll<HTMLElement>(mockSelector);
|
||||
for (const element of Array.from(mockElements)) {
|
||||
element.remove();
|
||||
}
|
||||
|
||||
// Mark the tour as completed.
|
||||
await addCompletedTour(tourId);
|
||||
});
|
||||
|
||||
// For every step we have, add it to the tour and subsequently add all the
|
||||
// event handlers to that step.
|
||||
for (const stepOptions of steps) {
|
||||
const step = tour.addStep(stepOptions);
|
||||
|
||||
for (const [targetStepId, [eventName, eventHandler]] of eventHandlers) {
|
||||
if (targetStepId === step.id) {
|
||||
step.on(eventName, eventHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pull the lever, Kronk!
|
||||
tour.start();
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", main);
|
Loading…
Reference in New Issue