diff --git a/source/content-scripts/features/markdown-toolbar.tsx b/source/content-scripts/features/markdown-toolbar.tsx index 54564ab..1820a0b 100644 --- a/source/content-scripts/features/markdown-toolbar.tsx +++ b/source/content-scripts/features/markdown-toolbar.tsx @@ -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: - "
\nClick to expand spoiler.\n\n<>\n
", - 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: "<>", - 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>, +) { + const count = addToolbarsToTextareas(snippets); log(`Markdown Toolbar: Initialized for ${count} textareas.`); } -function addToolbarsToTextareas(): number { +function addToolbarsToTextareas( + snippets: Array>, +): 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) => ( - + )); + 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); - // And render the dropdown directly after the menu. - const dropdownPlaceholder = document.createElement("div"); - const menuParent = menu.parentElement!; - menu.after(dropdownPlaceholder); - render( - , - menuParent, - dropdownPlaceholder, - ); + if (!noDropdownSnippets) { + // And render the dropdown directly after the menu. + const dropdownPlaceholder = document.createElement("div"); + const menuParent = menu.parentElement!; + menu.after(dropdownPlaceholder); + render( + <> + + , + menuParent, + dropdownPlaceholder, + ); + } } return markdownForms.length; } type Props = { - snippet?: MarkdownSnippet; + allSnippets: Array>; + snippet?: Value; textarea: HTMLTextAreaElement; }; @@ -133,22 +82,29 @@ function SnippetButton(props: Required) { return (
  • ); } function SnippetDropdown(props: Props) { - const options = snippets.map((snippet) => ( - - )); + const snippets = props.allSnippets; + const options = snippets + ?.filter((snippet) => snippet.value.inDropdown) + .map((snippet) => ( + + )); + + 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) { // 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) { markdown + textarea.value.slice(selectionEnd); - textarea.selectionEnd = selectionEnd + index; + textarea.selectionEnd = selectionEnd + cursorPosition; } diff --git a/source/content-scripts/setup.tsx b/source/content-scripts/setup.tsx index 295fba4..94d0909 100644 --- a/source/content-scripts/setup.tsx +++ b/source/content-scripts/setup.tsx @@ -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); }); }