import {render} from "preact"; import {log, querySelectorAll} from "../../utilities/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(); log(`Markdown Toolbar: Initialized for ${count} textareas.`); } function addToolbarsToTextareas(): 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) { return 0; } for (const form of markdownForms) { // Add `trx-toolbar` to indicate this Markdown form already has the toolbar. form.classList.add("trx-toolbar"); const menu = form.querySelector(".tab-markdown-mode")!; const textarea = form.querySelector( 'textarea[name="markdown"]', )!; const snippetButtons = snippets .filter((snippet) => !snippet.dropdown) .map((snippet) => ( )); // 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, ); } return markdownForms.length; } type Props = { snippet?: MarkdownSnippet; textarea: HTMLTextAreaElement; }; function SnippetButton(props: Required) { const click = (event: MouseEvent) => { event.preventDefault(); insertSnippet(props); }; return (
  • ); } function SnippetDropdown(props: Props) { const options = snippets.map((snippet) => ( )); const change = (event: Event) => { event.preventDefault(); const snippet = snippets.find( (value) => value.name === (event.target as HTMLSelectElement).value, )!; insertSnippet({ ...props, snippet, }); (event.target as HTMLSelectElement).selectedIndex = 0; }; return ( ); } function insertSnippet(props: Required) { const {textarea, snippet} = props; const {selectionStart, selectionEnd} = textarea; // Since you have to press a button or go into a dropdown to click on a // snippet, the textarea won't be focused anymore. So focus it again. textarea.focus(); let {index, markdown} = snippet; // If any text has been selected, include it. if (selectionStart !== selectionEnd) { markdown = markdown.slice(0, index) + textarea.value.slice(selectionStart, selectionEnd) + markdown.slice(index); // 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; } } textarea.value = textarea.value.slice(0, selectionStart) + markdown + textarea.value.slice(selectionEnd); textarea.selectionEnd = selectionEnd + index; }