From e34a813355b19196627e7cc5865126a8da2c6b10 Mon Sep 17 00:00:00 2001 From: Pablo Zmdl Date: Mon, 23 Jun 2025 14:52:21 +0200 Subject: [PATCH] New plugin "markdown_editor": compose in markdown, send as HTML This adds a markdown editor that sends HTML to the server. It uses codemirror and some custom code to show a syntax highlighted textarea and some buttons to help editing (including a preview). Drafts get marked via an internal email header that causes the markdown editor to automatically start if a message composition is continued that was started using the markdown editor. --- .ci/config-test.inc.php | 1 + .ci/run_browser_tests.sh | 3 + .tx/config | 6 +- CHANGELOG.md | 1 + Makefile | 18 +- plugins/markdown_editor/.gitignore | 5 + plugins/markdown_editor/README.md | 20 + plugins/markdown_editor/build.sh | 5 + plugins/markdown_editor/composer.json | 22 + plugins/markdown_editor/javascript/index.js | 379 ++++ .../javascript/toolbar-button.js | 23 + .../javascript/toolbar-plugin.js | 24 + .../markdown_editor/localization/en_US.inc | 38 + plugins/markdown_editor/markdown_editor.php | 46 + plugins/markdown_editor/package-lock.json | 1925 +++++++++++++++++ plugins/markdown_editor/package.json | 29 + plugins/markdown_editor/rollup.config.js | 22 + .../skins/elastic/styles/iframe.less | 22 + .../skins/elastic/styles/markdown_editor.less | 142 ++ .../tests/Browser/ComposeTest.php | 113 + program/include/rcmail_sendmail.php | 4 + 21 files changed, 2845 insertions(+), 3 deletions(-) create mode 100644 plugins/markdown_editor/.gitignore create mode 100644 plugins/markdown_editor/README.md create mode 100755 plugins/markdown_editor/build.sh create mode 100644 plugins/markdown_editor/composer.json create mode 100644 plugins/markdown_editor/javascript/index.js create mode 100644 plugins/markdown_editor/javascript/toolbar-button.js create mode 100644 plugins/markdown_editor/javascript/toolbar-plugin.js create mode 100644 plugins/markdown_editor/localization/en_US.inc create mode 100644 plugins/markdown_editor/markdown_editor.php create mode 100644 plugins/markdown_editor/package-lock.json create mode 100644 plugins/markdown_editor/package.json create mode 100644 plugins/markdown_editor/rollup.config.js create mode 100644 plugins/markdown_editor/skins/elastic/styles/iframe.less create mode 100644 plugins/markdown_editor/skins/elastic/styles/markdown_editor.less create mode 100644 plugins/markdown_editor/tests/Browser/ComposeTest.php diff --git a/.ci/config-test.inc.php b/.ci/config-test.inc.php index 1432b7d14..5866ce9cb 100644 --- a/.ci/config-test.inc.php +++ b/.ci/config-test.inc.php @@ -34,6 +34,7 @@ $config['plugins'] = [ 'archive', 'attachment_reminder', 'markasjunk', + 'markdown_editor', 'zipdownload', ]; diff --git a/.ci/run_browser_tests.sh b/.ci/run_browser_tests.sh index 82dc02dab..087cc421c 100755 --- a/.ci/run_browser_tests.sh +++ b/.ci/run_browser_tests.sh @@ -47,6 +47,9 @@ bin/install-jsdeps.sh # Compile Elastic's styles make css-elastic +# Build JS and CSS for plugins +make plugins-build + # Use minified javascript files bin/jsshrink.sh diff --git a/.tx/config b/.tx/config index e86eabe63..c2bf58c0f 100644 --- a/.tx/config +++ b/.tx/config @@ -92,8 +92,12 @@ file_filter = plugins/zipdownload/localization/.inc source_file = plugins/zipdownload/localization/en_US.inc source_lang = en_US +[o:roundcube:p:roundcube-webmail:r:plugin-markdown_editor] +file_filter = plugins/markdown_editor/localization/.inc +source_file = plugins/markdown_editor/localization/en_US.inc +source_lang = en_US + [o:roundcube:p:roundcube-webmail:r:timezones] file_filter = program/localization//timezones.inc source_file = program/localization/en_US/timezones.inc source_lang = en_US - diff --git a/CHANGELOG.md b/CHANGELOG.md index e07d5718d..fae268534 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This file includes only changes we consider noteworthy for users, admins and plu - Add ability to chose from all available contact fields on CSV import (#9419) - Password: Removed the (insecure) virtualmin driver (#8007) - Fix jqueryui plugin's minicolors.css issue with custom skins (#9967) +- Add a new plugin called `markdown_editor` that provides an alternative editor to compose emails with in Markdown syntax, which gets converted into HTML before sending. ## Release 1.7-beta2 diff --git a/Makefile b/Makefile index 82bfd73c0..3bbf2a2f6 100644 --- a/Makefile +++ b/Makefile @@ -61,7 +61,7 @@ verify: shasum: shasum -a 256 roundcubemail-$(VERSION).tar.gz roundcubemail-$(VERSION)-complete.tar.gz roundcube-framework-$(VERSION).tar.gz -roundcubemail-git: buildtools +roundcubemail-git: buildtools plugin-markdown_editor-prepare-for-release git clone --branch=$(GITBRANCH) --depth=1 $(GITREMOTE) roundcubemail-git (cd roundcubemail-git; bin/jsshrink.sh; bin/updatecss.sh; bin/cssshrink.sh) (cd roundcubemail-git/skins/elastic && make css) @@ -119,4 +119,18 @@ composer-update: /tmp/composer.phar install-jsdeps: npm-install ./bin/install-jsdeps.sh -build: composer-update install-jsdeps css-elastic +build: composer-update install-jsdeps css-elastic plugins-build + +plugins-build: plugin-markdown_editor-build + +plugin-markdown_editor-build: plugin-markdown_editor-clean + cd plugins/markdown_editor && \ + npm clean-install && \ + npm run build && \ + rm -rf node_module + +plugin-markdown_editor-clean: + cd plugins/markdown_editor && npm run clean + +plugin-markdown_editor-prepare-for-release: plugin-markdown_editor-build + (cd roundcubemail-git/plugins/markdown_editor; rm -rf node_modules package*.json rollup.config.*js build.sh javascript *.less tests) diff --git a/plugins/markdown_editor/.gitignore b/plugins/markdown_editor/.gitignore new file mode 100644 index 000000000..60fc5e5eb --- /dev/null +++ b/plugins/markdown_editor/.gitignore @@ -0,0 +1,5 @@ +node_modules +/bundle-stats.html +/skins/elastic/styles/markdown_editor.*css +/skins/elastic/styles/iframe.*css +/markdown_editor.*js diff --git a/plugins/markdown_editor/README.md b/plugins/markdown_editor/README.md new file mode 100644 index 000000000..6c7b14866 --- /dev/null +++ b/plugins/markdown_editor/README.md @@ -0,0 +1,20 @@ +MarkdownEditor +============== + +A markdown editor (duh!). + +This plugins adds an alternative editor to compose emails with in Markdown syntax, which gets converted into HTML before sending. + +It provides syntax highlighting and a toolbar (including a preview button) to help writing markdown. Drafts are saved as-is, and are automatically re-opened in this editor on re-editing. On sending the written markdown text gets converted into HTML. + +(Roundcubemail automatically produces a multipart/alternative email from the given HTML, including a text/plain part consisting of re-converted HTML-to-text. Using our original text as text/plain part would be preferable but is left for a future version of this plugin.) + + +Installation +------------ + +Run `npm clean-install && npm run build` to produce the minified Javascript and CSS files required to run. + +To enable this editor, add `'markdown_editor'` to the list of plugins in your `config.inc.php`. + +There is no configuration. diff --git a/plugins/markdown_editor/build.sh b/plugins/markdown_editor/build.sh new file mode 100755 index 000000000..48d68ff2c --- /dev/null +++ b/plugins/markdown_editor/build.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -ex + +exec npm run build diff --git a/plugins/markdown_editor/composer.json b/plugins/markdown_editor/composer.json new file mode 100644 index 000000000..9759d1359 --- /dev/null +++ b/plugins/markdown_editor/composer.json @@ -0,0 +1,22 @@ +{ + "name": "roundcube/markdown_editor", + "type": "roundcube-plugin", + "description": "An editor to compose emails in Markdown syntax, which gets converted into HTML before sending.", + "license": "GPL-3.0-or-later", + "version": "0.1", + "authors": [ + { + "name": "Pablo Zimdahl", + "homepage": "https://github.com/pabzm" + } + ], + "require": { + "php": ">=8.1.0", + "roundcube/plugin-installer": "~0.3.5" + }, + "autoload": { + "classmap": [ + "markdown_editor.php" + ] + } +} diff --git a/plugins/markdown_editor/javascript/index.js b/plugins/markdown_editor/javascript/index.js new file mode 100644 index 000000000..aa52a09da --- /dev/null +++ b/plugins/markdown_editor/javascript/index.js @@ -0,0 +1,379 @@ +import markdownit from 'markdown-it'; +import { + EditorView, keymap, highlightSpecialChars, ViewPlugin, +} from '@codemirror/view'; +import { defaultHighlightStyle, syntaxHighlighting, indentOnInput } from '@codemirror/language'; +import { + defaultKeymap, history, historyKeymap, undo, redo, +} from '@codemirror/commands'; +import { EditorState, Compartment } from '@codemirror/state'; +import { markdown } from '@codemirror/lang-markdown'; +import * as Commands from 'codemirror-markdown-commands'; +import { materialLight } from '@fsegurai/codemirror-theme-material-light'; +import { materialDark } from '@fsegurai/codemirror-theme-material-dark'; +import ToolbarButton from './toolbar-button'; +import ToolbarPlugin from './toolbar-plugin'; + +// TODO: +// * Better icons for 'redo' and 'undo' buttons. In Font Awesome v5 Free the good icons are not included. +// * Replace SVG markdown element with markdown icon from Font Awesome after upgrading to a version that includes it. + +class Index { + #defaultTextarea; + + #toolbar; + + #markdownIt; + + #previewIframe; + + #domParser; + + #textEditingToolbarButtons; + + #debounceTimers; + + #editorTheme; + + #view; + + #container; + + // Use a map with fixed callbacks so we can remove the event-listeners later, too. + #eventListeners = new Map(); + + constructor() { + this.#defaultTextarea = rcmail.gui_objects.messageform.querySelector('#composebody'); + this.#toolbar = document.querySelector('.editor-toolbar'); + this.#toolbar.append(this.#makeMarkdownEditorButton()); + + this.#markdownIt = markdownit({ + // Turn '\n' into '
' (required to preserve e.g. email signatures) + breaks: true, + }); + this.#editorTheme = new Compartment(); + + // Reload from plain text textarea if text was inserted or changed through buttons. + this.#eventListeners.set('change_identity', () => this.#reloadContentFromDefaultTextarea()); + // If a quick-response is to be inserted, put the textarea cursor at the position where our cursor is, so the + // response text is actually inserted at the right position. + this.#eventListeners.set('requestsettings/response-get', () => this.#setTextareaCursorPosition()); + // Reload content from the textarea after a quick-response was inserted. + this.#eventListeners.set('insert_response', () => this.#reloadContentFromDefaultTextarea()); + } + + get #wantedTheme() { + if (document.firstElementChild.classList.contains('dark-mode')) { + return materialDark; + } + return materialLight; + } + + #toggleTheme() { + if (!this.#view) { + return; + } + this.#view.dispatch({ + effects: this.#editorTheme.reconfigure(this.#wantedTheme), + }); + } + + #makePreviewIframe(elementToAppendTo) { + // We're using an iframe to isolate the preview content from any styles of the main document. Those wouldn't be + // sent with the email, so the preview shouldn't be using them, either. + const iframe = document.createElement('iframe'); + iframe.id = 'markdown-editor-preview'; + this.#hide(iframe); + elementToAppendTo.append(iframe); + // Handle dark-mode by injecting minimal CSS and a class (must be done after connecting the iframe-element to + // the DOM). + const iframeDoc = iframe.contentWindow.document; + if (document.firstElementChild.classList.contains('dark-mode')) { + iframeDoc.firstElementChild.classList.add('dark-mode'); + } + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = rcmail.assets_path(rcmail.env.markdown_editor_iframe_css_path); + iframeDoc.head.append(link); + return iframe; + } + + #makeMarkdownEditorButton() { + const markdownEditorButton = document.createElement('a'); + markdownEditorButton.className = 'markdown-editor-start-button'; + markdownEditorButton.tabIndex = '-2'; + markdownEditorButton.href = '#'; + markdownEditorButton.addEventListener('click', (ev) => { + ev.preventDefault(); + this.startMarkdownEditor(); + // Force saving to mark this content as edited by markdown_editor. + rcmail.submit_messageform(true); + }); + markdownEditorButton.title = rcmail.get_label('markdown_editor.editor_button_title'); + const readonly = this.#defaultTextarea.hasAttribute('readonly') || this.#defaultTextarea.hasAttribute('disabled'); + if (readonly) { + markdownEditorButton.setAttribute('disabled', 'disabled'); + } + + // Use an inline SVG element so we can style it with CSS. + const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); + svg.setAttribute('viewBox', '0 0 471 289.85'); + const svgTitle = document.createElement('title'); + svgTitle.textContent = 'markdown editor'; + const svgPath1 = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + svgPath1.setAttribute('d', 'M437,289.85H34a34,34,0,0,1-34-34V34A34,34,0,0,1,34,0H437a34,34,0,0,1,34,34V255.88A34,34,0,0,1,437,289.85ZM34,22.64A11.34,11.34,0,0,0,22.64,34V255.88A11.34,11.34,0,0,0,34,267.2H437a11.34,11.34,0,0,0,11.33-11.32V34A11.34,11.34,0,0,0,437,22.64Z'); + const svgPath2 = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + svgPath2.setAttribute('d', 'M67.93,221.91v-154h45.29l45.29,56.61L203.8,67.93h45.29v154H203.8V133.6l-45.29,56.61L113.22,133.6v88.31Zm283.06,0-67.94-74.72h45.29V67.93h45.29v79.26h45.29Z'); + svg.append(svgTitle, svgPath1, svgPath2); + markdownEditorButton.append(svg); + + return markdownEditorButton; + } + + #makeContainer() { + const container = document.createElement('div'); + container.id = 'markdown-editor-container'; + return container; + } + + #makeEditorView(content) { + const contentChangedNotifier = EditorView.updateListener.of( + (viewUpdate) => this.#save() + ); + this.#textEditingToolbarButtons = [ + new ToolbarButton('bold', '\uF032', Commands.bold), + new ToolbarButton('italic', '\uF033', Commands.italic), + new ToolbarButton('strike', '\uF0CC', Commands.strike), + new ToolbarButton('separator', '|'), + new ToolbarButton('h1', '\uF1DC1', Commands.h1), + new ToolbarButton('h2', '\uF1DC2', Commands.h2), + new ToolbarButton('h3', '\uF1DC3', Commands.h3), + new ToolbarButton('h4', '\uF1DC4', Commands.h4), + new ToolbarButton('separator', '|'), + new ToolbarButton('blockquote', '\uF10E', Commands.quote), + new ToolbarButton('ordered_list', '\uF0CB', Commands.ol), + new ToolbarButton('unordered_list', '\uF0CA', Commands.ul), + new ToolbarButton('separator', '|'), + new ToolbarButton('link', '\uF0C1', Commands.link), + new ToolbarButton('separator', '|'), + new ToolbarButton('undo', '\uF0E2', (view) => undo(view)), + new ToolbarButton('redo', '\uF01E', (view) => redo(view)), + ]; + const toolbarItems = [ + new ToolbarButton('quit', '\uF00D', (view) => this.stopMarkdownEditor()), + new ToolbarButton('separator', '|'), + ...this.#textEditingToolbarButtons, + new ToolbarButton('space', ''), + new ToolbarButton('help', '\uF128', (view) => window.open('https://www.markdownguide.org/basic-syntax/', '_blank')), + new ToolbarButton('preview', '\uF06E', (view) => this.#togglePreview()), + ]; + const toolbarExtension = ViewPlugin.define((view) => new ToolbarPlugin(view, toolbarItems)); + + return new EditorView({ + parent: this.#container, + state: EditorState.create({ + doc: content, + extensions: [ + this.#editorTheme.of(this.#wantedTheme), + markdown(), + // Replace non-printable characters with placeholders + highlightSpecialChars(), + // The undo history + history(), + // Re-indent lines when typing specific input + indentOnInput(), + // Highlight syntax with a default style + syntaxHighlighting(defaultHighlightStyle), + keymap.of([ + // A large set of basic bindings + ...defaultKeymap, + // Redo/undo keys + ...historyKeymap, + ]), + contentChangedNotifier, + toolbarExtension, + EditorView.lineWrapping, + ], + }), + }); + } + + startMarkdownEditor() { + if (!this.#container) { + this.#container = this.#makeContainer(); + document.querySelector('#composebodycontainer').append(this.#container); + } + + const content = this.#defaultTextarea.value ?? ''; + this.#view = this.#makeEditorView(content); + this.#previewIframe = this.#makePreviewIframe(this.#view.scrollDOM); + this.#setupDarkModeWatcher(); + + // Add a new field to mark the content as markdown (pun intended). + const markdownField = document.createElement('input'); + markdownField.type = 'hidden'; + markdownField.name = '_markdown_editor'; + markdownField.value = '1'; + this.#view.dom.append(markdownField); + + this.#eventListeners.forEach((callback, eventName) => { + rcmail.addEventListener(eventName, callback); + }); + + // Disable the spellchecker + rcmail.enable_command('spellcheck', false); + + // Hook into the sending logic to convert the content to HTML + this.#defaultTextarea.form.addEventListener('submit', (ev) => { + const is_draft = this.#defaultTextarea.form._draft.value === '1'; + // Only convert to HTML if this actually gets send now. We want drafts to be in plain text to not trigger + // TinyMCE to take over when editing drafts. + if (!is_draft) { + this.#defaultTextarea.value = this.#editorContentAsHTML; + this.#defaultTextarea.form._is_html.value = '1'; + this.#defaultTextarea.form._markdown_editor.value = '0'; + } + }); + + rcmail.editor.spellcheck_stop(); + this.#hide(this.#defaultTextarea, this.#toolbar); + } + + stopMarkdownEditor() { + this.#defaultTextarea.value = this.#editorContent; + this.#view.destroy(); + this.#eventListeners.forEach((callback, eventName) => { + rcmail.removeEventListener(eventName, callback); + }); + this.#stopDarkModeWatcher(); + this.#hide(this.#previewIframe); + this.#show(this.#defaultTextarea, this.#toolbar); + // Re-enable the spellchecker + rcmail.enable_command('spellcheck', true); + // Force saving to mark this content as *not* edited by markdown_editor. + rcmail.submit_messageform(true); + } + + #stopDarkModeWatcher() { + this.mutationObserver.disconnect(); + } + + #setupDarkModeWatcher() { + // Callback function to execute when mutations are observed + const mutationCallback = (mutationList, observer) => { + for (const mutation of mutationList) { + if (mutation.type === 'attributes' && mutation.attributeName === 'class') { + this.#toggleTheme(); + } + } + }; + + // Create an observer instance linked to the callback function + this.mutationObserver = new MutationObserver(mutationCallback); + + // Start observing the target node for configured mutations + this.mutationObserver.observe(document.firstElementChild, { attributes: true, childList: false, subtree: false }); + } + + #setTextareaCursorPosition() { + this.#defaultTextarea.selectionEnd = this.#view.state.selection.main.head; + } + + #reloadContentFromDefaultTextarea() { + this.#debounce(() => { + this.#view.dispatch({ + changes: { + from: 0, + to: this.#view.state.doc.length, + insert: this.#defaultTextarea.value, + }, + }); + const cursorPosition = this.#defaultTextarea.selectionEnd; + if (cursorPosition !== undefined) { + this.#view.dispatch({ + selection: { + anchor: cursorPosition, + }, + scrollIntoView: true, + }); + } + this.#view.focus(); + }, 100); + } + + #debounce(callback, delay) { + this.#debounceTimers ??= {}; + if (this.#debounceTimers[callback]) { + clearTimeout(this.#debounceTimers[callback]); + } + this.#debounceTimers[callback] = setTimeout(() => callback(), delay); + } + + #save() { + // Debounce writing to the textarea using a delay of 1s. + this.#debounce(() => { + this.#defaultTextarea.value = this.#editorContent; + }, 1000); + } + + #hide(...elems) { + elems.forEach((elem) => { + elem.style.display = 'none'; + }); + } + + #show(...elems) { + elems.forEach((elem) => { + elem.style.display = null; + }); + } + + get #editorContent() { + return this.#view.state.doc.toString(); + } + + #disableToolbarButtons() { + this.#textEditingToolbarButtons.forEach((elem) => { + elem.disabled = 'disabled'; + }); + } + + #enableToolbarButtons() { + this.#textEditingToolbarButtons.forEach((elem) => { + elem.disabled = null; + }); + } + + #togglePreview() { + const previewButtonElem = document.querySelector('.codemirror-toolbar .toolbar-button-preview'); + if (this.#previewIframe.checkVisibility()) { + this.#enableToolbarButtons(); + this.#hide(this.#previewIframe); + this.#show(this.#view.contentDOM); + previewButtonElem.classList.remove('active'); + } else { + // markdown-it by default strips raw HTML, so we don't have to purify the result. + this.#domParser ??= new DOMParser(); + const doc = this.#domParser.parseFromString(this.#editorContentAsHTML, 'text/html'); + this.#previewIframe.contentDocument.body = doc.body; + this.#disableToolbarButtons(); + this.#previewIframe.style.height = this.#view.scrollDOM.scrollHeight + 'px'; + this.#hide(this.#view.contentDOM); + this.#show(this.#previewIframe); + previewButtonElem.classList.add('active'); + } + } + + get #editorContentAsHTML() { + // Replace the space in the signature separator ('\n-- \n') by a non-breakable white-space so it gets preserved in HTML. + return this.#markdownIt.render(this.#editorContent.replace(/\n-- \n/, '\n--\u00A0\n')); + } +} + +rcmail.addEventListener('init', () => { + window.markdown_editor = new Index(); + if (rcmail.env.start_markdown_editor === true) { + window.markdown_editor.startMarkdownEditor(); + } +}); diff --git a/plugins/markdown_editor/javascript/toolbar-button.js b/plugins/markdown_editor/javascript/toolbar-button.js new file mode 100644 index 000000000..24a041b95 --- /dev/null +++ b/plugins/markdown_editor/javascript/toolbar-button.js @@ -0,0 +1,23 @@ +export default class ToolbarButton extends HTMLElement { + constructor(name, content, command) { + super(); + this.name = name; + this.className = `fa-icon toolbar-button-${name}`; + this.title = rcmail.get_label(`markdown_editor.toolbar_button_${name}`), + this.command = command; + this.append(content); + } + + set disabled(value) { + if (value) { + this.classList.add('disabled'); + } else { + this.classList.remove('disabled'); + } + } + + get disabled() { + return this.classList.contains('disabled'); + } +} +customElements.define('toolbar-button', ToolbarButton); diff --git a/plugins/markdown_editor/javascript/toolbar-plugin.js b/plugins/markdown_editor/javascript/toolbar-plugin.js new file mode 100644 index 000000000..9055f8b74 --- /dev/null +++ b/plugins/markdown_editor/javascript/toolbar-plugin.js @@ -0,0 +1,24 @@ +export default class ToolbarPlugin { + destroy() { + this.element.remove(); + } + + constructor(view, buttons) { + this.view = view; + buttons.forEach((button) => { + if (typeof button.command === 'function') { + button.addEventListener('click', (ev) => { + ev.preventDefault(); + if (!button.disabled) { + button.command(this.view); + } + }); + button.classList.add('clickable'); + } + }); + this.element = document.createElement('div'); + this.element.classList.add('codemirror-toolbar'); + this.element.append(...buttons); + this.view.dom.prepend(this.element); + } +} diff --git a/plugins/markdown_editor/localization/en_US.inc b/plugins/markdown_editor/localization/en_US.inc new file mode 100644 index 000000000..c522440e2 --- /dev/null +++ b/plugins/markdown_editor/localization/en_US.inc @@ -0,0 +1,38 @@ +add_hook('message_compose_body', [$this, 'load_editor']); + $this->add_hook('message_ready', [$this, 'save_markdown_editor_usage']); + } + + public function load_editor(array $args): array + { + $start_markdown_editor = false; + + if (isset($args['message']->headers)) { + $draft_info = rcmail_sendmail::draftinfo_decode($args['message']->headers->get('x-draft-info')); + $start_markdown_editor = $draft_info['markdown_editor'] === 'yes'; + } + + $rcmail = rcube::get_instance(); + $rcmail->output->set_env('start_markdown_editor', $start_markdown_editor); + + // Load the editor files. + $this->include_script('markdown_editor.min.js', ['type' => 'module']); + $this->include_stylesheet($this->local_skin_path() . '/styles/markdown_editor.min.css'); + $rcmail->output->set_env('markdown_editor_iframe_css_path', $this->url($this->local_skin_path() . '/styles/iframe.min.css')); + $this->add_texts('localization', true); + + return $args; + } + + public function save_markdown_editor_usage($args) + { + if (isset($_POST['_markdown_editor']) && $_POST['_markdown_editor'] === '1') { + $draft_info = rcmail_sendmail::draftinfo_decode($args['message']->headers()['X-Draft-Info']); + $draft_info['markdown_editor'] = 'yes'; + $args['message']->headers(['X-Draft-Info' => rcmail_sendmail::draftinfo_encode($draft_info)], true); + } + + return $args; + } +} diff --git a/plugins/markdown_editor/package-lock.json b/plugins/markdown_editor/package-lock.json new file mode 100644 index 000000000..2c43e76ab --- /dev/null +++ b/plugins/markdown_editor/package-lock.json @@ -0,0 +1,1925 @@ +{ + "name": "markdown_editor", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@codemirror/commands": "^6.8.1", + "@codemirror/lang-markdown": "^6.3.3", + "@codemirror/language": "^6.11.2", + "@codemirror/state": "^6.5.2", + "@codemirror/view": "^6.38.1", + "@fsegurai/codemirror-theme-material-dark": "^6.2.2", + "@fsegurai/codemirror-theme-material-light": "^6.2.2", + "@rollup/plugin-node-resolve": "^16.0.1", + "@rollup/plugin-terser": "^0.4.4", + "codemirror-markdown-commands": "^0.1.0", + "less": "^4.4.1", + "less-plugin-clean-css": "^1.6.0", + "markdown-it": "^14.1.0", + "rollup": "^4.45.1" + }, + "devDependencies": { + "rollup-plugin-bundle-stats": "^4.21.3" + } + }, + "node_modules/@bundle-stats/cli-utils": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@bundle-stats/cli-utils/-/cli-utils-4.21.3.tgz", + "integrity": "sha512-xyJDpT7mXfaUP/1YIBEkiFKetOOVo85Cq03H9CRMe91X0V11jyHy66sbfFreEKGJpQTTtaCIQmIkGpDSXRg8/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bundle-stats/html-templates": "^4.21.3", + "@bundle-stats/plugin-webpack-filter": "^4.21.3", + "@bundle-stats/utils": "^4.21.3", + "find-cache-dir": "^3.1.0", + "lodash": "^4.17.21", + "stream-chain": "^3.0.1", + "stream-json": "^1.8.0" + }, + "engines": { + "node": ">= 14.0" + } + }, + "node_modules/@bundle-stats/html-templates": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@bundle-stats/html-templates/-/html-templates-4.21.3.tgz", + "integrity": "sha512-M7HJ3W6o5Bb+dze+Fghv70WWHhypKKwAfJENZICl2Opmq8qsQ9L91DdvfnrGBJe9wS2fQwIUCRE2J3InU5BoFg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@bundle-stats/plugin-webpack-filter": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@bundle-stats/plugin-webpack-filter/-/plugin-webpack-filter-4.21.3.tgz", + "integrity": "sha512-eTpk5y3uSmqSw+c7C62hsSfDUSCes/XfBTzk5jRH1KujtDjlXu5QDTzzM72CbuuoQD1MOqoZ4Gn16t/zcNnicw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">= 14.0" + }, + "peerDependencies": { + "core-js": "^3.0.0" + } + }, + "node_modules/@bundle-stats/plugin-webpack-validate": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@bundle-stats/plugin-webpack-validate/-/plugin-webpack-validate-4.21.3.tgz", + "integrity": "sha512-yIn0KEAvyHtGF36CrlfXMmcdy8JhEsstNLeswpFKO/sLJI9fz9zC37GtbFDcNHLyFv9t1CqzzYnJjYPYxG7uEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "4.17.21", + "superstruct": "2.0.2", + "tslib": "2.8.1" + }, + "engines": { + "node": ">= 14.0" + } + }, + "node_modules/@bundle-stats/utils": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@bundle-stats/utils/-/utils-4.21.3.tgz", + "integrity": "sha512-o8F/Jbeai2WDWeypluuP0s2uL6AuZROUerTeB+W0sp0bOxbRc3OkkkQEckRHuQmI7OQ5c5JElrs5Ia/NuOs6+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bundle-stats/plugin-webpack-filter": "^4.21.3", + "@bundle-stats/plugin-webpack-validate": "^4.21.3", + "serialize-query-params": "2.0.2" + }, + "engines": { + "node": ">= 14.0" + }, + "peerDependencies": { + "core-js": "^3.0.0", + "lodash": "^4.0.0" + } + }, + "node_modules/@codemirror/autocomplete": { + "version": "6.18.6", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz", + "integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz", + "integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-css": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", + "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/css": "^1.1.7" + } + }, + "node_modules/@codemirror/lang-html": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz", + "integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/css": "^1.1.0", + "@lezer/html": "^1.3.0" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz", + "integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-markdown": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.3.3.tgz", + "integrity": "sha512-1fn1hQAPWlSSMCvnF810AkhWpNLkJpl66CRfIy3vVl20Sl4NwChkorCHqpMtNbXr1EuMJsrDnhEpjZxKZ2UX3A==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.7.1", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.3.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.2.1", + "@lezer/markdown": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.2.tgz", + "integrity": "sha512-p44TsNArL4IVXDTbapUmEkAlvWs2CFQbcfc0ymDsis1kH2wh0gcY96AS29c/vp2d0y2Tquk1EDSaawpzilUiAw==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.1.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.8.5", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz", + "integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.35.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", + "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.38.1", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.1.tgz", + "integrity": "sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.5.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@emnapi/core": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz", + "integrity": "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/wasi-threads": "1.0.4", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", + "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.4.tgz", + "integrity": "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@fsegurai/codemirror-theme-material-dark": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@fsegurai/codemirror-theme-material-dark/-/codemirror-theme-material-dark-6.2.2.tgz", + "integrity": "sha512-RISNwSHEmxpUwYB81UuZSzxpTO49znJr4eBnjNCuhVP4Q88TTHh2ENlNcKEvhzIiaH81Nzk1Q68u4InquxG9HA==", + "license": "MIT", + "peerDependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/@fsegurai/codemirror-theme-material-light": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@fsegurai/codemirror-theme-material-light/-/codemirror-theme-material-light-6.2.2.tgz", + "integrity": "sha512-HkXow6grTdK6paZI4YRIdxghZo5XpedRE8PB96grb6EMOOXDs8xTUbUgkEP05MI8gjUPMAYF+ZuDZSOGIiMqkA==", + "license": "MIT", + "peerDependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lezer/common": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", + "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==", + "license": "MIT" + }, + "node_modules/@lezer/css": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.0.tgz", + "integrity": "sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/highlight": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", + "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/html": { + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz", + "integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/javascript": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.1.tgz", + "integrity": "sha512-ATOImjeVJuvgm3JQ/bpo2Tmv55HSScE2MTPnKRMRIPx2cLhHGyX2VnqpHhtIV1tVzIjZDbcWQm+NCTF40ggZVw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", + "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/markdown": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.4.3.tgz", + "integrity": "sha512-kfw+2uMrQ/wy/+ONfrH83OkdFNM0ye5Xq96cLlaCy7h5UT9FO54DU4oRoIc0CSBh5NWmWuiIJA7NGLMJbQ+Oxg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "license": "MIT" + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.3.tgz", + "integrity": "sha512-rZxtMsLwjdXkMUGC3WwsPwLNVqVqnTJT6MNIB6e+5fhMcSCPP0AOsNWuMQ5mdCq6HNjs/ZeWAEchpqeprqBD2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/core": "^1.4.5", + "@emnapi/runtime": "^1.4.5", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@oxc-project/runtime": { + "version": "0.82.3", + "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.82.3.tgz", + "integrity": "sha512-LNh5GlJvYHAnMurO+EyA8jJwN1rki7l3PSHuosDh2I7h00T6/u9rCkUjg/SvPmT1CZzvhuW0y+gf7jcqUy/Usg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.82.3", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.82.3.tgz", + "integrity": "sha512-6nCUxBnGX0c6qfZW5MaF6/fmu5dHJDMiMPaioKHKs5mi5+8/FHQ7WGjgQIz1zxpmceMYfdIXkOaLYE+ejbuOtA==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-beta.34", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.34.tgz", + "integrity": "sha512-jf5GNe5jP3Sr1Tih0WKvg2bzvh5T/1TA0fn1u32xSH7ca/p5t+/QRr4VRFCV/na5vjwKEhwWrChsL2AWlY+eoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-beta.34", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.34.tgz", + "integrity": "sha512-2F/TqH4QuJQ34tgWxqBjFL3XV1gMzeQgUO8YRtCPGBSP0GhxtoFzsp7KqmQEothsxztlv+KhhT9Dbg3HHwHViQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-beta.34", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.34.tgz", + "integrity": "sha512-E1QuFslgLWbHQ8Qli/AqUKdfg0pockQPwRxVbhNQ74SciZEZpzLaujkdmOLSccMlSXDfFCF8RPnMoRAzQ9JV8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-beta.34", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.34.tgz", + "integrity": "sha512-VS8VInNCwnkpI9WeQaWu3kVBq9ty6g7KrHdLxYMzeqz24+w9hg712TcWdqzdY6sn+24lUoMD9jTZrZ/qfVpk0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-beta.34", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.34.tgz", + "integrity": "sha512-4St4emjcnULnxJYb/5ZDrH/kK/j6PcUgc3eAqH5STmTrcF+I9m/X2xvSF2a2bWv1DOQhxBewThu0KkwGHdgu5w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-beta.34", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.34.tgz", + "integrity": "sha512-a737FTqhFUoWfnebS2SnQ2BS50p0JdukdkUBwy2J06j4hZ6Eej0zEB8vTfAqoCjn8BQKkXBy+3Sx0IRkgwz1gA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-beta.34", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.34.tgz", + "integrity": "sha512-NH+FeQWKyuw0k+PbXqpFWNfvD8RPvfJk766B/njdaWz4TmiEcSB0Nb6guNw1rBpM1FmltQYb3fFnTumtC6pRfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-beta.34", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.34.tgz", + "integrity": "sha512-Q3RSCivp8pNadYK8ke3hLnQk08BkpZX9BmMjgwae2FWzdxhxxUiUzd9By7kneUL0vRQ4uRnhD9VkFQ+Haeqdvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-beta.34", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.34.tgz", + "integrity": "sha512-wDd/HrNcVoBhWWBUW3evJHoo7GJE/RofssBy3Dsiip05YUBmokQVrYAyrboOY4dzs/lJ7HYeBtWQ9hj8wlyF0A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-beta.34", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.34.tgz", + "integrity": "sha512-dH3FTEV6KTNWpYSgjSXZzeX7vLty9oBYn6R3laEdhwZftQwq030LKL+5wyQdlbX5pnbh4h127hpv3Hl1+sj8dg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "peer": true + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-beta.34", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.34.tgz", + "integrity": "sha512-y5BUf+QtO0JsIDKA51FcGwvhJmv89BYjUl8AmN7jqD6k/eU55mH6RJYnxwCsODq5m7KSSTigVb6O7/GqB8wbPw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.0.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-beta.34", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.34.tgz", + "integrity": "sha512-ga5hFhdTwpaNxEiuxZHWnD3ed0GBAzbgzS5tRHpe0ObptxM1a9Xrq6TVfNQirBLwb5Y7T/FJmJi3pmdLy95ljg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rolldown/binding-win32-ia32-msvc": { + "version": "1.0.0-beta.34", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.34.tgz", + "integrity": "sha512-4/MBp9T9eRnZskxWr8EXD/xHvLhdjWaeX/qY9LPRG1JdCGV3DphkLTy5AWwIQ5jhAy2ZNJR5z2fYRlpWU0sIyQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-beta.34", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.34.tgz", + "integrity": "sha512-7O5iUBX6HSBKlQU4WykpUoEmb0wQmonb6ziKFr3dJTHud2kzDnWMqk344T0qm3uGv9Ddq6Re/94pInxo1G2d4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.34", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.34.tgz", + "integrity": "sha512-LyAREkZHP5pMom7c24meKmJCdhf2hEyvam2q0unr3or9ydwDL+DJ8chTF6Av/RFPb3rH8UFBdMzO5MxTZW97oA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz", + "integrity": "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-terser": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", + "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", + "license": "MIT", + "dependencies": { + "serialize-javascript": "^6.0.1", + "smob": "^1.0.0", + "terser": "^5.17.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.2.0.tgz", + "integrity": "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz", + "integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.1.tgz", + "integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.1.tgz", + "integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.1.tgz", + "integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.1.tgz", + "integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.1.tgz", + "integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.1.tgz", + "integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.1.tgz", + "integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.1.tgz", + "integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.1.tgz", + "integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.1.tgz", + "integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.1.tgz", + "integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.1.tgz", + "integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.1.tgz", + "integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.1.tgz", + "integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.1.tgz", + "integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.1.tgz", + "integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.1.tgz", + "integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.1.tgz", + "integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.1.tgz", + "integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz", + "integrity": "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansis": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.1.0.tgz", + "integrity": "sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==", + "dev": true, + "license": "ISC", + "peer": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "license": "MIT", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/codemirror-markdown-commands": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/codemirror-markdown-commands/-/codemirror-markdown-commands-0.1.0.tgz", + "integrity": "sha512-be/h9E0ZOezWvTSP14rZ2ZTtLYUadyL8hbziVn6p62pCCo0GTecrymo35wQzs+44ubigcKNnyo0giiOW9gIsKw==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "license": "MIT", + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/core-js": { + "version": "3.45.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.1.tgz", + "integrity": "sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "license": "MIT", + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC", + "optional": true + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "license": "MIT", + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "license": "MIT" + }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "license": "MIT" + }, + "node_modules/less": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/less/-/less-4.4.1.tgz", + "integrity": "sha512-X9HKyiXPi0f/ed0XhgUlBeFfxrlDP3xR4M7768Zl+WXLUViuL9AOPPJP4nCV0tgRWvTYvpNmN0SFhZOQzy16PA==", + "license": "Apache-2.0", + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/less-plugin-clean-css": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/less-plugin-clean-css/-/less-plugin-clean-css-1.6.0.tgz", + "integrity": "sha512-jwXX6WlXT57OVCXa5oBJBaJq1b4s1BOKeEEoAL2UTeEitogQWfTcBbLT/vow9pl0N0MXV8Mb4KyhTGG0YbEKyQ==", + "license": "Apache-2.0", + "dependencies": { + "clean-css": "5.3.3" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/less/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "license": "MIT", + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/less/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT" + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/needle": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", + "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "license": "MIT", + "optional": true + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-beta.34", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.34.tgz", + "integrity": "sha512-Wwh7EwalMzzX3Yy3VN58VEajeR2Si8+HDNMf706jPLIqU7CxneRW+dQVfznf5O0TWTnJyu4npelwg2bzTXB1Nw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@oxc-project/runtime": "=0.82.3", + "@oxc-project/types": "=0.82.3", + "@rolldown/pluginutils": "1.0.0-beta.34", + "ansis": "^4.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-beta.34", + "@rolldown/binding-darwin-arm64": "1.0.0-beta.34", + "@rolldown/binding-darwin-x64": "1.0.0-beta.34", + "@rolldown/binding-freebsd-x64": "1.0.0-beta.34", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.34", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.34", + "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.34", + "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.34", + "@rolldown/binding-linux-x64-musl": "1.0.0-beta.34", + "@rolldown/binding-openharmony-arm64": "1.0.0-beta.34", + "@rolldown/binding-wasm32-wasi": "1.0.0-beta.34", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.34", + "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.34", + "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.34" + } + }, + "node_modules/rollup": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz", + "integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.45.1", + "@rollup/rollup-android-arm64": "4.45.1", + "@rollup/rollup-darwin-arm64": "4.45.1", + "@rollup/rollup-darwin-x64": "4.45.1", + "@rollup/rollup-freebsd-arm64": "4.45.1", + "@rollup/rollup-freebsd-x64": "4.45.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", + "@rollup/rollup-linux-arm-musleabihf": "4.45.1", + "@rollup/rollup-linux-arm64-gnu": "4.45.1", + "@rollup/rollup-linux-arm64-musl": "4.45.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", + "@rollup/rollup-linux-riscv64-gnu": "4.45.1", + "@rollup/rollup-linux-riscv64-musl": "4.45.1", + "@rollup/rollup-linux-s390x-gnu": "4.45.1", + "@rollup/rollup-linux-x64-gnu": "4.45.1", + "@rollup/rollup-linux-x64-musl": "4.45.1", + "@rollup/rollup-win32-arm64-msvc": "4.45.1", + "@rollup/rollup-win32-ia32-msvc": "4.45.1", + "@rollup/rollup-win32-x64-msvc": "4.45.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-bundle-stats": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/rollup-plugin-bundle-stats/-/rollup-plugin-bundle-stats-4.21.3.tgz", + "integrity": "sha512-JqdsZz3byWH8zhqPsosR8/mCMLszpLHhMtu3NYlXsN/LoXm342/urE5dOVaL+qWP/4mLqI1lNkbxG9QoLX1M6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bundle-stats/cli-utils": "^4.21.3", + "rollup-plugin-webpack-stats": "2.1.4", + "tslib": "2.8.1" + }, + "engines": { + "node": ">= 16.0" + }, + "peerDependencies": { + "rolldown": "^1.0.0-beta.0", + "rollup": "^3.0.0 || ^4.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "rolldown": { + "optional": true + }, + "rollup": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/rollup-plugin-stats": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-stats/-/rollup-plugin-stats-1.5.0.tgz", + "integrity": "sha512-TsWaV7ulwPA9JhqGJemrDJkvXNeNQb60lB13gIcT2kVDXlBM/PQD3GqVyhCJpvn43Y4YT5+VmWDRsbIAbuilBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "rolldown": "^1.0.0-beta.0", + "rollup": "^3.0.0 || ^4.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "rolldown": { + "optional": true + }, + "rollup": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/rollup-plugin-webpack-stats": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/rollup-plugin-webpack-stats/-/rollup-plugin-webpack-stats-2.1.4.tgz", + "integrity": "sha512-kv9W0rK9Qy1Q8/vkxEPGOwJvq7zVERnHD13At5Jv15FPPOXwxm1LblXtpuASeoM1ALhOPD0sJSDz64qgtSgh7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "rollup-plugin-stats": "1.5.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "rolldown": "^1.0.0-beta.0", + "rollup": "^3.0.0 || ^4.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT", + "optional": true + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "license": "ISC", + "optional": true + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serialize-query-params": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/serialize-query-params/-/serialize-query-params-2.0.2.tgz", + "integrity": "sha512-1chMo1dST4pFA9RDXAtF0Rbjaut4is7bzFbI1Z26IuMub68pNCILku85aYmeFhvnY//BXUPUhoRMjYcsT93J/Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/smob": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz", + "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==", + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/stream-chain": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-3.4.0.tgz", + "integrity": "sha512-cyDiaDqAfgmeiv0PWFXCg9oKNVYNzYxHK9j5CMsYMHZDk+/yYcSV+CXQZliZ0U4mNU8DLqiVNZXUfs8BqhgwMw==", + "dev": true, + "license": "BSD-3-Clause", + "funding": { + "url": "https://github.com/sponsors/uhop" + } + }, + "node_modules/stream-json": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz", + "integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "stream-chain": "^2.2.5" + } + }, + "node_modules/stream-json/node_modules/stream-chain": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", + "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/style-mod": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", + "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", + "license": "MIT" + }, + "node_modules/superstruct": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz", + "integrity": "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/terser": { + "version": "5.43.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", + "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.14.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + } + } +} diff --git a/plugins/markdown_editor/package.json b/plugins/markdown_editor/package.json new file mode 100644 index 000000000..10760db6e --- /dev/null +++ b/plugins/markdown_editor/package.json @@ -0,0 +1,29 @@ +{ + "type": "module", + "dependencies": { + "@codemirror/commands": "^6.8.1", + "@codemirror/lang-markdown": "^6.3.3", + "@codemirror/language": "^6.11.2", + "@codemirror/state": "^6.5.2", + "@codemirror/view": "^6.38.1", + "@fsegurai/codemirror-theme-material-dark": "^6.2.2", + "@fsegurai/codemirror-theme-material-light": "^6.2.2", + "@rollup/plugin-node-resolve": "^16.0.1", + "@rollup/plugin-terser": "^0.4.4", + "codemirror-markdown-commands": "^0.1.0", + "less": "^4.4.1", + "less-plugin-clean-css": "^1.6.0", + "markdown-it": "^14.1.0", + "rollup": "^4.45.1" + }, + "devDependencies": { + "rollup-plugin-bundle-stats": "^4.21.3" + }, + "scripts": { + "build": "npm run build-js && npm run build-css", + "build-js": "rollup -c", + "build-css": "cd skins/elastic/styles && lessc --verbose --clean-css='--s1' markdown_editor.less markdown_editor.min.css && lessc --verbose --clean-css='--s1' iframe.less iframe.min.css", + "watch-js": "rollup -c -w", + "clean": "rm -rf node_modules markdown_editor.min.js skins/elastic/styles/{markdown_editor,iframe}.min.css" + } +} diff --git a/plugins/markdown_editor/rollup.config.js b/plugins/markdown_editor/rollup.config.js new file mode 100644 index 000000000..19e52b939 --- /dev/null +++ b/plugins/markdown_editor/rollup.config.js @@ -0,0 +1,22 @@ +import { nodeResolve } from '@rollup/plugin-node-resolve'; +// Un-comment to generate bundle-stats.html +// import { bundleStats } from 'rollup-plugin-bundle-stats'; +import terser from '@rollup/plugin-terser'; + +export default { + input: 'javascript/index.js', + output: [ + { + file: 'markdown_editor.min.js', + format: 'es', + plugins: [terser()], + // Un-comment to generate bundle-stats.html + // assetFileNames: 'assets/[name].[hash][extname]', + // chunkFileNames: 'assets/[name].[hash].js', + // entryFileNames: 'assets/[name].[hash].js', + }, + ], + plugins: [nodeResolve()], + // Un-comment to generate bundle-stats.html + // plugins: [nodeResolve(), bundleStats()] +}; diff --git a/plugins/markdown_editor/skins/elastic/styles/iframe.less b/plugins/markdown_editor/skins/elastic/styles/iframe.less new file mode 100644 index 000000000..2148f12cb --- /dev/null +++ b/plugins/markdown_editor/skins/elastic/styles/iframe.less @@ -0,0 +1,22 @@ +@import (reference) "../../../../../skins/elastic/styles/colors"; + +// These are the same styles we send in outgoing HTML messages. +body { + color: @color-font; +} + +html.dark-mode body { + color: @color-dark-font; +} + +blockquote { + padding: 0 0.4em; + border-left: #1010ff 2px solid; + margin: 0; +} + +pre, div.pre { + margin: 0; + padding: 0; + font-family: monospace; +} diff --git a/plugins/markdown_editor/skins/elastic/styles/markdown_editor.less b/plugins/markdown_editor/skins/elastic/styles/markdown_editor.less new file mode 100644 index 000000000..5cf181578 --- /dev/null +++ b/plugins/markdown_editor/skins/elastic/styles/markdown_editor.less @@ -0,0 +1,142 @@ +@import (reference) "../../../../../skins/elastic/styles/colors"; +@import (reference) "../../../../../skins/elastic/styles/mixins"; + +#composebodycontainer.html-editor .editor-toolbar .mce-i-html { + float: left; + margin-left: 6px; +} + +.markdown-editor-start-button { + display: block; + float: left; + margin: 2px 0px 2px 0px; + border-radius: .25rem; + color: @color-toolbar-button; + + &:hover { + border-color: #e2e4e7; + background-color: #dee0e2; // from TinyMCE + } + + svg { + display: block; + width: 24px; + margin: 0.5rem 0.4rem; + fill: @color-toolbar-button; + } +} + +#markdown-editor-container { + .cm-editor { + min-height: 45vh; + height: auto; + resize: vertical; + border: solid thin @color-input-border; + border-radius: .25rem; + } + + .cm-editor:has(.cm-content:focus) { + border-color: @color-main; + box-shadow: 0 0 0 .2rem @color-input-border-focus-shadow; + } + + .cm-content { + padding: 4px; + font-size: 13px; + line-height: 1.5; + } + + .codemirror-toolbar { + display: flex; + flex-wrap: wrap; + min-height: 2.47rem; + background-color: #eee; + padding: 0.4rem 0.2rem; + border-bottom: solid thin @color-input-border; + + .fa-icon { + &:extend(.font-icon-class); + cursor: default; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 1em; + height: 1.5rem; + width: 2rem; + margin: 0; + padding: 0; + } + + .fa-icon.toolbar-button-space { + /* Make this element take all left-over space */ + flex: 1; + } + + .fa-icon.toolbar-button-separator { + color: lightgrey; + margin: 0; + width: 1rem; + } + + .fa-icon.clickable { + &:hover { + cursor: pointer; + border-color: #e2e4e7; + background-color: #dee0e2; // from TinyMCE + } + + &.active { + color: @color-main; + } + + &.disabled, + &.disabled:hover { + opacity: .5; + background-color: inherit; + cursor: default; + } + } + } + + #markdown-editor-preview { + height: 45vh; + width: 100%; + border: none; + } +} + +html.dark-mode { + .markdown-editor-start-button { + color: @color-dark-font; + + svg { + fill: @color-dark-font; + } + + &:hover, + &:focus { + background-color: @color-dark-input-addon-background-focus; + } + } + + #markdown-editor-container .codemirror-toolbar { + background-color: @color-dark-input-addon-background; + + .fa-icon { + color: @color-dark-font; + + &.clickable:hover { + background-color: @color-dark-input-addon-background-focus; + } + + &.disabled, + &.disabled:hover { + background-color: inherit; + } + } + } + + #markdown-preview { + color: @color-dark-font; + } +} diff --git a/plugins/markdown_editor/tests/Browser/ComposeTest.php b/plugins/markdown_editor/tests/Browser/ComposeTest.php new file mode 100644 index 000000000..3e8bb3ec7 --- /dev/null +++ b/plugins/markdown_editor/tests/Browser/ComposeTest.php @@ -0,0 +1,113 @@ +browse(static function ($browser) { + $browser->go('mail'); + $browser->waitFor('#taskmenu .compose'); + $browser->click('#taskmenu .compose'); + $browser->waitFor('#compose-content .markdown-editor-start-button', 10); + $browser->click('.markdown-editor-start-button'); + + $browser->waitFor('#markdown-editor-container .cm-editor'); + $browser->assertVisible('#markdown-editor-container .cm-content'); + $browser->assertMissing('#markdown-editor-container #markdown-editor-preview'); + $browser->assertVisible('#markdown-editor-container .codemirror-toolbar'); + $browser->assertVisible('#markdown-editor-container .codemirror-toolbar .toolbar-button-quit'); + $browser->assertVisible('#markdown-editor-container .codemirror-toolbar .toolbar-button-bold'); + $browser->assertVisible('#markdown-editor-container .codemirror-toolbar .toolbar-button-italic'); + $browser->assertVisible('#markdown-editor-container .codemirror-toolbar .toolbar-button-blockquote'); + $browser->assertVisible('#markdown-editor-container .codemirror-toolbar .toolbar-button-help'); + $browser->assertVisible('#markdown-editor-container .codemirror-toolbar .toolbar-button-preview'); + $browser->assertElementsCount('#markdown-editor-container .codemirror-toolbar .clickable', 16); + $browser->assertElementsCount('#markdown-editor-container .codemirror-toolbar .clickable.disabled', 0); + + // Test that clicking the preview button disables all but two toolbar buttons. + $browser->click('#markdown-editor-container .codemirror-toolbar .toolbar-button-preview'); + $browser->assertVisible('#markdown-editor-container .codemirror-toolbar .toolbar-button-bold.disabled'); + $browser->assertElementsCount('#markdown-editor-container .codemirror-toolbar .clickable.disabled', 13); + $browser->assertVisible('#markdown-editor-container #markdown-editor-preview'); + $browser->assertMissing('#markdown-editor-container .cm-content'); + + // Test that clicking the preview button again re-enables all toolbar buttons. + $browser->click('#markdown-editor-container .codemirror-toolbar .toolbar-button-preview'); + $browser->assertElementsCount('#markdown-editor-container .codemirror-toolbar .clickable.disabled', 0); + $browser->assertMissing('#markdown-editor-container #markdown-editor-preview'); + $browser->assertVisible('#markdown-editor-container .cm-content'); + + // Test the preview iframe. + $browser->keys('#markdown-editor-container .cm-content', 'Hello, World!'); + $browser->click('#markdown-editor-container .codemirror-toolbar .toolbar-button-h1'); + $browser->assertSeeIn('#markdown-editor-container .cm-content .cm-line:first-child span:nth-child(1)', '#'); + $browser->assertSeeIn('#markdown-editor-container .cm-content .cm-line:first-child span:nth-child(2)', 'Hello, World!'); + $browser->click('#markdown-editor-container .codemirror-toolbar .toolbar-button-preview'); + $browser->withinFrame('#markdown-editor-container #markdown-editor-preview', static function ($browser) { + $browser->assertSeeIn('h1', 'Hello, World!'); + }); + $browser->click('#markdown-editor-container .codemirror-toolbar .toolbar-button-preview'); + $browser->assertMissing('#markdown-editor-container #markdown-editor-preview'); + + // Test that drafts are still plaintext/markdown. + $browser->click('.header .menu .save.draft'); + $browser->waitForMessage('confirmation', 'Message saved to Drafts.'); + $browser->waitUntilMissing('#messagestack .confirmation', 10); + // For some reason this only works if we click the button again. + $browser->click('.header .menu .save.draft'); + $browser->waitForMessage('confirmation', 'Message saved to Drafts.'); + $browser->waitUntilMissing('#messagestack .confirmation', 10); + $browser->click('#taskmenu .mail'); + if (!$browser->isDesktop()) { + $browser->click('#messagelist-header .back-sidebar-button'); + } + $browser->waitFor('#mailboxlist .mailbox.inbox.selected'); + $browser->waitUntilNotBusy(); + $browser->click('#mailboxlist .mailbox.drafts a'); + if ($browser->isDesktop()) { + $browser->waitFor('#mailboxlist .mailbox.drafts.selected'); + } + $browser->waitUntilNotBusy(); + $browser->waitFor('#messagelist .message:first-child'); + $browser->click('#messagelist .message:first-child'); + $browser->waitFor('#messagecontframe'); + $browser->withinFrame('#messagecontframe', static function ($browser) { + $browser->waitFor('#message-content #messagebody'); + $browser->assertSeeIn('#message-content #messagebody', '# Hello, World!'); + }); + + // Test that the editor starts automatically for a message that we had edited with it. + $browser->withinFrame('#messagecontframe', static function ($browser) { + $browser->press('#message-buttons .btn'); + }); + $browser->waitFor('#markdown-editor-container .cm-content'); + $browser->assertSeeIn('#markdown-editor-container .cm-content .cm-line:first-child', '# Hello, World!'); + + // Test that sent messages are converted to HTML. + $browser->keys('#compose_to input', 'user@example.com'); + $browser->keys('#compose_subject input', 'markdown-editor test'); + $browser->click('#compose-content .btn.send'); + if (!$browser->isDesktop()) { + $browser->waitFor('#messagelist-header .back-sidebar-button'); + $browser->click('#messagelist-header .back-sidebar-button'); + } + $browser->waitFor('#mailboxlist .mailbox.drafts.selected'); + $browser->waitUntilNotBusy(); + $browser->click('#mailboxlist .mailbox.sent a'); + if ($browser->isDesktop()) { + $browser->waitFor('#mailboxlist .mailbox.sent.selected'); + } + $browser->waitFor('#messagelist .message:first-child'); + $browser->click('#messagelist .message:first-child'); + $browser->waitFor('#messagecontframe'); + $browser->withinFrame('#messagecontframe', static function ($browser) { + $browser->waitFor('#message-content #messagebody'); + $browser->assertSeeIn('#message-content #messagebody h1', 'Hello, World!'); + }); + }); + } +} diff --git a/program/include/rcmail_sendmail.php b/program/include/rcmail_sendmail.php index d35862d18..3fcc09ecd 100644 --- a/program/include/rcmail_sendmail.php +++ b/program/include/rcmail_sendmail.php @@ -886,6 +886,10 @@ class rcmail_sendmail { $info = []; + if (empty(trim($str))) { + return $info; + } + foreach (preg_split('/;\s+/', $str) as $part) { [$key, $val] = explode('=', $part, 2); if (str_starts_with($val, 'B::')) {