1
Fork 0

Adapt the content script for the new Markdown snippets.

This commit is contained in:
Bauke 2023-11-21 12:24:08 +01:00
parent 60e5e100ac
commit 223dfa2658
Signed by: Bauke
GPG Key ID: C1C0F29952BCF558
2 changed files with 81 additions and 102 deletions

View File

@ -1,83 +1,21 @@
import {type Value} from "@holllo/webextension-storage";
import {render} from "preact";
import {log, querySelectorAll} from "../../utilities/exports.js";
import {
type MarkdownSnippet,
MarkdownSnippetMarker,
} from "../../storage/exports.js";
type MarkdownSnippet = {
dropdown: boolean;
index: number;
markdown: string;
name: string;
};
const snippets: MarkdownSnippet[] = [
{
dropdown: false,
markdown: "[<>]()",
name: "Link",
},
{
dropdown: false,
markdown: "```\n<>\n```",
name: "Code",
},
{
dropdown: false,
markdown: "~~<>~~",
name: "Strikethrough",
},
{
dropdown: false,
markdown:
"<details>\n<summary>Click to expand spoiler.</summary>\n\n<>\n</details>",
name: "Spoilerbox",
},
{
dropdown: true,
markdown: "**<>**",
name: "Bold",
},
{
dropdown: true,
markdown: "\n\n---\n\n<>",
name: "Horizontal Divider",
},
{
dropdown: true,
markdown: "`<>`",
name: "Inline Code",
},
{
dropdown: true,
markdown: "*<>*",
name: "Italic",
},
{
dropdown: true,
markdown: "1. <>",
name: "Ordered List",
},
{
dropdown: true,
markdown: "<small><></small>",
name: "Small",
},
{
dropdown: true,
markdown: "* <>",
name: "Unordered List",
},
].map(({dropdown, markdown, name}) => ({
dropdown,
name,
index: markdown.indexOf("<>"),
markdown: markdown.replace(/<>/, ""),
}));
export function runMarkdownToolbarFeature() {
const count = addToolbarsToTextareas();
export function runMarkdownToolbarFeature(
snippets: Array<Value<MarkdownSnippet>>,
) {
const count = addToolbarsToTextareas(snippets);
log(`Markdown Toolbar: Initialized for ${count} textareas.`);
}
function addToolbarsToTextareas(): number {
function addToolbarsToTextareas(
snippets: Array<Value<MarkdownSnippet>>,
): number {
// Grab all Markdown forms that don't have already have a toolbar.
const markdownForms = querySelectorAll(".form-markdown:not(.trx-toolbar)");
if (markdownForms.length === 0) {
@ -94,33 +32,44 @@ function addToolbarsToTextareas(): number {
)!;
const snippetButtons = snippets
.filter((snippet) => !snippet.dropdown)
.filter((snippet) => !snippet.value.inDropdown)
.map((snippet) => (
<SnippetButton snippet={snippet} textarea={textarea} />
<SnippetButton
allSnippets={snippets}
snippet={snippet}
textarea={textarea}
/>
));
const noDropdownSnippets = snippets.length === snippetButtons.length;
// Render the buttons inside the tab menu so they appear
// next to the Edit and Preview buttons.
const menuPlaceholder = document.createElement("div");
menu.append(menuPlaceholder);
render(snippetButtons, menu, menuPlaceholder);
if (!noDropdownSnippets) {
// And render the dropdown directly after the menu.
const dropdownPlaceholder = document.createElement("div");
const menuParent = menu.parentElement!;
menu.after(dropdownPlaceholder);
render(
<SnippetDropdown textarea={textarea} />,
<>
<SnippetDropdown allSnippets={snippets} textarea={textarea} />
</>,
menuParent,
dropdownPlaceholder,
);
}
}
return markdownForms.length;
}
type Props = {
snippet?: MarkdownSnippet;
allSnippets: Array<Value<MarkdownSnippet>>;
snippet?: Value<MarkdownSnippet>;
textarea: HTMLTextAreaElement;
};
@ -133,22 +82,29 @@ function SnippetButton(props: Required<Props>) {
return (
<li class="tab-item">
<button class="btn btn-link" onClick={click}>
{props.snippet.name}
{props.snippet.value.name}
</button>
</li>
);
}
function SnippetDropdown(props: Props) {
const options = snippets.map((snippet) => (
<option value={snippet.name}>{snippet.name}</option>
const snippets = props.allSnippets;
const options = snippets
?.filter((snippet) => snippet.value.inDropdown)
.map((snippet) => (
<option value={snippet.value.name}>{snippet.value.name}</option>
));
if (options.length === 0) {
return null;
}
const change = (event: Event) => {
event.preventDefault();
const snippet = snippets.find(
(value) => value.name === (event.target as HTMLSelectElement).value,
(value) => value.value.name === (event.target as HTMLSelectElement).value,
)!;
insertSnippet({
@ -175,20 +131,41 @@ function insertSnippet(props: Required<Props>) {
// snippet, the textarea won't be focused anymore. So focus it again.
textarea.focus();
let {index, markdown} = snippet;
let {markdown} = snippet.value;
// Get the marker positions and remove them from the snippet.
let cursorIndex = markdown.indexOf(MarkdownSnippetMarker.Cursor);
markdown = markdown.replace(MarkdownSnippetMarker.Cursor, "");
const selectedCursorIndex = markdown.indexOf(
MarkdownSnippetMarker.SelectedCursor,
);
markdown = markdown.replace(MarkdownSnippetMarker.SelectedCursor, "");
// If we have a Cursor and SelectedCursor in the snippet, and the Cursor is
// placed after the SelectedCursor we have to account for the marker string
// length.
// We don't have to do it in reverse because the Cursor index is taken first
// and the marker string for that is removed before the SelectedCursor index
// is taken.
if (
cursorIndex !== -1 &&
selectedCursorIndex !== -1 &&
cursorIndex > selectedCursorIndex
) {
cursorIndex -= MarkdownSnippetMarker.SelectedCursor.length;
}
let cursorPosition = cursorIndex;
// If any text has been selected, include it.
if (selectionStart !== selectionEnd) {
markdown =
markdown.slice(0, index) +
markdown.slice(0, cursorIndex) +
textarea.value.slice(selectionStart, selectionEnd) +
markdown.slice(index);
markdown.slice(cursorIndex);
// Change the index when the Link snippet is used so the cursor ends up
// in the URL part of the Markdown: "[existing text](cursor here)".
if (snippet.name === "Link") {
index += 2;
}
cursorPosition =
selectedCursorIndex === -1 ? cursorIndex : selectedCursorIndex;
}
textarea.value =
@ -196,5 +173,5 @@ function insertSnippet(props: Required<Props>) {
markdown +
textarea.value.slice(selectionEnd);
textarea.selectionEnd = selectionEnd + index;
textarea.selectionEnd = selectionEnd + cursorPosition;
}

View File

@ -9,6 +9,7 @@ import {
Data,
Feature,
MiscellaneousFeature,
collectMarkdownSnippets,
fromStorage,
} from "../storage/exports.js";
import {
@ -106,8 +107,9 @@ async function initialize() {
}
if (enabledFeatures.value.has(Feature.MarkdownToolbar) && isLoggedIn) {
observerFeatures.push(() => {
runMarkdownToolbarFeature();
observerFeatures.push(async () => {
const snippets = await collectMarkdownSnippets();
runMarkdownToolbarFeature(snippets);
});
}