diff --git a/core/static/webpack/easymde-index.js b/core/static/webpack/easymde-index.js deleted file mode 100644 index 378c1303..00000000 --- a/core/static/webpack/easymde-index.js +++ /dev/null @@ -1,182 +0,0 @@ -// biome-ignore lint/correctness/noUndeclaredDependencies: shipped by easymde -import "codemirror/lib/codemirror.css"; -import "easymde/src/css/easymde.css"; -import easyMde from "easymde"; -import { markdownRenderMarkdown } from "#openapi"; - -/** - * Create a new easymde based textarea - * @param {HTMLTextAreaElement} textarea to use - **/ -window.easymdeFactory = (textarea) => { - const easymde = new easyMde({ - element: textarea, - spellChecker: false, - autoDownloadFontAwesome: false, - previewRender: Alpine.debounce(async (plainText, preview) => { - preview.innerHTML = ( - await markdownRenderMarkdown({ body: { text: plainText } }) - ).data; - return null; - }, 300), - forceSync: true, // Avoid validation error on generic create view - toolbar: [ - { - name: "heading-smaller", - action: easyMde.toggleHeadingSmaller, - className: "fa fa-header", - title: gettext("Heading"), - }, - { - name: "italic", - action: easyMde.toggleItalic, - className: "fa fa-italic", - title: gettext("Italic"), - }, - { - name: "bold", - action: easyMde.toggleBold, - className: "fa fa-bold", - title: gettext("Bold"), - }, - { - name: "strikethrough", - action: easyMde.toggleStrikethrough, - className: "fa fa-strikethrough", - title: gettext("Strikethrough"), - }, - { - name: "underline", - action: function customFunction(editor) { - const cm = editor.codemirror; - cm.replaceSelection(`__${cm.getSelection()}__`); - }, - className: "fa fa-underline", - title: gettext("Underline"), - }, - { - name: "superscript", - action: function customFunction(editor) { - const cm = editor.codemirror; - cm.replaceSelection(`^${cm.getSelection()}^`); - }, - className: "fa fa-superscript", - title: gettext("Superscript"), - }, - { - name: "subscript", - action: function customFunction(editor) { - const cm = editor.codemirror; - cm.replaceSelection(`~${cm.getSelection()}~`); - }, - className: "fa fa-subscript", - title: gettext("Subscript"), - }, - { - name: "code", - action: easyMde.toggleCodeBlock, - className: "fa fa-code", - title: gettext("Code"), - }, - "|", - { - name: "quote", - action: easyMde.toggleBlockquote, - className: "fa fa-quote-left", - title: gettext("Quote"), - }, - { - name: "unordered-list", - action: easyMde.toggleUnorderedList, - className: "fa fa-list-ul", - title: gettext("Unordered list"), - }, - { - name: "ordered-list", - action: easyMde.toggleOrderedList, - className: "fa fa-list-ol", - title: gettext("Ordered list"), - }, - "|", - { - name: "link", - action: easyMde.drawLink, - className: "fa fa-link", - title: gettext("Insert link"), - }, - { - name: "image", - action: easyMde.drawImage, - className: "fa-regular fa-image", - title: gettext("Insert image"), - }, - { - name: "table", - action: easyMde.drawTable, - className: "fa fa-table", - title: gettext("Insert table"), - }, - "|", - { - name: "clean-block", - action: easyMde.cleanBlock, - className: "fa fa-eraser fa-clean-block", - title: gettext("Clean block"), - }, - "|", - { - name: "preview", - action: easyMde.togglePreview, - className: "fa fa-eye no-disable", - title: gettext("Toggle preview"), - }, - { - name: "side-by-side", - action: easyMde.toggleSideBySide, - className: "fa fa-columns no-disable no-mobile", - title: gettext("Toggle side by side"), - }, - { - name: "fullscreen", - action: easyMde.toggleFullScreen, - className: "fa fa-expand no-mobile", - title: gettext("Toggle fullscreen"), - }, - "|", - { - name: "guide", - action: "/page/Aide_sur_la_syntaxe", - className: "fa fa-question-circle", - title: gettext("Markdown guide"), - }, - ], - }); - - const submits = textarea.closest("form").querySelectorAll('input[type="submit"]'); - const parentDiv = textarea.parentElement; - let submitPressed = false; - - function checkMarkdownInput() { - // an attribute is null if it does not exist, else a string - const required = textarea.getAttribute("required") != null; - const length = textarea.value.trim().length; - - if (required && length === 0) { - parentDiv.style.boxShadow = "red 0px 0px 1.5px 1px"; - } else { - parentDiv.style.boxShadow = ""; - } - } - - function onSubmitClick(e) { - if (!submitPressed) { - easymde.codemirror.on("change", checkMarkdownInput); - } - submitPressed = true; - checkMarkdownInput(e); - } - - for (const submit of submits) { - submit.addEventListener("click", onSubmitClick); - } -}; diff --git a/core/static/webpack/easymde-index.ts b/core/static/webpack/easymde-index.ts new file mode 100644 index 00000000..4961a1b1 --- /dev/null +++ b/core/static/webpack/easymde-index.ts @@ -0,0 +1,195 @@ +// biome-ignore lint/correctness/noUndeclaredDependencies: shipped by easymde +import "codemirror/lib/codemirror.css"; +import "easymde/src/css/easymde.css"; +// biome-ignore lint/correctness/noUndeclaredDependencies: Imported by EasyMDE +import type CodeMirror from "codemirror"; +// biome-ignore lint/style/useNamingConvention: This is how they called their namespace +import EasyMDE from "easymde"; +import { markdownRenderMarkdown } from "#openapi"; + +class MarkdownInput extends HTMLTextAreaElement { + constructor() { + super(); + new EasyMDE({ + element: this, + spellChecker: false, + autoDownloadFontAwesome: false, + previewRender: Alpine.debounce((plainText: string, preview: MarkdownInput) => { + const func = async ( + plainText: string, + preview: MarkdownInput, + ): Promise => { + preview.innerHTML = ( + await markdownRenderMarkdown({ body: { text: plainText } }) + ).data as string; + return null; + }; + func(plainText, preview); + return null; + }, 300), + forceSync: true, // Avoid validation error on generic create view + toolbar: [ + { + name: "heading-smaller", + action: EasyMDE.toggleHeadingSmaller, + className: "fa fa-header", + title: gettext("Heading"), + }, + { + name: "italic", + action: EasyMDE.toggleItalic, + className: "fa fa-italic", + title: gettext("Italic"), + }, + { + name: "bold", + action: EasyMDE.toggleBold, + className: "fa fa-bold", + title: gettext("Bold"), + }, + { + name: "strikethrough", + action: EasyMDE.toggleStrikethrough, + className: "fa fa-strikethrough", + title: gettext("Strikethrough"), + }, + { + name: "underline", + action: function customFunction(editor: { codemirror: CodeMirror.Editor }) { + const cm = editor.codemirror; + cm.replaceSelection(`__${cm.getSelection()}__`); + }, + className: "fa fa-underline", + title: gettext("Underline"), + }, + { + name: "superscript", + action: function customFunction(editor: { codemirror: CodeMirror.Editor }) { + const cm = editor.codemirror; + cm.replaceSelection(`^${cm.getSelection()}^`); + }, + className: "fa fa-superscript", + title: gettext("Superscript"), + }, + { + name: "subscript", + action: function customFunction(editor: { codemirror: CodeMirror.Editor }) { + const cm = editor.codemirror; + cm.replaceSelection(`~${cm.getSelection()}~`); + }, + className: "fa fa-subscript", + title: gettext("Subscript"), + }, + { + name: "code", + action: EasyMDE.toggleCodeBlock, + className: "fa fa-code", + title: gettext("Code"), + }, + "|", + { + name: "quote", + action: EasyMDE.toggleBlockquote, + className: "fa fa-quote-left", + title: gettext("Quote"), + }, + { + name: "unordered-list", + action: EasyMDE.toggleUnorderedList, + className: "fa fa-list-ul", + title: gettext("Unordered list"), + }, + { + name: "ordered-list", + action: EasyMDE.toggleOrderedList, + className: "fa fa-list-ol", + title: gettext("Ordered list"), + }, + "|", + { + name: "link", + action: EasyMDE.drawLink, + className: "fa fa-link", + title: gettext("Insert link"), + }, + { + name: "image", + action: EasyMDE.drawImage, + className: "fa-regular fa-image", + title: gettext("Insert image"), + }, + { + name: "table", + action: EasyMDE.drawTable, + className: "fa fa-table", + title: gettext("Insert table"), + }, + "|", + { + name: "clean-block", + action: EasyMDE.cleanBlock, + className: "fa fa-eraser fa-clean-block", + title: gettext("Clean block"), + }, + "|", + { + name: "preview", + action: EasyMDE.togglePreview, + className: "fa fa-eye no-disable", + title: gettext("Toggle preview"), + }, + { + name: "side-by-side", + action: EasyMDE.toggleSideBySide, + className: "fa fa-columns no-disable no-mobile", + title: gettext("Toggle side by side"), + }, + { + name: "fullscreen", + action: EasyMDE.toggleFullScreen, + className: "fa fa-expand no-mobile", + title: gettext("Toggle fullscreen"), + }, + "|", + { + name: "guide", + action: "/page/Aide_sur_la_syntaxe", + className: "fa fa-question-circle", + title: gettext("Markdown guide"), + }, + ], + }); + + const submits: HTMLInputElement[] = Array.from( + this.closest("form").querySelectorAll('input[type="submit"]'), + ); + const parentDiv = this.parentElement; + let submitPressed = false; + + function checkMarkdownInput(_event: Event) { + // an attribute is null if it does not exist, else a string + const required = this.getAttribute("required") != null; + const length = this.value.trim().length; + + if (required && length === 0) { + parentDiv.style.boxShadow = "red 0px 0px 1.5px 1px"; + } else { + parentDiv.style.boxShadow = ""; + } + } + + function onSubmitClick(e: Event) { + if (!submitPressed) { + this.codemirror.on("change", checkMarkdownInput); + } + submitPressed = true; + checkMarkdownInput(e); + } + + for (const submit of submits) { + submit.addEventListener("click", onSubmitClick); + } + } +} + +window.customElements.define("markdown-input", MarkdownInput, { extends: "textarea" }); diff --git a/core/templates/core/widgets/markdown_textarea.jinja b/core/templates/core/widgets/markdown_textarea.jinja index 2412497d..f7cae8c4 100644 --- a/core/templates/core/widgets/markdown_textarea.jinja +++ b/core/templates/core/widgets/markdown_textarea.jinja @@ -1,13 +1,7 @@
- + {# The easymde script can be included twice, it's safe in the code #} -
diff --git a/tsconfig.json b/tsconfig.json index 9ac04682..6bb7d717 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,9 +4,11 @@ "sourceMap": true, "noImplicitAny": true, "module": "es6", - "target": "es5", + "target": "es6", "allowJs": true, "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, "types": ["jquery", "alpinejs"], "paths": { "#openapi": ["./staticfiles/generated/openapi/index.ts"],