// ==UserScript== // @name AO3 Markdown Editor // @namespace https://veryroundbird.house // @version 2026-03-31 // @description replaces the standard AO3 editor with a markdown editor that has syntax highlighting and a previewer for people with my brain disease // @author You // @match https://archiveofourown.org/works/* // @match https://squidgeworld.org/works/* // @match https://sunset.femslash.club/works/* // @match https://superlove.sayitditto.net/works/* // @match https://adastrafanfic.com/works/* // @match https://cfaarchive.org/works/* // @icon https://www.google.com/s2/favicons?sz=64&domain=archiveofourown.org // @require https://unpkg.com/turndown/dist/turndown.js // @require https://unpkg.com/@highlightjs/cdn-assets@11.11.1/highlight.min.js // @require https://cdn.jsdelivr.net/npm/marked/lib/marked.umd.js // ==/UserScript== const addCss = (url, container) => { const css = document.createElement('link'); css.setAttribute('rel', 'stylesheet'); css.setAttribute('type', 'text/css'); css.setAttribute('href', url); container.appendChild(css); return css; } (() => { 'use strict'; addCss('https://unpkg.com/@highlightjs/cdn-assets@11.11.1/styles/base16/cupcake.min.css', document.head); addCss('https://unpkg.com/@fontsource/maple-mono@5.2.6/index.css', document.head); const styles = document.createElement('style'); styles.setAttribute('type', 'text/css'); styles.innerHTML = ` #editor-tabs { display: flex; gap: 10px; } #editor-tabs button { border-radius: 10px 10px 0 0; box-shadow: none; } #editor-tabs button[disabled] { opacity: 0.5; } #editor-container, #preview { border: 1px white solid; } #editor, #preview { height: 500px; overflow: auto; display: block; } #editor { font-family: 'Maple Mono', monospace; font-size: 16px; } #preview { padding: 20px; } #preview p { padding: 0; margin: 1em 0; } `; document.head.appendChild(styles); const content = document.getElementById('content'); const buttons = document.querySelector('.rtf-html-switch'); if (buttons) { buttons.style.display = 'none'; } const turndownService = new TurndownService({ headingStyle: 'atx', emDelimiter: '*', codeBlockStyle: 'fenced' }); if (content) { content.style.display = 'none'; const initialText = turndownService.turndown(content.textContent); const wrapper = document.createElement('div'); wrapper.id = 'editor-wrapper'; const tabs = document.createElement('div'); tabs.id = 'editor-tabs'; const mdBtn = document.createElement('button'); mdBtn.setAttribute('type', 'button'); mdBtn.textContent = "Markdown"; mdBtn.setAttribute("disabled", "disabled"); mdBtn.id = 'editor-btn-markdown'; tabs.appendChild(mdBtn); const htmlBtn = document.createElement('button'); htmlBtn.setAttribute('type', 'button'); htmlBtn.id = 'editor-btn-html'; htmlBtn.textContent = "Preview"; tabs.appendChild(htmlBtn); wrapper.appendChild(tabs); const editorContainer = document.createElement('pre'); editorContainer.id = 'editor-container'; const editor = document.createElement('code'); editor.id = 'editor'; editor.classList.add('language-markdown'); editor.setAttribute("contenteditable", "plaintext-only"); editor.textContent = initialText; editorContainer.appendChild(editor); wrapper.appendChild(editorContainer); const preview = document.createElement('div'); preview.id = 'preview'; preview.style.display = "none"; preview.innerHTML = content.value; wrapper.appendChild(preview); content.after(wrapper); mdBtn.addEventListener('click', (e) => { e.preventDefault(); htmlBtn.removeAttribute("disabled"); mdBtn.setAttribute("disabled", "disabled"); editorContainer.style.display = "block"; preview.style.display = "none"; }); htmlBtn.addEventListener('click', (e) => { e.preventDefault(); mdBtn.removeAttribute("disabled"); htmlBtn.setAttribute("disabled", "disabled"); editorContainer.style.display = "none"; preview.style.display = "block"; }); editor.addEventListener('input', (_e) => { const text = marked.parse(editor.innerHTML.replaceAll(/<\/?span(\sclass="hljs-[a-z]+")>/g, "")); preview.innerHTML = text; content.value = text; }); hljs.highlightAll(); } })();