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 {render} from "preact";
import {log, querySelectorAll} from "../../utilities/exports.js"; import {log, querySelectorAll} from "../../utilities/exports.js";
import {
type MarkdownSnippet,
MarkdownSnippetMarker,
} from "../../storage/exports.js";
type MarkdownSnippet = { export function runMarkdownToolbarFeature(
dropdown: boolean; snippets: Array<Value<MarkdownSnippet>>,
index: number; ) {
markdown: string; const count = addToolbarsToTextareas(snippets);
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();
log(`Markdown Toolbar: Initialized for ${count} textareas.`); 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. // Grab all Markdown forms that don't have already have a toolbar.
const markdownForms = querySelectorAll(".form-markdown:not(.trx-toolbar)"); const markdownForms = querySelectorAll(".form-markdown:not(.trx-toolbar)");
if (markdownForms.length === 0) { if (markdownForms.length === 0) {
@ -94,33 +32,44 @@ function addToolbarsToTextareas(): number {
)!; )!;
const snippetButtons = snippets const snippetButtons = snippets
.filter((snippet) => !snippet.dropdown) .filter((snippet) => !snippet.value.inDropdown)
.map((snippet) => ( .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 // Render the buttons inside the tab menu so they appear
// next to the Edit and Preview buttons. // next to the Edit and Preview buttons.
const menuPlaceholder = document.createElement("div"); const menuPlaceholder = document.createElement("div");
menu.append(menuPlaceholder); menu.append(menuPlaceholder);
render(snippetButtons, menu, menuPlaceholder); render(snippetButtons, menu, menuPlaceholder);
if (!noDropdownSnippets) {
// And render the dropdown directly after the menu. // And render the dropdown directly after the menu.
const dropdownPlaceholder = document.createElement("div"); const dropdownPlaceholder = document.createElement("div");
const menuParent = menu.parentElement!; const menuParent = menu.parentElement!;
menu.after(dropdownPlaceholder); menu.after(dropdownPlaceholder);
render( render(
<SnippetDropdown textarea={textarea} />, <>
<SnippetDropdown allSnippets={snippets} textarea={textarea} />
</>,
menuParent, menuParent,
dropdownPlaceholder, dropdownPlaceholder,
); );
} }
}
return markdownForms.length; return markdownForms.length;
} }
type Props = { type Props = {
snippet?: MarkdownSnippet; allSnippets: Array<Value<MarkdownSnippet>>;
snippet?: Value<MarkdownSnippet>;
textarea: HTMLTextAreaElement; textarea: HTMLTextAreaElement;
}; };
@ -133,22 +82,29 @@ function SnippetButton(props: Required<Props>) {
return ( return (
<li class="tab-item"> <li class="tab-item">
<button class="btn btn-link" onClick={click}> <button class="btn btn-link" onClick={click}>
{props.snippet.name} {props.snippet.value.name}
</button> </button>
</li> </li>
); );
} }
function SnippetDropdown(props: Props) { function SnippetDropdown(props: Props) {
const options = snippets.map((snippet) => ( const snippets = props.allSnippets;
<option value={snippet.name}>{snippet.name}</option> 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) => { const change = (event: Event) => {
event.preventDefault(); event.preventDefault();
const snippet = snippets.find( const snippet = snippets.find(
(value) => value.name === (event.target as HTMLSelectElement).value, (value) => value.value.name === (event.target as HTMLSelectElement).value,
)!; )!;
insertSnippet({ insertSnippet({
@ -175,20 +131,41 @@ function insertSnippet(props: Required<Props>) {
// snippet, the textarea won't be focused anymore. So focus it again. // snippet, the textarea won't be focused anymore. So focus it again.
textarea.focus(); 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 any text has been selected, include it.
if (selectionStart !== selectionEnd) { if (selectionStart !== selectionEnd) {
markdown = markdown =
markdown.slice(0, index) + markdown.slice(0, cursorIndex) +
textarea.value.slice(selectionStart, selectionEnd) + 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 cursorPosition =
// in the URL part of the Markdown: "[existing text](cursor here)". selectedCursorIndex === -1 ? cursorIndex : selectedCursorIndex;
if (snippet.name === "Link") {
index += 2;
}
} }
textarea.value = textarea.value =
@ -196,5 +173,5 @@ function insertSnippet(props: Required<Props>) {
markdown + markdown +
textarea.value.slice(selectionEnd); textarea.value.slice(selectionEnd);
textarea.selectionEnd = selectionEnd + index; textarea.selectionEnd = selectionEnd + cursorPosition;
} }

View File

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