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 {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);
|
||||||
|
|
||||||
// And render the dropdown directly after the menu.
|
if (!noDropdownSnippets) {
|
||||||
const dropdownPlaceholder = document.createElement("div");
|
// And render the dropdown directly after the menu.
|
||||||
const menuParent = menu.parentElement!;
|
const dropdownPlaceholder = document.createElement("div");
|
||||||
menu.after(dropdownPlaceholder);
|
const menuParent = menu.parentElement!;
|
||||||
render(
|
menu.after(dropdownPlaceholder);
|
||||||
<SnippetDropdown textarea={textarea} />,
|
render(
|
||||||
menuParent,
|
<>
|
||||||
dropdownPlaceholder,
|
<SnippetDropdown allSnippets={snippets} textarea={textarea} />
|
||||||
);
|
</>,
|
||||||
|
menuParent,
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue