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;
}