Adapt the content script for the new Markdown snippets.
This commit is contained in:
parent
60e5e100ac
commit
223dfa2658
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue