import {html} from 'htm/preact';
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() {
// Create an observer that will add toolbars whenever
// a new Markdown form is created (like when clicking Reply).
const observer = new window.MutationObserver(() => {
observer.disconnect();
addToolbarsToTextareas();
startObserver();
});
function startObserver() {
observer.observe(document, {
childList: true,
subtree: true,
});
}
// Run once when the page loads.
addToolbarsToTextareas();
startObserver();
log('Markdown Toolbar: Initialized.');
}
function addToolbarsToTextareas() {
// 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;
}
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) =>
html`<${snippetButton} snippet=${snippet} textarea=${textarea} />`,
);
// 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(
html`<${snippetDropdown} textarea=${textarea} />`,
menuParent,
dropdownPlaceholder,
);
}
}
type Props = {
snippet?: MarkdownSnippet;
textarea: HTMLTextAreaElement;
};
function snippetButton(props: Required): TRXComponent {
const click = (event: MouseEvent) => {
event.preventDefault();
insertSnippet(props);
};
return html`
`;
}
function snippetDropdown(props: Props): TRXComponent {
const options = snippets.map(
(snippet) => html``,
);
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 html`
`;
}
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;
}