diff --git a/integreat_cms/api/v3/pages.py b/integreat_cms/api/v3/pages.py index 7fa803bb5e..fd2318f6e9 100644 --- a/integreat_cms/api/v3/pages.py +++ b/integreat_cms/api/v3/pages.py @@ -90,6 +90,7 @@ def transform_page( expand_shortcodes(page_translation.combined_text, context=context) ), "content": expand_shortcodes(page_translation.combined_text, context=context), + "page_id": page_translation.page.id, "parent": parent, "order": order, "available_languages": page_translation.available_languages_dict, diff --git a/integreat_cms/cms/models/utils.py b/integreat_cms/cms/models/utils.py index c660f9e26e..ab8fee6b32 100644 --- a/integreat_cms/cms/models/utils.py +++ b/integreat_cms/cms/models/utils.py @@ -50,6 +50,8 @@ def format_object_translation( + object_translation.link_title.tail ) return { + "id": object_translation.id, + "foreign_object_id": object_translation.foreign_object.id, "path": object_translation.path(), "title": text_title, "html_title": html_title, diff --git a/integreat_cms/cms/templates/_tinymce_config.html b/integreat_cms/cms/templates/_tinymce_config.html index 6eccad192a..5137095278 100644 --- a/integreat_cms/cms/templates/_tinymce_config.html +++ b/integreat_cms/cms/templates/_tinymce_config.html @@ -19,8 +19,11 @@ {% comment %} Styling for text diff taken from style.scss {% endcomment %} {% firstof font_style|add:"del { background-color: rgb(252 165 165); } ins { background-color: rgb(134 239 172); text-decoration-line: none; }" as content_style %}
{ }, insert: { title: "Insert", - items: "openmediacenter add_link add_contact media | charmap hr", + items: "add_shortcode_page | add_shortcode_contact | openmediacenter add_link add_contact media | charmap hr", }, }, link_title: false, @@ -110,6 +110,7 @@ window.addEventListener("load", () => { mediacenter: tinymceConfig.getAttribute("data-custom-plugins"), custom_link_input: tinymceConfig.getAttribute("data-custom-plugins"), custom_contact_input: tinymceConfig.getAttribute("data-custom-plugins"), + shortcodes: tinymceConfig.getAttribute("data-custom-plugins"), }, link_default_protocol: "https", link_target_list: false, diff --git a/integreat_cms/static/src/js/tinymce-plugins/custom_link_input/plugin.js b/integreat_cms/static/src/js/tinymce-plugins/custom_link_input/plugin.js index 0d00577df6..14cfd2e84f 100644 --- a/integreat_cms/static/src/js/tinymce-plugins/custom_link_input/plugin.js +++ b/integreat_cms/static/src/js/tinymce-plugins/custom_link_input/plugin.js @@ -1,4 +1,5 @@ import { getCsrfToken } from "../../utils/csrf-token"; +import { stripProtocol } from "../../utils/url-tools"; (() => { const tinymceConfig = document.getElementById("tinymce-config-options"); @@ -47,7 +48,24 @@ import { getCsrfToken } from "../../utils/csrf-token"; }; tinymce.PluginManager.add("custom_link_input", (editor, _url) => { - const isAnchor = (node) => node.nodeName.toLowerCase() === "a" && node.href && node.isContentEditable; + const internalPageURLRegex = new RegExp(String.raw` + ^[^:/]*:// + ${stripProtocol(tinymceConfig.getAttribute("data-webapp-url")).replace(/\/$/, "")} + ( + / + ([^/]+) + / + ([^/]{2,8}) + / + ( + ([^?#]+) + / + )? + ([^/?#]+) + ) + `.replace(/\s+/g, "")); + + const isAnchor = (node) => node.nodeName.toLowerCase() === "a" && node.href && node.isContentEditable && !internalPageURLRegex.exec(node.href); const getAnchor = () => { let node = editor.selection.getNode(); while (node !== null) { diff --git a/integreat_cms/static/src/js/tinymce-plugins/shortcodes/contact.ts b/integreat_cms/static/src/js/tinymce-plugins/shortcodes/contact.ts new file mode 100644 index 0000000000..ec8764dcd6 --- /dev/null +++ b/integreat_cms/static/src/js/tinymce-plugins/shortcodes/contact.ts @@ -0,0 +1,74 @@ +import { ShortcodeHandle, AcceptArbitraryArguments, PargsDescriptor, KWargsDescriptor } from "./utils"; +import type { ToolbarButtonInstanceApi, ContextFormInstanceApi, ContextFormButtonInstanceApi, ContextFormToggleButtonInstanceApi, DialogInstanceApi, DialogData, MenuItemInstanceApi, DialogSpec, BodyComponentSpec } from "../tinymce.d.ts"; +import { Editor } from "tinymce"; +import TomSelect from "tom-select"; +import { getCsrfToken } from "../../utils/csrf-token"; +import { stripProtocol } from "../../utils/url-tools"; +import { evaluateOnceDecorator } from "../../utils/caching-functions"; + +class ContactHandle extends ShortcodeHandle { + keyword = "contact"; + addIcon = "contact"; + editIcon = "contact"; + removeIcon = "remove"; + + pargs: PargsDescriptor = [ + [["Contact ID", "The ID of the Contact whose details should be displayed"]], + [ + ["address", "Whether the address should be shown and other, not explicitly wanted details should be hidden"], + ["email", "Whether the email should be shown and other, not explicitly wanted details should be hidden"], + ["phone_number", "Whether the phone number should be shown and other, not explicitly wanted details should be hidden"], + ["mobile_phone_number", "Whether the mobile phone number should be shown and other, not explicitly wanted details should be hidden"], + ["website", "Whether the website should be shown and other, not explicitly wanted details should be hidden"], + ], + ]; + kwargs: KWargsDescriptor = []; + + domainAndPrefix: () => string = evaluateOnceDecorator(() => stripProtocol(this.tinymceConfig.getAttribute("data-webapp-url"))); + // Regular expression to check íf a link could be a page + // Capture groups: Path, Region slug, language slug, page infix, page slug + internalPageURLRegex: () => RegExp = evaluateOnceDecorator(() => new RegExp(String.raw` + ^[^:/]*:// + ${this.domainAndPrefix().replace(/\/$/, "")} + ( + / + ([^/]+) + / + ([^/]{2,8}) + / + ( + ([^?#]+) + / + )? + ([^/?#]+) + ) + `.replace(/\s+/g, ""))); + + //contactCache: ContactCache = null; + + predicate(node: Element): boolean { + // We also consider old contact cards as instances of the contact shortcode. + // This way shortcodes will work on the old contact cards and we will naturally slowly convert content from the old style directly embedded HTML. + if ("contactId" in (node as HTMLElement).dataset) { + return true; + } + return super.predicate(node); + } + + setup(editor: Editor): boolean { + super.setup(editor); + + this.addText = this.tinymceConfig.getAttribute("data-contact-menu-text"); + this.editText = this.tinymceConfig.getAttribute("data-contact-change-text"); + this.removeText = this.tinymceConfig.getAttribute("data-contact-remove-text"); + + const isContactsEnabled = this.tinymceConfig.getAttribute("data-contact-module-activated") !== "False"; + if (!isContactsEnabled) { + return false; + } + return true; + } +} + + +export default ContactHandle; diff --git a/integreat_cms/static/src/js/tinymce-plugins/shortcodes/page.ts b/integreat_cms/static/src/js/tinymce-plugins/shortcodes/page.ts new file mode 100644 index 0000000000..6b35c2ade7 --- /dev/null +++ b/integreat_cms/static/src/js/tinymce-plugins/shortcodes/page.ts @@ -0,0 +1,510 @@ +import { ShortcodeHandle, AcceptArbitraryArguments, PargsDescriptor, KWargsDescriptor } from "./utils"; +import type { ToolbarButtonInstanceApi, ContextFormInstanceApi, ContextFormButtonInstanceApi, ContextFormToggleButtonInstanceApi, DialogInstanceApi, DialogData, MenuItemInstanceApi, DialogSpec, BodyComponentSpec } from "../tinymce.d.ts"; +import { Editor } from "tinymce"; +import { getCsrfToken } from "../../utils/csrf-token"; +import { stripProtocol } from "../../utils/url-tools"; +import { evaluateOnceDecorator } from "../../utils/caching-functions"; + +import { debounce } from "../../utils/debounce"; + + +class PageHandle extends ShortcodeHandle { + keyword = "page"; + + pargs: PargsDescriptor = [ + [["id", "The ID of the page to link to"]], + [["text", "The text to display (if not specified, show page title)"]], + ]; + kwargs: KWargsDescriptor = [ + // TODO: implement lang kwarg + // [["lang", false, "The language to which to link (instead of preferring the language of the document containing this link)", "Language"]], + ]; + + addText = "Add Page Link" + editText = "Edit Page Link" + removeText = "Remove Page Link" + + domainAndPrefix: () => string = evaluateOnceDecorator(() => stripProtocol(this.tinymceConfig.getAttribute("data-webapp-url"))); + // Regular expression to check íf a link could be a page + // Capture groups: Path, Region slug, language slug, page infix, page slug + internalPageURLRegex: () => RegExp = evaluateOnceDecorator(() => new RegExp(String.raw` + ^[^:/]*:// + ${this.domainAndPrefix().replace(/\/$/, "")} + ( + / + ([^/]+) + / + ([^/]{2,8}) + / + ( + ([^?#]+) + / + )? + ([^/?#]+) + ) + `.replace(/\s+/g, ""))); + + pageCache: PageCache = null; + + predicate(node: Element): boolean { + // We also consider old links that look like they point to pages as instances of the page shortcode. + // This way shortcodes will work on the old links and we will naturally slowly convert content from the old style direct links. + if (node.nodeName.toLowerCase() === "a") { + const href = (node as HTMLLinkElement).href; + if (href && (node as HTMLElement).isContentEditable && this.internalPageURLRegex().exec(href)) { + return true; + } + } + return super.predicate(node); + } + + argsFromNode(node: HTMLElement | null): [string[], Map] { + // If we are operating on an old style direct link, we need to recover what that would be as the arguments for the new shortcode. + if (node && node.nodeName.toLowerCase() === "a") { + const [fullURL, path, regionSlug, languageSlug, infix, pageSlug] = node.getAttribute("href").match(this.internalPageURLRegex()); + let text = node.textContent !== node.getAttribute("href") ? node.textContent : ""; + // Get page id for slug + const translation = this.pageCache.byPath.get(path); + const id = translation.page.id; + return [[`${id}`, text], new Map()]; + } + return super.argsFromNode(node); + } + + renderPreviewNode(pargs: string[], kwargs: Map): string { + // The html string representation of the shortcode in the TinyMCE editor + // By default this is a span marked with mceNonEditable and the shortcode keyword and parameters + // and defers the visual presented to the user to be rendered to renderPreview() + + // Ensure the data for this is loaded in the cache, + // including ancestors, so the edit dialog can render the title path + this.pageCache.requestId(parseInt(pargs[0]), true).then(); + return super.renderPreviewNode(pargs, kwargs) + } + + renderPreview(pargs: string[], kwargs: Map): string { + // The html string representation of the shortcode preview in the TinyMCE editor + // By default this is just the canonical text representation. This function will be overwritten by most subclasses. + const id = parseInt(pargs[0]); + const languageSlug = this.tinymceConfig.getAttribute("data-language-slug"); + const page = this.pageCache.byId.get(id); + // TODO: If page not in cache, re-render after request resolved + const translation = page?.translations.get(languageSlug); + const text = pargs[1] || translation?.title; + + const TEXT_MISSING = "MISSING LINK"; // TODO: translations (#4044) + let element; + if (!translation) { + element = document.createElement("i"); + element.classList.add("error"); + element.innerText = `[${text || TEXT_MISSING}]`; + } else { + element = document.createElement("a"); + element.innerText = text; + // No href, the link is non-interactible anyway + element.href = "#" + } + return element.outerHTML; + } + + async getCompletions(query: string, id: number) { + const url = this.tinymceConfig.getAttribute("data-link-ajax-url"); + const response = await fetch(url, { + method: "POST", + headers: { + "X-CSRFToken": getCsrfToken(), + }, + body: JSON.stringify({ + query_string: query, + object_types: ["event", "page", "poi"], + archived: false, + is_link_suggestion: true, + }), + }); + const HTTP_STATUS_OK = 200; + if (response.status !== HTTP_STATUS_OK) { + return []; + } + + const data = await response.json(); + return [data.data, id]; + } + + displayEditDialog(initialPargs: string[], initialKWargs: [string, string][]) { + const ID_ARG = "parg0"; + const TEXT_ARG = "parg1"; + + const node = this.getNode(); + const initialText = node ? initialPargs[1] : this.editor.selection.getContent({ format: "text" }); + + let prevSearchText = ""; + let prevSelectedCompletion = initialPargs[0] ? initialPargs[0] : ""; + + // Stores the current request id, so that outdated requests get ignored + let ajaxRequestId = 0; + const defaultCompletionItem = { + text: this.tinymceConfig.getAttribute("data-link-no-results-text"), + title: "", + value: "", + }; + const languageSlug = this.tinymceConfig.getAttribute("data-language-slug"); + const cachedPageData = this.pageCache.byId.get(parseInt(initialPargs[0]))?.translations.get(languageSlug); + const initialCompletionItem = { + text: cachedPageData?.titlePath || "", + title: cachedPageData?.title || "", + value: `${initialPargs[0]}`, + }; + const completionItems = cachedPageData ? [initialCompletionItem] : [defaultCompletionItem]; + let currentCompletionText = ""; + + const that = this; + const updateDialog = (api: DialogInstanceApi) => { + super.defaultOnChange(api); + + let data = api.getData(); + + let urlChangedBySearch = false; + // Check if the selected completion changed + if (prevSelectedCompletion !== data[ID_ARG]) { + // find the correct text currently shown in the completion items box + if (completionItems.length > 0) { + const currentCompletion = completionItems.find( + (completion) => completion.value === data[ID_ARG] + ); + // Don't set the completion text to `- no results -` + if (currentCompletion && currentCompletion.value !== "") { + currentCompletionText = currentCompletion.title; + } else { + currentCompletionText = ""; + } + } else { + currentCompletionText = ""; + } + } + prevSelectedCompletion = data[ID_ARG]; + + // Disable the submit button if no valid page found + api.setEnabled("submit", data[ID_ARG]); + + // make new ajax request on user input + if (data.search !== prevSearchText && data.search !== "") { + ajaxRequestId += 1; + this.getCompletions(data.search, ajaxRequestId).then(([newCompletions, requestId]) => { + if (requestId !== ajaxRequestId) { + return; + } + + completionItems.length = 0; + for (const completion of newCompletions) { + const [fullURL, path, regionSlug, languageSlug, infix, pageSlug] = completion.url.match(that.internalPageURLRegex()); + completionItems.push({ + text: completion.path, + title: completion.html_title, + value: `${completion.foreign_object_id}`, + //value: `${that.pageCache.bySlug.get(languageSlug).get(path).page.id}`, + }); + } + + let completionDisabled = false; + if (completionItems.length === 0) { + completionDisabled = true; + completionItems.push(defaultCompletionItem); + } + + + // It seems like there is no better way to update the completion list + /* eslint-disable-next-line @typescript-eslint/no-use-before-define */ + api.redial(dialogConfig); + api.setData(data); + api.focus("search"); + prevSearchText = data.search; + + api.setEnabled(ID_ARG, !completionDisabled); + + updateDialog(api); + }); + } else if (data.search === "" && prevSearchText !== "") { + // force an update so that the original user url can get restored + completionItems.length = 0; + completionItems.push(defaultCompletionItem); + /* eslint-disable-next-line @typescript-eslint/no-use-before-define */ + api.redial(dialogConfig); + api.setData(data); + api.focus("search"); + prevSearchText = data.search; + //api.disable(ID_ARG); + updateDialog(api); + } + }; + + const completion: any = {}; + completion[ID_ARG] = prevSelectedCompletion + const dialogConfig: DialogSpec = { + title: this.text(this.editText), + body: { + type: "panel", + items: [ + { + type: "input", + name: TEXT_ARG, + label: this.tinymceConfig.getAttribute("data-link-dialog-text-text"), + //disabled: textDisabled, + }, + { + type: "label", + label: this.tinymceConfig.getAttribute("data-link-dialog-internal_link-text"), + items: [ + { + type: "input", + name: "search", + }, + { + type: "selectbox", + name: ID_ARG, + items: completionItems, + //disabled: true, + }, + ], + }, + ], + }, + buttons: [ + { + type: "cancel", + text: this.tinymceConfig.getAttribute("data-dialog-cancel-text"), + }, + { + type: "submit", + name: "submit", + text: this.tinymceConfig.getAttribute("data-dialog-submit-text"), + primary: true, + enabled: false, + }, + ], + initialData: { + ...this.defaultInitialData(initialPargs, initialKWargs), + ...completion, + }, + onSubmit: this.defaultOnSubmit.bind(this), + onChange: updateDialog.bind(this), + }; + + return this.editor.windowManager.open(dialogConfig); + } + + populatePageCache() { + const baseUrl = this.tinymceConfig.getAttribute("data-base-url"); + const regionSlug = this.tinymceConfig.getAttribute("data-region-slug"); + const languageSlug = this.tinymceConfig.getAttribute("data-language-slug"); + const url = `${baseUrl}/api/v3/${regionSlug}/${languageSlug}/pages/`; + + fetch(url, { + method: "GET", + headers: { + "X-CSRFToken": getCsrfToken(), + }, + }).then((response): any => { + const HTTP_STATUS_OK = 200; + if (response.status !== HTTP_STATUS_OK) { + return []; + } + return response.json(); + }).then(async (data) => { + for (let mainTranslation of data) { + await this.pageCache.requestId(mainTranslation.page_id); + }; + }); + } + + setup(editor: Editor) { + super.setup(editor); + + this.addText = this.tinymceConfig.getAttribute("data-link-dialog-title-text"); + this.editText = this.tinymceConfig.getAttribute("data-link-dialog-title-text"); + this.removeText = this.tinymceConfig.getAttribute("data-link-remove-text"); + + const baseUrl = this.tinymceConfig.getAttribute("data-base-url"); + const regionSlug = this.tinymceConfig.getAttribute("data-region-slug"); + const languageSlug = this.tinymceConfig.getAttribute("data-language-slug"); + + const debouncedRefreshPreview = debounce((pageMetadata: PageMetadata) => { + this.refreshPreview((pargs, kwargs) => parseInt(pargs[0]) == pageMetadata.id); + }, 100); + this.pageCache = new PageCache(baseUrl, regionSlug, languageSlug, debouncedRefreshPreview); + + //this.populatePageCache(); + return true; + } +} + + +class PageCache { + byId = new Map(); + byPath = new Map(); + + pending = new Map>(); + + afterFetch: ((metadata: PageMetadata) => void) | null = null; + + baseUrl: string; + regionSlug: string; + defaultLanguageSlug: string; + + _pathRegex = new RegExp(String.raw` + / + ([^/]+) + / + ([^/]{2,8}) + / + ( + ([^?#]+) + / + )? + ([^/?#]+) + `.replace(/\s+/g, "")); + + constructor(baseUrl: string, regionSlug: string, defaultLanguageSlug: string, afterFetch: ((metadata: PageMetadata) => void) | null = null) { + this.baseUrl = baseUrl; + this.regionSlug = regionSlug; + this.defaultLanguageSlug = defaultLanguageSlug; + this.afterFetch = afterFetch; + } + + cacheTranslationMetadata(translation: any) { + const [fullURL, regionSlug, languageSlug, infix, _, slug] = translation.path.match(this._pathRegex); + console.assert(translation.page_id, `translation without page id:`, translation); + + const pageMetadata = this.byId.get(translation.page_id) || new PageMetadata({ + id: translation.page_id, + parentId: translation.parent?.id, + regionSlug: regionSlug, + }, this); + if (!this.byId.has(translation.page_id)) { + this.byId.set(translation.page_id, pageMetadata); + } + + const translationMetadata = pageMetadata.translations.get(languageSlug) || new TranslationMetadata({ + id: translation.id, + languageSlug: languageSlug, + title: translation.title, + slug: slug, + path: translation.path, + page: pageMetadata, + }, this); + pageMetadata.translations.set(languageSlug, translationMetadata); + + this.byPath.set(translation.path, translationMetadata); + + if (this.afterFetch) this.afterFetch(pageMetadata); + return pageMetadata; + } + + async _getPage(id: number, languageSlug: string = undefined) { + languageSlug = languageSlug || this.defaultLanguageSlug; + + const url = `${this.baseUrl}/api/v3/${this.regionSlug}/${languageSlug}/page/?id=${id}`; + + const response = await fetch(url, { + method: "GET", + headers: { + "X-CSRFToken": getCsrfToken(), + }, + }) + const translation = response.status === 200 ? await response.json() : null; + return { + id: id, + languageSlug: languageSlug, + translation: translation, + }; + } + + requestId(id: number, ancestors: boolean = false): Promise { + const that = this; + async function inner(id: number) { + const {languageSlug, translation} = await that._getPage(id); + if (!translation) return null; + const pageMetadata = that.cacheTranslationMetadata(translation); + + for (const [lang, tr] of Object.entries(translation.available_languages)) { + const {translation} = await that._getPage(id, lang); + if (translation) { + that.cacheTranslationMetadata(translation); + } + } + if (ancestors && pageMetadata.parentId) { + await that.requestId(pageMetadata.parentId, ancestors); + } + return pageMetadata; + } + + if (this.byId.has(id)) { + // Return a Promise that immediately resolves + return new Promise((res, rej) => res(this.byId.get(id))); + } else if (!this.pending.has(id)) { + // Start a new query + this.pending.set(id, inner(id)); + } + // Return the Promise of the ongoing query + return this.pending.get(id); + } +} +class PageMetadata { + _cache: PageCache; + + id: number; + parentId: number; + regionSlug: string; + translations = new Map(); + + get parent() { + return this._cache?.byId.get(this.parentId); + } + + constructor(data: {id: number, parentId: number, regionSlug: string}, cache: PageCache = null) { + this._cache = cache; + this.id = data.id; + this.parentId = data.parentId; + this.regionSlug = data.regionSlug; + } +} + +class TranslationMetadata { + _cache: PageCache; + + id: number; + languageSlug: string; + title: string; + slug: string; + path: string; + page: PageMetadata; + + get parent() { + return this.page.parent?.translations.get(this.languageSlug); + } + get titlePath() { + const reverseTitles = [this.title]; + let translation: TranslationMetadata = this; + while (translation.page.parentId) { + translation = translation.parent; + if (translation) { + reverseTitles.push(translation.title); + } else { + reverseTitles.push("[?]"); + break; + } + } + return reverseTitles.reverse().join(" → "); + } + + constructor(data: {id: number, languageSlug: string, title: string, slug: string, path: string, page: PageMetadata}, cache: PageCache = null) { + this._cache = cache; + this.id = data.id; + this.languageSlug = data.languageSlug; + this.title = data.title; + this.slug = data.slug; + this.path = data.path; + this.page = data.page; + } +} + +export default PageHandle; diff --git a/integreat_cms/static/src/js/tinymce-plugins/shortcodes/plugin.js b/integreat_cms/static/src/js/tinymce-plugins/shortcodes/plugin.js new file mode 100644 index 0000000000..d5698fcdce --- /dev/null +++ b/integreat_cms/static/src/js/tinymce-plugins/shortcodes/plugin.js @@ -0,0 +1,72 @@ +import { Parser } from "./shortcodes"; +import ContactHandle from "./contact"; +import PageHandle from "./page"; +import { Registry, ShortcodeHandle } from "./utils"; + +Registry.register(new PageHandle()); +Registry.register(new ContactHandle()); + +function DummyHandleFactory(keyword) { + const handle = new ShortcodeHandle(); + handle.keyword = keyword; + return handle; +} +Registry.setUnknownHandleFactory(DummyHandleFactory); + + +(() => { + const tinymceConfig = document.getElementById("tinymce-config-options"); + const parser = new Parser("[", "]", "\\", true, true); + const context = { + language: tinymceConfig.getAttribute("data-language"), + directionality: tinymceConfig.getAttribute("data-directionality"), + }; + + tinymce.PluginManager.add("shortcodes", editor => { + /* + function insertShortcode() { + let html = `[shortcode 2]`; + editor.insertContent(html); + } + */ + + editor.on('BeforeSetContent', function(e) { + /* + // Ensure all shortcodes are represented by a marker node in tinyMCE + e.content = e.content.replace(/(\[shortcode (\d+)\]<\/span>|\[shortcode (\d+)\])/g, (match, _, a, b) => { + return `[shortcode ${a || b}]`; + }); + */ + console.log("Parsing registered handles:", Registry.instance.handles); + try { + e.content = parser.parse(e.content, context); + } catch (e) { + console.error("Failed to expand shortcodes:", e); + } + }); + + editor.on('PreProcess', function(e) { + // Strip the mce marker out when extracting the content for saving or the source code view + console.log(`PreProcess – restoring canonical form`, e); + const shortcodes = Array.from(e.node.querySelectorAll('span.mceNonEditable[data-shortcode]')); + shortcodes.forEach(node => { + const keyword = node.dataset.shortcode; + const handle = Registry.get(keyword); + node.outerText = handle.renderShortcode(...handle.argsFromNode(node)); + }); + }); + + /* + editor.ui.registry.addMenuItem("add_shortcode", { + text: "Shortcode", + icon: "link", + //shortcut: "Meta+L", + onAction: insertShortcode, + }); + */ + + Registry.setupAll(editor, parser); + + return {}; + }); +})(); diff --git a/integreat_cms/static/src/js/tinymce-plugins/shortcodes/shortcodes.ts b/integreat_cms/static/src/js/tinymce-plugins/shortcodes/shortcodes.ts new file mode 100644 index 0000000000..43f697ca82 --- /dev/null +++ b/integreat_cms/static/src/js/tinymce-plugins/shortcodes/shortcodes.ts @@ -0,0 +1,411 @@ +/******************************************* + * JS version of pythons shortcode package * + * which is licensed under MIT. * + * This ensures that editor and backend * + * behave exactly the same. * + *******************************************/ + + +// Globally-registered handler functions indexed by keyword. +const global_keywords = new Map, context: any, content?: string) => string, string]>(); + + +// The set of all end-words for globally-registered block-scoped shortcodes. +const global_endwords = new Set(); + + +// Decorator function for globally registering shortcode handlers. +function register(keyword: string, endword: string) { + + function register_function(func: (pargs: string[], kwargs: Map, context: any, content?: string) => string) { + global_keywords.set(keyword, [func, endword]); + if (endword) { + global_endwords.add(endword); + } + return func; + } + + return register_function; +} + + +/*********************** + * Exception Classes * + ***********************/ + + +// Base class for all exceptions raised by the library. +class ShortcodeError extends Error { + constructor(message: string) { + super(message); + this.name = "ShortcodeError"; + } +} + + +// Raised if the parser detects invalid shortcode syntax. +class ShortcodeSyntaxError extends ShortcodeError { + constructor(message: string) { + super(message); + this.name = "ShortcodeSyntaxError"; + } +} + + +// Raised if a handler function throws an error. +class ShortcodeRenderingError extends ShortcodeError { + constructor(message: string) { + super(message); + this.name = "ShortcodeRenderingError"; + } +} + + +/*************** + * AST Nodes * + ***************/ + + +// Input text is parsed into a tree of ASTNode instances. +class ASTNode { + children: ASTNode[] = []; + token: Token; + + constructor() { + } + + render(context: any): string { + return this.children.map(c => c.render(context)).join(""); + } +} + + +// Represents ordinary text not enclosed in tag delimiters. +class Text extends ASTNode { + text: string; + + constructor(text: string) { + super(); + this.text = text; + } + + render(context: any): string { + return this.text; + } +} + + +// Base class for atomic and block-scoped shortcodes. +class Shortcode extends ASTNode { + // Regex for parsing the shortcode's arguments. + re_args = new RegExp(String.raw` + (?:([^\s'"=]+)=)? + ( + "((?:[^\\"]|\\.)*)" + | + '((?:[^\\']|\\.)*)' + ) + | + ([^\s'"=]+)=(\S+) + | + (\S+) + `.replace(/\s+/g, ""), "g"); + + handler: (pargs: string[], kwargs: Map, context: any, content?: string) => string; + pargs: string[]; + kwargs: Map; + children: ASTNode[]; + + constructor(token: Token, handler_function: (pargs: string[], kwargs: Map, context: any, content?: string) => string) { + super(); + this.token = token; + this.handler = handler_function; + [this.pargs, this.kwargs] = this.parse_args(token.text.slice(token.keyword.length)); + this.children = []; + } + + parse_args(argstring: string): [string[], Map] { + const pargs: string[] = []; + const kwargs = new Map(); + for (const match of argstring.matchAll(this.re_args)) { + if (match[2] || match[5]) { + const key = match[1] || match[5]; + const value = match[3] || match[4] || match[6]; + if (key) { + kwargs.set(key, value); + } else { + pargs.push(value); + } + } else { + pargs.push(match[7]); + } + } + console.log(`parsed from argstring: »${argstring}«`, pargs, kwargs); + return [pargs, kwargs]; + } +} + + +// An atomic shortcode is a shortcode with no closing tag. +class AtomicShortcode extends Shortcode { + /* If the shortcode handler raises an exception we intercept it and wrap it + * in a ShortcodeRenderingError. + */ + render(context: any) { + try { + return this.handler(this.pargs, this.kwargs, context).toString(); + } catch (ex: unknown) { + const msg = `An exception was raised while rendering the '${this.token.keyword}' shortcode in line ${this.token.line_number}.`; + const error = new ShortcodeRenderingError(msg); + if (ex instanceof Error) { + error.stack = ex.stack; + } + throw error; + } + } +} + + +// A block-scoped shortcode is a shortcode with a closing tag. +class BlockShortcode extends Shortcode { + /* If the shortcode handler raises an exception we intercept it and wrap it + * in a ShortcodeRenderingError. The original exception will still be + * available via the exception's __cause__ attribute. + */ + render(context: any) { + const content = this.children.map(c => c.render(context)).join(""); + try { + return this.handler(this.pargs, this.kwargs, context, content).toString(); + } catch (ex: unknown) { + const msg = `An exception was raised while rendering the '${this.token.keyword}' shortcode in line ${this.token.line_number}.` + const error = new ShortcodeRenderingError(msg); + if (ex instanceof Error) { + error.stack = ex.stack; + } + throw error; + } + } +} + + +/************ + * Parser * + ************/ + + +/* A Parser instance parses input text and renders shortcodes. A single Parser + * instance can parse an unlimited number of input strings. Note that the parse() + * method accepts an optional arbitrary context object which it passes on to each + * shortcode's handler function. + * + * If the `inherit_globals` parameter is true, the parser will inherit a copy of + * the set of globally-registered shortcodes at the moment of instantiation. + * + * If `ignore_unknown` is true, unknown shortcodes are ignored. If this parameter + * is false (the default), unknown shortcodes cause an error. + */ +class Parser { + start: string; + end: string; + esc_start: string; + keywords: Map, context: any, content?: string) => string, string]>; + endwords: Set; + ignore_unknown: boolean; + unknownHandlerFactory: (keyword: string) => (pargs: string[], kwargs: Map, context: any, content?: string) => string; // patched in + + constructor(start: string = '[%', end: string = '%]', esc: string = '\\', inherit_globals: boolean = true, ignore_unknown: boolean = false) { + this.start = start; + this.end = end; + this.esc_start = esc + start; + this.keywords = new Map, context: any, content?: string) => string, string]>(inherit_globals ? global_keywords : null); + this.endwords = new Set(inherit_globals ? global_endwords : null); + this.ignore_unknown = ignore_unknown; + this.unknownHandlerFactory = null; // patched in + } + + register(func: (pargs: string[], kwargs: Map, context: any, content?: string) => string, keyword: string, endword: string = null) { + this.keywords.set(keyword, [func, endword]); + if (endword) { + this.endwords.add(endword); + } + } + + // patched in + setUnknownHandlerFactory(func: (keyword: string) => (pargs: string[], kwargs: Map, context: any, content?: string) => string) { + this.unknownHandlerFactory = func; + } + + parse(text: string, context: any = null) { + if (!text.includes(this.start)) { + return text; + } + + const stack = [new ASTNode()]; + const expecting = []; + + const lexer = new Lexer(text, this.start, this.end, this.esc_start); + for (const token of lexer.tokenize()) { + if (token.type == "TEXT") { + stack[stack.length-1].children.push(new Text(token.text)); + } else if (this.keywords.has(token.keyword)) { + const [handler, endword] = this.keywords.get(token.keyword); + if (endword) { + const node = new BlockShortcode(token, handler); + stack[stack.length-1].children.push(node); + stack.push(node); + expecting.push(endword); + } else { + const node = new AtomicShortcode(token, handler); + stack[stack.length-1].children.push(node); + } + } else if (this.endwords.has(token.keyword)) { + if (expecting.length == 0) { + const msg = `Unexpected '${token.keyword}' tag in line ${token.line_number}.`; + throw new ShortcodeSyntaxError(msg); + } else if (token.keyword == expecting[expecting.length-1]) { + stack.pop(); + expecting.pop(); + } else { + const msg = `Unexpected '${token.keyword}' tag in line ${token.line_number}. The shortcode parser was expecting a closing '${expecting[-1]}' tag.`; + throw new ShortcodeSyntaxError(msg); + } + } else if (token.keyword == '') { + const msg = `Empty shortcode tag in line ${token.line_number}.`; + throw new ShortcodeSyntaxError(msg); + } else if (this.ignore_unknown) { + if (this.unknownHandlerFactory !== null) { // START patched in + // Instead of treating the unknown shortcode as text, parse it as a dummy one + const node = new AtomicShortcode(token, this.unknownHandlerFactory(token.keyword)); + stack[stack.length-1].children.push(node); + continue; + } // END patched in + stack[stack.length-1].children.push(new Text(token.raw_text)); + } else { + const msg = `Unrecognised shortcode tag '${token.keyword}' in line ${token.line_number}.` + throw new ShortcodeSyntaxError(msg); + } + } + + if (expecting.length) { + const token = stack[stack.length-1].token; + const msg = `Unexpected end of document. The shortcode parser was expecting a closing '${expecting[-1]}' tag to close the '${token.keyword}' tag opened in line ${token.line_number}.`; + throw new ShortcodeSyntaxError(msg); + } + + return stack.pop().render(context); + } +} + + +/*********** + * Lexer * + ***********/ + + +class Token { + keyword: string; + type: string; + text: string; + raw_text: string; + line_number: number; + + constructor(token_type: string, token_text: string, raw_text: string, line_number: number) { + const words = token_text.split(/\s+/); + this.keyword = words ? words[0] : ''; + this.type = token_type; + this.text = token_text; + this.raw_text = raw_text; + this.line_number = line_number; + } + + toString(): string { + return `(${this.type}, ${this.text.toString()}, ${this.line_number})`; + } +} + + +class Lexer { + text: string; + start: string; + end: string; + esc_start: string; + tokens: Token[]; + index: number; + line_number: number; + + constructor(text: string, start: string, end: string, esc_start: string) { + this.text = text; + this.start = start; + this.end = end; + this.esc_start = esc_start; + this.tokens = []; + this.index = 0; + this.line_number = 1; + } + + match(target: string): boolean { + if (this.text.startsWith(target, this.index)) { + return true; + } + return false; + } + + advance() { + if (this.text[this.index] == '\n') { + this.line_number += 1; + } + this.index += 1; + } + + tokenize(): Token[] { + while (this.index < this.text.length) { + if (this.match(this.esc_start)) { + this.read_escaped_tag_delimiter(); + } else if (this.match(this.start)) { + this.read_tag(); + } else { + this.read_text(); + } + } + return this.tokens; + } + + read_escaped_tag_delimiter() { + this.index += this.esc_start.length; + this.tokens.push(new Token("TEXT", this.start, this.esc_start, this.line_number)); + } + + read_tag() { + this.index += this.start.length; + const start_index = this.index; + const start_line_number = this.line_number; + while (this.index < this.text.length) { + if (this.match(this.end)) { + const text = this.text.slice(start_index, this.index).trim(); + const raw_text = this.text.slice(start_index-this.start.length, this.index+this.end.length); + this.tokens.push(new Token("TAG", text, raw_text, start_line_number)); + this.index += this.end.length; + return; + } + this.advance(); + } + const msg = `Unclosed shortcode tag. The tag was opened in line ${start_line_number}.`; + throw new ShortcodeSyntaxError(msg); + } + + read_text() { + const start_index = this.index; + const start_line_number = this.line_number; + while (this.index < this.text.length) { + if (this.match(this.esc_start) || this.match(this.start)) { + break; + } + this.advance(); + } + const text = this.text.slice(start_index, this.index); + this.tokens.push(new Token("TEXT", text, text, start_line_number)); + } +} + + +export { register, ShortcodeError, ShortcodeSyntaxError, ShortcodeRenderingError, ASTNode, Text, Shortcode, AtomicShortcode, BlockShortcode, Parser, Token, Lexer }; diff --git a/integreat_cms/static/src/js/tinymce-plugins/shortcodes/utils.ts b/integreat_cms/static/src/js/tinymce-plugins/shortcodes/utils.ts new file mode 100644 index 0000000000..92bef89cf1 --- /dev/null +++ b/integreat_cms/static/src/js/tinymce-plugins/shortcodes/utils.ts @@ -0,0 +1,870 @@ +/// +import type { ToolbarButtonInstanceApi, ContextFormInstanceApi, ContextFormButtonInstanceApi, ContextFormToggleButtonInstanceApi, DialogInstanceApi, DialogData, MenuItemInstanceApi, DialogSpec, BodyComponentSpec } from "../tinymce.d.ts"; +import { Editor } from "tinymce"; + +import type { Parser } from "./shortcodes"; +import { Shortcode as parser } from "./shortcodes"; + +/* + +- enable easily implementing behaviour to edit/manage different kinds of shortcodes +- register handle() function to parse shortcode arguments into tinymce readable marker +- ??? provide function to get canocical shortcode ??? (to be used when turning markers back into just the shortcode) +- provide easily overrideable methods for UI handling + +- one ShortcodeHandle object per shortcode type, not per shortcode in content + +*/ + + +const AcceptArbitraryArguments = Symbol("AcceptArbitraryArguments"); +type ARBITRARY = typeof AcceptArbitraryArguments; + +type TextDescriptor = string | ((self: ShortcodeHandle) => string); + +type SingleParg = null // Just to occupy this index. + | string // Name for the positional argument. + | [string, string?]; // Name and description of the positional argument. +type PargsDescriptor = ARBITRARY // Allow any number of positional arguments. + | number // How many positional arguments have to be given (exactly, not more and not less). + | [number, number | ARBITRARY] // How many positional arguments are required, and up to how many CAN be given (Infinity or null means unbounded). + | [SingleParg[], SingleParg[] | [...SingleParg[], ARBITRARY] | ARBITRARY]; // The list of the required and the list of optional positional arguments. +type SingleKWarg = string // Keyword (required) + | [string, boolean?, string?, string?]; // Keyword, whether it is required (default: not required), a description of the argument and a human readable name to use instead of the default conversion of the keyword. +type KWargsDescriptor = ARBITRARY // Allow any keyword argument. + | SingleKWarg[] // List of all keyword arguments being accepted. Also serves as a canonical order normalizing the shortcode. + | [...SingleKWarg[], ARBITRARY]; // If the last element is AcceptArbitraryArguments, anything is accepted as optional argument. +type ExplicitArg = { + name: string; + specifiedName?: string; + genericName?: string; + required: boolean; + description: string; + unifiedDescription: string; +}; + + +class ShortcodeHandle { + readonly keyword: string; + readonly endword: string | null = null; + editor: Editor; + tinymceConfig: HTMLElement; + addText: TextDescriptor = (self: ShortcodeHandle) => `Insert ${self.keyword}`; + addIcon: string = "link"; + editText: TextDescriptor = (self: ShortcodeHandle) => `Edit ${self.keyword}`; + editIcon: string = "link"; + removeText: TextDescriptor = (self: ShortcodeHandle) => `Remove ${self.keyword}`; + removeIcon: string = "unlink"; + + static escape(str: string | undefined): string { + if (!str) return '""'; + return str.includes(" ") ? `"${str.replace(/"/g, '\\"')}"` : str; + } + + readonly pargs: PargsDescriptor = AcceptArbitraryArguments; + readonly kwargs: KWargsDescriptor = AcceptArbitraryArguments; + get maxPargs(): number { + if (this.pargs === AcceptArbitraryArguments) + return Infinity; + if (typeof this.pargs === "number") + return this.pargs; + else if (this.pargs.length > 0 && typeof this.pargs[0] === "number") { + // [min, max] style + if (this.pargs[1] === AcceptArbitraryArguments) + return Infinity; + else + return this.pargs[1] as number; + } else { + // Listing of required and list of optional positional arguments style + const required = this.pargs[0] as SingleParg[]; + const optional = this.pargs[1] as SingleParg[] | [...SingleParg[], ARBITRARY]; + if (optional[optional.length] === AcceptArbitraryArguments) + return Infinity; + else + return required.length + optional.length; + } + } + get minPargs(): number { + if (this.pargs === AcceptArbitraryArguments) + return 0; + if (typeof this.pargs === "number") + return this.pargs; + else if (this.pargs.length > 0 && typeof this.pargs[0] === "number") { + // [min, max] style + return this.pargs[0]; + } else { + // Listing of required and list of optional positional arguments style + return (this.pargs[0] as SingleParg[]).length; + } + } + getExplicitParg(index: number): ExplicitArg { + // Get an explicit descriptor of the positional argument at this index + if (index < 0 || index >= this.maxPargs) + throw RangeError; + const parg: ExplicitArg = { + name: null, + specifiedName: null, + genericName: `Argument ${index}`, + required: null, + description: null, + unifiedDescription: null, + }; + if (this.pargs === AcceptArbitraryArguments) + parg.required = false; + else if (typeof this.pargs === "number") + parg.required = true; + else if (this.pargs.length > 0 && typeof this.pargs[0] === "number") { + let [min, max] = this.pargs; + parg.required = index < min; + } else { + // Listing of required and list of optional positional arguments style + const required = this.pargs[0] as SingleParg[]; + const optional = this.pargs[1] === AcceptArbitraryArguments ? [] : this.pargs[1] as SingleParg[] | [...SingleParg[], ARBITRARY]; + // Create a joined list that only contains argument descriptions, not the Symbol + const pargs = required.concat(( + optional[optional.length-1] === AcceptArbitraryArguments ? + optional.slice(0, optional.length-1) + : optional + ) as SingleParg[]); + let length = pargs.length; + if (index < length) { + if (typeof pargs[index] === "string") + parg.specifiedName = pargs[index]; + else if (pargs[index] !== null) { + const arg = pargs[index] as [string, string?]; + parg.specifiedName = arg[0]; + if (arg.length > 1) + parg.description = arg[1]; + } + } else + parg.required = false; + } + // Fill in missing details + parg.name = parg.specifiedName !== null ? parg.specifiedName : parg.genericName; + if (parg.required === null) + parg.required = index < this.minPargs; + if (parg.description === null) + parg.description = ``; // Description stays empty + if (parg.specifiedName !== null) { + parg.unifiedDescription = parg.specifiedName; + if (parg.description) + parg.unifiedDescription += ` – ${parg.description}`; + } else + parg.unifiedDescription = parg.description || parg.genericName; + return parg; + } + + get requiredKWargs(): Set { + if (this.kwargs === AcceptArbitraryArguments) return new Set(); + const known = this.kwargs.filter(kw => kw !== AcceptArbitraryArguments) as SingleKWarg[]; + const required = known.reduce((acc: string[], key: SingleKWarg) => { + if (typeof key === "string") { + acc.push(key); + } else if (key.length > 1 && key[1]) { + acc.push(key[0]); + } + return acc; + }, []); + return new Set(required); + } + get optionalKWargs(): Set { + if (this.kwargs === AcceptArbitraryArguments) return new Set(); + const known = this.kwargs.filter(kw => kw !== AcceptArbitraryArguments) as SingleKWarg[]; + const optional = known.reduce((acc, key: SingleKWarg) => { + if (!(typeof key === "string") && !(key.length > 1 && key[1])) { + acc.push(key[0]); + } + return acc; + }, []); + return new Set(optional); + } + get acceptingArbitraryKWargs(): boolean { + if (this.kwargs === AcceptArbitraryArguments) + return true; + return (this.kwargs as [...SingleKWarg[], ARBITRARY]) // Wrong type, it might also not contain AcceptArbitraryArguments, but this way typescript doesn't complain + .includes(AcceptArbitraryArguments); // This would make perfect sense to me even if kwargs is regarded as SingleKWarg[] | [...SingleKWarg[], ARBITRARY] + } + get kwargsOrder(): string[] { + if (this.kwargs === AcceptArbitraryArguments) + return []; + const kwargs = this.kwargs.filter(kw => kw !== AcceptArbitraryArguments) as SingleKWarg[] + const keywords = kwargs.map((kw: SingleKWarg) => typeof kw === "string" ? kw : kw[0]); + return keywords; + } + getExplicitKWarg(name: string): ExplicitArg { + // Get an explicit descriptor of the keyword argument at this index + /*if (!this.acceptingArbitraryKWargs && !this.requiredKWargs.has(name) && !this.optionalKWargs.has(name)) + throw RangeError;*/ + + const kwarg: ExplicitArg = { + name: null, + specifiedName: null, + genericName: `${name.slice(0,1).toUpperCase()}${name.slice(1).replace("-", " ")}`, + required: null, + description: null, + unifiedDescription: null, + }; + if (this.kwargs === AcceptArbitraryArguments) { + kwarg.required = false; + } else if (name) { + // Listing all individual arguments style + // Iterate over them until we find the argument we are looking for + for (const descriptor of this.kwargs) { + if (typeof descriptor === "string") { + if (descriptor == name) { + kwarg.required = true; + break; + } + } else if (typeof descriptor === "object" && "length" in descriptor && descriptor[0] == name) { + if (descriptor.length > 1) + kwarg.required = descriptor[1]; + if (descriptor.length > 2) + kwarg.description = descriptor[2]; + if (descriptor.length > 3) + kwarg.specifiedName = descriptor[3]; + break; + } + } + } + // Fill in missing details + kwarg.name = kwarg.specifiedName !== null ? kwarg.specifiedName : kwarg.genericName; + if (kwarg.required === null) + kwarg.required = this.requiredKWargs.has(name); + if (kwarg.description === null) + kwarg.description = ``; // Description stays empty + kwarg.unifiedDescription = kwarg.name; + if (kwarg.description) + kwarg.unifiedDescription += ` – ${kwarg.description}`; + return kwarg; + } + + lastUnsavedPargs: string[] | null = null; + lastUnsavedKWargs: [string, string][] | null = null; + + text(text: TextDescriptor): string { + // A helper method to render a string from either a fixed value or a dynamic function + if (typeof text === "string") { + return text; + } + return text(this); + } + + predicate(node: Element): boolean { + // A method determining whether a node in TinyMCE represents this shortcode + // (e.g. whether the toolbar specific to this shortcode should be shown) + if (!("dataset" in node)) + return false; + const dataset = (node as HTMLElement).dataset; + return dataset.shortcode && (dataset.shortcode == this.keyword || !this.keyword); + } + + getNode(): HTMLElement | null { + // A helper method to get the shortcode node the user has currently selected + const node = this.editor.selection.getNode(); + return this.predicate(node) ? node : null; + }; + + sortKWargs(kwpairs: Iterable<[string, string]> | [string, string][]): [string, string][] { + // A helper method determining a canonical order for keyword arguments + const order = this.kwargsOrder; + if (!(kwpairs instanceof Array)) kwpairs = Array.from(kwpairs); + return (kwpairs as Array<[string, string]>).sort((a, b) => { + const aPos = order.includes(a[0]) ? order.indexOf(a[0]) : order.length; + const bPos = order.includes(b[0]) ? order.indexOf(b[0]) : order.length; + return aPos - bPos; + }); + } + + validate(pargs: string[], kwargs: {[key: string]: string}): boolean { + if (pargs.length < this.minPargs || pargs.length > this.maxPargs) + return false; + // Positional arguments pass! + + if (this.kwargs === AcceptArbitraryArguments) + return true; // Early exit if we don't define any required keyword arguments and accept everything + const keywords = new Set(Object.keys(kwargs)); + const requiredKWargs = this.requiredKWargs; + // Check if any required keyword arguments are missing + if (requiredKWargs.difference(keywords).size > 0) + return false; + // Check if there are any keyword arguments that are not allowed + if (!this.acceptingArbitraryKWargs && keywords.difference(requiredKWargs).difference(this.optionalKWargs).size > 0) + return false; + + // All arguments pass! + return true; + } + + argsFromNode(node: HTMLElement | null): [string[], Map] { + let prefix = "parg"; + const pargs = [...Object.entries(node !== null ? node.dataset : {})].reduce((acc, pair) => { + if (pair[0].startsWith(prefix)) { + const index = parseInt(pair[0].slice(prefix.length)); + acc[index] = pair[1]; + } + return acc; + }, []); + while (pargs.length < this.minPargs) { + pargs.push(""); + } + + prefix = "kw"; + const kwargs = [...Object.entries(node !== null ? node.dataset : {})].reduce((acc, pair) => { + if (pair[0].startsWith(prefix)) { + // Revert camelCase transformation automatically done by the dataset api + let keyword = pair[0].replace(/[A-Z]/g, c => `-${c.toLowerCase()}`); + // Strip the prefix + dash + keyword = keyword.slice(prefix.length + 1); + acc.push([keyword, pair[1]]); + } + return acc; + }, []); + + return [pargs, new Map(kwargs)]; + } + + truncateArgs(pargs: string[], kwargs: Map): [string[], Map] { + // Ensure the arguments fit the specification + let newPargs = [...pargs]; + const newKWargs = new Map(kwargs); + + // Ensure the correct number of positional arguments + if (newPargs.length > this.maxPargs) { + newPargs = newPargs.slice(0, this.maxPargs); + } + while (newPargs.length < this.minPargs) { + newPargs.push(""); + } + + // Ensure all required keywords exist + const requiredKWargs = this.requiredKWargs; + const optionalKWargs = this.optionalKWargs; + requiredKWargs.forEach(kwarg => { + if (!newKWargs.has(kwarg)) { + newKWargs.set(kwarg, ""); + } + }); + if (!this.acceptingArbitraryKWargs) { + // Ensure no unallowed keywords exist + kwargs.forEach((v, kwarg) => { + if (!(requiredKWargs.has(kwarg) || optionalKWargs.has(kwarg))) { + newKWargs.delete(kwarg); + } + }); + } + + return [newPargs, newKWargs]; + } + + renderShortcode(pargs: string[], kwargs: Map): string { + // The canonical text representation of the shortcode + const pairs = this.sortKWargs(kwargs.entries()).map(pair => pair.map(ShortcodeHandle.escape).join("=")); + const parts = [ShortcodeHandle.escape(this.keyword), ...pargs.map(ShortcodeHandle.escape), ...pairs]; + return `[${parts.join(" ")}]`; + } + + renderPreviewNode(pargs: string[], kwargs: Map): string { + // The html string representation of the shortcode in the TinyMCE editor + // By default this is a span marked with mceNonEditable and the shortcode keyword and parameters + // and defers the visual presented to the user to be rendered to renderPreview() + const ppairs = pargs.map((arg, i) => `data-parg${i}=${ShortcodeHandle.escape(arg)}`); + const kwpairs = this.sortKWargs(kwargs.entries()).map(([key, value]) => `data-kw-${key}=${ShortcodeHandle.escape(value)}`); + const parts = [`class="mceNonEditable"`, `data-shortcode="${this.keyword}"`, ...ppairs, ...kwpairs]; + return `${this.renderPreview(pargs, kwargs)}`; + } + + findNodes(): NodeListOf { + return this.editor.contentDocument.querySelectorAll(`[data-shortcode="${this.keyword}"]`); + } + + renderPreview(pargs: string[], kwargs: Map): string { + // The html string representation of the shortcode preview in the TinyMCE editor + // By default this is just the canonical text representation. This function will be overwritten by most subclasses. + return this.renderShortcode(pargs, kwargs); + } + + refreshPreview(predicate: ((pargs: string[], kwargs: Map) => boolean) | undefined) { + const previousSelection = this.editor.selection.getBookmark(); + + this.findNodes().forEach(((node: HTMLElement) => { + const [pargs, kwargs] = this.argsFromNode(node); + if (predicate && !predicate(pargs, kwargs)) return; + + this.editor.selection.select(node); + node.remove(); + this.editor.insertContent(this.renderShortcode(pargs, new Map(Object.entries(kwargs)))); + }).bind(this)); + + // Restore selection + this.editor.selection.moveToBookmark(previousSelection); + } + + reconstructArgsFromDialog(api: DialogInstanceApi): [string[], {[key: string]: string}, [string, string][]] { + // Reconstruct the positional and keyword arguments from the form data + const data = api.getData(); + const pargs = Object.entries(data).reduce((acc: string[], [key, value]) => { + const match = key.match(/^parg([0-9]+)$/); + if (match) { + acc[parseInt(match[1])] = value; + } + return acc; + }, []); + type TemporaryPairs = {[key: number]: [null | string, null | string]}; + type FinalizedPairs = {[key: string]: string}; + type OrderedPairs = [string, string][]; + const orderedKWargs: OrderedPairs = []; + const kwargs = Object.entries(data).reduce((acc: TemporaryPairs & FinalizedPairs, [key, value]) => { + // First piece together the names with the values again + const match = key.match(/^(kw-(.+)|kwarg([0-9]+)-(name|value))$/); + if (match) { + if (match[2]) { + acc[match[2]] = value; + } else { + const id = parseInt(match[3]); + const which = match[4] == "name" ? 0 : 1; + if (acc[id] === undefined) acc[id] = [null, null]; + acc[id][which] = value; + if (acc[id][(which+1) % 2] !== null) { + // Pair is complete! + [key, value] = acc[id]; + // Normalize keys to dash-style + // This is not its own overridable function because it is already automatically transformed + // to dash-style on the marker node representing the shortode to TinyMCE (this is how HTML attributes work) + // and to camelCase by the dataset API on JS side. + key = key.toLowerCase().replace(/\s+/g, "-"); + acc[key] = value; + orderedKWargs[id] = [key, value]; + delete acc[id]; + } + } + } + return acc; + }, {}) as unknown as FinalizedPairs; + return [pargs, kwargs, orderedKWargs]; + } + + openEditDialog() { + const node = this.getNode(); + const [initialPargs, initialKWargs] = this.truncateArgs(...this.argsFromNode(node)); + this.lastUnsavedPargs = initialPargs; + this.lastUnsavedKWargs = this.sortKWargs(initialKWargs.entries()); + return this.displayEditDialog(initialPargs, this.lastUnsavedKWargs); + } + + displayEditDialog(initialPargs: string[], initialKWargs: [string, string][]) { + // The default implementation for constructing the edit dialog for a generic shortcode + const argumentItems: BodyComponentSpec[] = []; + initialPargs.forEach((parg: string, i: number) => { + const explicit = this.getExplicitParg(i); + argumentItems.push.apply(argumentItems, [ + { + type: "label", + label: explicit.unifiedDescription, + for: `parg${i}`, + items: [ + { + type: "bar", + items: [ + { + type: "input", + name: `parg${i}`, + //label: explicit.unifiedDescription, + }, + ...(explicit.required ? [] : [{ + type: "htmlpanel", + html: ``, + }] as BodyComponentSpec[]), + ], + }, + ], + }, + ]); + }); + if (this.minPargs != this.maxPargs) { + argumentItems.push({ + type: "htmlpanel", + html: `
+
+ +
+
+ +
+
`.replace(/\s+/g, " "), + }); + } + initialKWargs.forEach(([keyword, value]: [string, string], i: number) => { + const explicit = this.getExplicitKWarg(keyword); + if (this.acceptingArbitraryKWargs) { + argumentItems.push.apply(argumentItems, [ + { + type: "label", + label: explicit.description || `Value`, + for: `parg${i}`, + items: [ + { + type: "bar", + items: [ + { + type: "input", + name: `kwarg${i}-name`, + label: explicit.name, + }, + { + type: "input", + name: `kwarg${i}-value`, + //label: explicit.description || `Value`, + }, + ...(explicit.required ? [] : [{ + type: "htmlpanel", + html: ``, + }] as BodyComponentSpec[]), + ], + }, + ], + }, + ]); + } else { + argumentItems.push({ + type: "label", + label: explicit.description || `Value`, + for: `parg${i}`, + items: [ + { + type: "bar", + items: [ + { + type: "input", + name: `kw-${keyword}`, + label: explicit.unifiedDescription, + }, + ...(explicit.required ? [] : [{ + type: "htmlpanel", + html: ``, + }] as BodyComponentSpec[]), + ], + }, + ], + }); + } + }); + const optionalKWargs = this.optionalKWargs; + if (this.acceptingArbitraryKWargs || optionalKWargs.size > 0) { + const keys = new Set(initialKWargs.map(([key, value]) => key)); + const missingRequired = this.requiredKWargs.difference(keys); + const missingOptional = optionalKWargs.difference(keys); + const givenOptional = optionalKWargs.intersection(keys); + argumentItems.push({ + type: "bar", + items: [ + { + type: "htmlpanel", + html: `
+
+ +
+
+ +
+
`.replace(/\s+/g, " "), + }, + ], + }); + } + + const dialogConfig: DialogSpec = { + title: this.text(this.editText), + body: { + type: "panel", + items: argumentItems, + }, + buttons: [ + { + type: "cancel", + text: this.tinymceConfig.getAttribute("data-dialog-cancel-text"), + }, + { + type: "submit", + name: "submit", + text: this.tinymceConfig.getAttribute("data-dialog-submit-text"), + primary: true, + }, + ], + initialData: this.defaultInitialData(initialPargs, initialKWargs), + onSubmit: this.defaultOnSubmit.bind(this), + onChange: this.defaultOnChange.bind(this), + }; + console.log(`[${this.keyword}]`, this, dialogConfig); + + setTimeout(this.defaultEditDialogRefinement.bind(this), 0); + + return this.editor.windowManager.open(dialogConfig); + } + + defaultInitialData(initialPargs: string[], initialKWargs: [string, string][]) { + return { + ...Object.fromEntries(initialPargs.map((parg: string, i: number) => [`parg${i}`, parg])), + ...Object.fromEntries(initialKWargs.reduce((acc, [keyword, value], i) => { + if (this.acceptingArbitraryKWargs) { + return acc.concat([ + [`kwarg${i}-name`, keyword], + [`kwarg${i}-value`, value], + ]); + } else { + return acc.concat([ + [`kw-${keyword}`, value], + ]); + } + }, [])), + }; + } + + defaultOnSubmit(api: DialogInstanceApi) { + // The default submit handler for the edit dialog + const [pargs, kwargs, orderedKWargs] = this.reconstructArgsFromDialog(api); + this.lastUnsavedPargs = pargs; + this.lastUnsavedKWargs = orderedKWargs; + + // Don't close the dialog if the arguments are not valid + if (!this.validate(pargs, kwargs)) + return console.error(`invalid arguments:`, pargs, kwargs); + + api.close(); + this.lastUnsavedPargs = null; + this.lastUnsavedKWargs = null; + + // Either insert a new shortcode or update the existing one + const node = this.getNode(); + if (!node) { + this.editor.insertContent(this.renderShortcode(pargs, new Map(Object.entries(kwargs)))); + } else { + node.remove(); + this.editor.insertContent(this.renderShortcode(pargs, new Map(Object.entries(kwargs)))); + } + } + + defaultOnChange(api: DialogInstanceApi) { + // The default change handler for the edit dialog + const [pargs, kwargs, orderedKWargs] = this.reconstructArgsFromDialog(api); + this.lastUnsavedPargs = pargs; + this.lastUnsavedKWargs = orderedKWargs; + } + + defaultEditDialogRefinement() { + // The default function invoked after rendering the edit dialog, + // e.g. to manipulate its HTML in a way TinyMCEs DialogSpec doesn't support + const dialog = document.querySelector('.tox-dialog'); + const pargRemove = dialog.querySelector('#parg-remove'); + const pargAdd = dialog.querySelector('#parg-add'); + if (pargRemove) { + pargRemove.addEventListener("click", (() => { + if (this.lastUnsavedPargs === null || this.lastUnsavedPargs.length <= this.minPargs) + return; + this.lastUnsavedPargs.pop(); + this.editor.windowManager.close(); + this.displayEditDialog(this.lastUnsavedPargs, this.lastUnsavedKWargs); + }).bind(this)); + } + if (pargAdd) { + pargAdd.addEventListener("click", (() => { + if (this.lastUnsavedPargs === null || this.lastUnsavedPargs.length >= this.maxPargs) + return; + this.lastUnsavedPargs.push(""); + this.editor.windowManager.close(); + this.displayEditDialog(this.lastUnsavedPargs, this.lastUnsavedKWargs); + }).bind(this)); + } + const kwargRemove = dialog.querySelector('#kwarg-remove'); + const kwargAdd = dialog.querySelector('#kwarg-add'); + if (kwargRemove) { + kwargRemove.addEventListener("click", (() => { + if (this.lastUnsavedKWargs === null) + return; + const requiredKWargs = this.requiredKWargs; + // Throw away the last keyword that is not required + for (let i = this.lastUnsavedKWargs.length-1; i >= 0; i--) { + if (requiredKWargs.has(this.lastUnsavedKWargs[i][0])) + continue; + const beforeThis = this.lastUnsavedKWargs.slice(0, i); + const afterThis = this.lastUnsavedKWargs.slice(i+1, this.lastUnsavedKWargs.length); + this.lastUnsavedKWargs = beforeThis.concat(afterThis); + break; + }; + this.editor.windowManager.close(); + this.displayEditDialog(this.lastUnsavedPargs, this.lastUnsavedKWargs); + }).bind(this)); + } + if (kwargAdd) { + kwargAdd.addEventListener("click", (() => { + if (this.lastUnsavedKWargs === null) + return; + // A flat list of known keywords in canonical order + const order = this.kwargsOrder; + function index(x: string): number { + // Determine the canonical position of the keyword + const i = order.indexOf(x); + if (i == -1) return Infinity; // If the keyword is unknown, sort it last + return i; + } + const requiredKWargs = this.requiredKWargs; + const keys = new Set(this.lastUnsavedKWargs.map(([key, value]) => key)); + const missingRequired = requiredKWargs.difference(keys); + let key; + if (missingRequired.size > 0) { + // Somehow, required keywords are missing. Add the first one by canonical order + key = Array.from(missingRequired).sort((a, b) => index(a) - index(b))[0]; + } else { + const optionalKWargs = this.optionalKWargs; + const missingOptional = optionalKWargs.difference(keys); + if (missingOptional.size > 0) { + // Add the first known optional argument by canonical order. If we don't know any and we accept arbitrary arguments, leave the key empty + key = Array.from(missingOptional).sort((a, b) => index(a) - index(b))[0] || ""; + } else if (this.acceptingArbitraryKWargs) { + key = ""; + } else + return; // There are no arguments left to add, immediately stop without doing anything + } + // Finally, actually append the key value pair and retrigger the dialog + this.lastUnsavedKWargs.push([key, ""]); + this.editor.windowManager.close(); + this.displayEditDialog(this.lastUnsavedPargs, this.lastUnsavedKWargs); + }).bind(this)); + } + } + + setup(editor: Editor): boolean { + /* Method to initialize the instance for an editor. + * Only if setup() returns true, the shortcode will be enabled. + */ + /* default behavior: + * - menu item to insert shortcode → open dialog + * - context toolbar with edit and delete + */ + this.editor = editor; + this.tinymceConfig = document.getElementById("tinymce-config-options"); + + const closeContextToolbar = () => { + editor.dispatch("contexttoolbar-hide", { + toolbarKey: `shortcode_${this.keyword}_context_form`, + }); + }; + + editor.ui.registry.addMenuItem(`add_shortcode_${this.keyword}`, { + text: this.text(this.addText), + icon: this.addIcon, + onAction: this.openEditDialog.bind(this), + }); + + editor.ui.registry.addButton(`edit_shortcode_${this.keyword}`, { + text: this.text(this.editText), + tooltip: this.text(this.editText), + icon: this.editIcon, + onAction: ((api: ToolbarButtonInstanceApi) => { + this.openEditDialog(); + closeContextToolbar(); + }).bind(this), + }); + + editor.ui.registry.addButton(`remove_shortcode_${this.keyword}`, { + text: this.text(this.removeText), + tooltip: this.text(this.removeText), + icon: this.removeIcon, + onAction: (() => { + const node = this.getNode(); + if (node) { + node.remove(); + } + closeContextToolbar(); + }).bind(this), + }); + + // This form opens when a shortcode is currently selected with the cursor + editor.ui.registry.addContextToolbar(`shortcode_${this.keyword}_context_form`, { + predicate: this.predicate.bind(this), + position: "node", + scope: "node", + items: `edit_shortcode_${this.keyword} remove_shortcode_${this.keyword}`, + }); + + return true; + } +} + + +class Registry { + static #instance: Registry; + + handles: Map; + unknownHandleFactory: null | ((keyword: string) => ShortcodeHandle); + + private constructor() { + this.handles = new Map(); + this.unknownHandleFactory = null; + } + + public static get instance(): Registry { + if (!Registry.#instance) { + Registry.#instance = new Registry(); + } + return Registry.#instance; + } + + public static has(keyword: string): boolean { + return Registry.instance.handles.has(keyword); + } + + public static get(keyword: string): ShortcodeHandle | null { + return Registry.instance.handles.get(keyword); + } + + public static register(handle: ShortcodeHandle) { + if (Registry.has(handle.keyword)) { + throw Error(`Keyword ${handle.keyword} already registered as ${Registry.get(handle.keyword)}`); + } + Registry.instance.handles.set(handle.keyword, handle); + } + public static unregister(handle: ShortcodeHandle | string): ShortcodeHandle { + const keyword = handle instanceof ShortcodeHandle ? handle.keyword : handle; + const old_handle = Registry.get(keyword); + if (handle instanceof ShortcodeHandle && old_handle !== handle) { + throw Error(`Keyword ${keyword} registered as a different handle: ${old_handle}`); + } + Registry.instance.handles.delete(keyword); + return old_handle; + } + public static unregisterAll() { + Registry.instance.handles.clear(); + } + + public static setUnknownHandleFactory(factory: (keyword: string) => ShortcodeHandle) { + this.instance.unknownHandleFactory = factory; + } + + public static setupAll(editor: Editor, parser: Parser) { + Registry.instance.handles.forEach((value: ShortcodeHandle, key: string) => { + if (value.setup(editor)) { + parser.register(value.renderPreviewNode.bind(value), key, value.endword); + } + }); + if (this.instance.unknownHandleFactory !== null) { + parser.setUnknownHandlerFactory((keyword: string) => { + const handle = this.instance.unknownHandleFactory(keyword); + const fn = handle.renderPreviewNode.bind(handle); + Registry.register(handle); + handle.setup(editor); + parser.register(fn, keyword); + console.log(`created and registered dummy handler for ${keyword}`); + return fn; + }); + } else { + parser.setUnknownHandlerFactory(null); + } + } +} + + +export { ShortcodeHandle, Registry, AcceptArbitraryArguments, TextDescriptor, SingleParg, PargsDescriptor, SingleKWarg, KWargsDescriptor, ExplicitArg }; diff --git a/integreat_cms/static/src/js/tinymce-plugins/tinymce.d.ts b/integreat_cms/static/src/js/tinymce-plugins/tinymce.d.ts new file mode 100644 index 0000000000..facc106ca8 --- /dev/null +++ b/integreat_cms/static/src/js/tinymce-plugins/tinymce.d.ts @@ -0,0 +1,3863 @@ +interface StringPathBookmark { + start: string; + end?: string; + forward?: boolean; +} +interface RangeBookmark { + rng: Range; + forward?: boolean; +} +interface IdBookmark { + id: string; + keep?: boolean; + forward?: boolean; +} +interface IndexBookmark { + name: string; + index: number; +} +interface PathBookmark { + start: number[]; + end?: number[]; + isFakeCaret?: boolean; + forward?: boolean; +} +type Bookmark = StringPathBookmark | RangeBookmark | IdBookmark | IndexBookmark | PathBookmark; +type NormalizedEvent = E & { + readonly type: string; + readonly target: T; + readonly isDefaultPrevented: () => boolean; + readonly preventDefault: () => void; + readonly isPropagationStopped: () => boolean; + readonly stopPropagation: () => void; + readonly isImmediatePropagationStopped: () => boolean; + readonly stopImmediatePropagation: () => void; +}; +type MappedEvent = K extends keyof T ? T[K] : any; +interface NativeEventMap { + 'beforepaste': Event; + 'blur': FocusEvent; + 'beforeinput': InputEvent; + 'click': MouseEvent; + 'compositionend': Event; + 'compositionstart': Event; + 'compositionupdate': Event; + 'contextmenu': PointerEvent; + 'copy': ClipboardEvent; + 'cut': ClipboardEvent; + 'dblclick': MouseEvent; + 'drag': DragEvent; + 'dragdrop': DragEvent; + 'dragend': DragEvent; + 'draggesture': DragEvent; + 'dragover': DragEvent; + 'dragstart': DragEvent; + 'drop': DragEvent; + 'focus': FocusEvent; + 'focusin': FocusEvent; + 'focusout': FocusEvent; + 'input': InputEvent; + 'keydown': KeyboardEvent; + 'keypress': KeyboardEvent; + 'keyup': KeyboardEvent; + 'mousedown': MouseEvent; + 'mouseenter': MouseEvent; + 'mouseleave': MouseEvent; + 'mousemove': MouseEvent; + 'mouseout': MouseEvent; + 'mouseover': MouseEvent; + 'mouseup': MouseEvent; + 'paste': ClipboardEvent; + 'selectionchange': Event; + 'submit': Event; + 'touchend': TouchEvent; + 'touchmove': TouchEvent; + 'touchstart': TouchEvent; + 'touchcancel': TouchEvent; + 'wheel': WheelEvent; +} +type EditorEvent = NormalizedEvent; +interface EventDispatcherSettings { + scope?: any; + toggleEvent?: (name: string, state: boolean) => void | boolean; + beforeFire?: (args: EditorEvent) => void; +} +interface EventDispatcherConstructor { + readonly prototype: EventDispatcher; + new (settings?: EventDispatcherSettings): EventDispatcher; + isNative: (name: string) => boolean; +} +declare class EventDispatcher { + static isNative(name: string): boolean; + private readonly settings; + private readonly scope; + private readonly toggleEvent; + private bindings; + constructor(settings?: EventDispatcherSettings); + fire>(name: K, args?: U): EditorEvent; + dispatch>(name: K, args?: U): EditorEvent; + on(name: K, callback: false | ((event: EditorEvent>) => void | boolean), prepend?: boolean, extra?: {}): this; + off(name?: K, callback?: (event: EditorEvent>) => void): this; + once(name: K, callback: (event: EditorEvent>) => void, prepend?: boolean): this; + has(name: string): boolean; +} +type UndoLevelType = 'fragmented' | 'complete'; +interface BaseUndoLevel { + type: UndoLevelType; + bookmark: Bookmark | null; + beforeBookmark: Bookmark | null; +} +interface FragmentedUndoLevel extends BaseUndoLevel { + type: 'fragmented'; + fragments: string[]; + content: ''; +} +interface CompleteUndoLevel extends BaseUndoLevel { + type: 'complete'; + fragments: null; + content: string; +} +type NewUndoLevel = CompleteUndoLevel | FragmentedUndoLevel; +type UndoLevel = NewUndoLevel & { + bookmark: Bookmark; +}; +interface UndoManager { + data: UndoLevel[]; + typing: boolean; + add: (level?: Partial, event?: EditorEvent) => UndoLevel | null; + dispatchChange: () => void; + beforeChange: () => void; + undo: () => UndoLevel | undefined; + redo: () => UndoLevel | undefined; + clear: () => void; + reset: () => void; + hasUndo: () => boolean; + hasRedo: () => boolean; + transact: (callback: () => void) => UndoLevel | null; + ignore: (callback: () => void) => void; + extra: (callback1: () => void, callback2: () => void) => void; +} +type SchemaType = 'html4' | 'html5' | 'html5-strict'; +interface ElementSettings { + block_elements?: string; + boolean_attributes?: string; + move_caret_before_on_enter_elements?: string; + non_empty_elements?: string; + self_closing_elements?: string; + text_block_elements?: string; + text_inline_elements?: string; + void_elements?: string; + whitespace_elements?: string; + transparent_elements?: string; + wrap_block_elements?: string; +} +interface SchemaSettings extends ElementSettings { + custom_elements?: string | Record; + extended_valid_elements?: string; + invalid_elements?: string; + invalid_styles?: string | Record; + schema?: SchemaType; + valid_children?: string; + valid_classes?: string | Record; + valid_elements?: string; + valid_styles?: string | Record; + verify_html?: boolean; + padd_empty_block_inline_children?: boolean; +} +interface Attribute { + required?: boolean; + defaultValue?: string; + forcedValue?: string; + validValues?: Record; +} +interface DefaultAttribute { + name: string; + value: string; +} +interface AttributePattern extends Attribute { + pattern: RegExp; +} +interface ElementRule { + attributes: Record; + attributesDefault?: DefaultAttribute[]; + attributesForced?: DefaultAttribute[]; + attributesOrder: string[]; + attributePatterns?: AttributePattern[]; + attributesRequired?: string[]; + paddEmpty?: boolean; + removeEmpty?: boolean; + removeEmptyAttrs?: boolean; + paddInEmptyBlock?: boolean; +} +interface SchemaElement extends ElementRule { + outputName?: string; + parentsRequired?: string[]; + pattern?: RegExp; +} +interface SchemaMap { + [name: string]: {}; +} +interface SchemaRegExpMap { + [name: string]: RegExp; +} +interface CustomElementSpec { + extends?: string; + attributes?: string[]; + children?: string[]; + padEmpty?: boolean; +} +interface Schema { + type: SchemaType; + children: Record; + elements: Record; + getValidStyles: () => Record | undefined; + getValidClasses: () => Record | undefined; + getBlockElements: () => SchemaMap; + getInvalidStyles: () => Record | undefined; + getVoidElements: () => SchemaMap; + getTextBlockElements: () => SchemaMap; + getTextInlineElements: () => SchemaMap; + getBoolAttrs: () => SchemaMap; + getElementRule: (name: string) => SchemaElement | undefined; + getSelfClosingElements: () => SchemaMap; + getNonEmptyElements: () => SchemaMap; + getMoveCaretBeforeOnEnterElements: () => SchemaMap; + getWhitespaceElements: () => SchemaMap; + getTransparentElements: () => SchemaMap; + getSpecialElements: () => SchemaRegExpMap; + isValidChild: (name: string, child: string) => boolean; + isValid: (name: string, attr?: string) => boolean; + isBlock: (name: string) => boolean; + isInline: (name: string) => boolean; + isWrapper: (name: string) => boolean; + getCustomElements: () => SchemaMap; + addValidElements: (validElements: string) => void; + setValidElements: (validElements: string) => void; + addCustomElements: (customElements: string | Record) => void; + addValidChildren: (validChildren: any) => void; +} +type Attributes$1 = Array<{ + name: string; + value: string; +}> & { + map: Record; +}; +interface AstNodeConstructor { + readonly prototype: AstNode; + new (name: string, type: number): AstNode; + create(name: string, attrs?: Record): AstNode; +} +declare class AstNode { + static create(name: string, attrs?: Record): AstNode; + name: string; + type: number; + attributes?: Attributes$1; + value?: string; + parent?: AstNode | null; + firstChild?: AstNode | null; + lastChild?: AstNode | null; + next?: AstNode | null; + prev?: AstNode | null; + raw?: boolean; + constructor(name: string, type: number); + replace(node: AstNode): AstNode; + attr(name: string, value: string | null | undefined): AstNode | undefined; + attr(name: Record | undefined): AstNode | undefined; + attr(name: string): string | undefined; + clone(): AstNode; + wrap(wrapper: AstNode): AstNode; + unwrap(): void; + remove(): AstNode; + append(node: AstNode): AstNode; + insert(node: AstNode, refNode: AstNode, before?: boolean): AstNode; + getAll(name: string): AstNode[]; + children(): AstNode[]; + empty(): AstNode; + isEmpty(elements: SchemaMap, whitespace?: SchemaMap, predicate?: (node: AstNode) => boolean): boolean; + walk(prev?: boolean): AstNode | null | undefined; +} +type Content = string | AstNode; +type ContentFormat = 'raw' | 'text' | 'html' | 'tree'; +interface GetContentArgs { + format: ContentFormat; + get: boolean; + getInner: boolean; + no_events?: boolean; + save?: boolean; + source_view?: boolean; + [key: string]: any; +} +interface SetContentArgs { + format: string; + set: boolean; + content: Content; + no_events?: boolean; + no_selection?: boolean; + paste?: boolean; + load?: boolean; + initial?: boolean; + [key: string]: any; +} +interface GetSelectionContentArgs extends GetContentArgs { + selection?: boolean; + contextual?: boolean; +} +interface SetSelectionContentArgs extends SetContentArgs { + content: string; + selection?: boolean; +} +interface BlobInfoData { + id?: string; + name?: string; + filename?: string; + blob: Blob; + base64: string; + blobUri?: string; + uri?: string; +} +interface BlobInfo { + id: () => string; + name: () => string; + filename: () => string; + blob: () => Blob; + base64: () => string; + blobUri: () => string; + uri: () => string | undefined; +} +interface BlobCache { + create: { + (o: BlobInfoData): BlobInfo; + (id: string, blob: Blob, base64: string, name?: string, filename?: string): BlobInfo; + }; + add: (blobInfo: BlobInfo) => void; + get: (id: string) => BlobInfo | undefined; + getByUri: (blobUri: string) => BlobInfo | undefined; + getByData: (base64: string, type: string) => BlobInfo | undefined; + findFirst: (predicate: (blobInfo: BlobInfo) => boolean) => BlobInfo | undefined; + removeByUri: (blobUri: string) => void; + destroy: () => void; +} +interface BlobInfoImagePair { + image: HTMLImageElement; + blobInfo: BlobInfo; +} +declare class NodeChange { + private readonly editor; + private lastPath; + constructor(editor: Editor); + nodeChanged(args?: Record): void; + private isSameElementPath; +} +interface SelectionOverrides { + showCaret: (direction: number, node: HTMLElement, before: boolean, scrollIntoView?: boolean) => Range | null; + showBlockCaretContainer: (blockCaretContainer: HTMLElement) => void; + hideFakeCaret: () => void; + destroy: () => void; +} +interface Quirks { + refreshContentEditable(): void; + isHidden(): boolean; +} +type DecoratorData = Record; +type Decorator = (uid: string, data: DecoratorData) => { + attributes?: {}; + classes?: string[]; +}; +type AnnotationListener = (state: boolean, name: string, data?: { + uid: string; + nodes: any[]; +}) => void; +type AnnotationListenerApi = AnnotationListener; +interface AnnotatorSettings { + decorate: Decorator; + persistent?: boolean; +} +interface Annotator { + register: (name: string, settings: AnnotatorSettings) => void; + annotate: (name: string, data: DecoratorData) => void; + annotationChanged: (name: string, f: AnnotationListenerApi) => void; + remove: (name: string) => void; + removeAll: (name: string) => void; + getAll: (name: string) => Record; +} +interface IsEmptyOptions { + readonly skipBogus?: boolean; + readonly includeZwsp?: boolean; + readonly checkRootAsContent?: boolean; + readonly isContent?: (node: Node) => boolean; +} +interface GeomRect { + readonly x: number; + readonly y: number; + readonly w: number; + readonly h: number; +} +interface Rect { + inflate: (rect: GeomRect, w: number, h: number) => GeomRect; + relativePosition: (rect: GeomRect, targetRect: GeomRect, rel: string) => GeomRect; + findBestRelativePosition: (rect: GeomRect, targetRect: GeomRect, constrainRect: GeomRect, rels: string[]) => string | null; + intersect: (rect: GeomRect, cropRect: GeomRect) => GeomRect | null; + clamp: (rect: GeomRect, clampRect: GeomRect, fixedSize?: boolean) => GeomRect; + create: (x: number, y: number, w: number, h: number) => GeomRect; + fromClientRect: (clientRect: DOMRect) => GeomRect; +} +interface NotificationManagerImpl { + open: (spec: NotificationSpec, closeCallback: () => void, hasEditorFocus: () => boolean) => NotificationApi; + close: (notification: T) => void; + getArgs: (notification: T) => NotificationSpec; +} +interface NotificationSpec { + type?: 'info' | 'warning' | 'error' | 'success'; + text: string; + icon?: string; + progressBar?: boolean; + timeout?: number; +} +interface NotificationApi { + close: () => void; + progressBar: { + value: (percent: number) => void; + }; + text: (text: string) => void; + reposition: () => void; + getEl: () => HTMLElement; + settings: NotificationSpec; +} +interface NotificationManager { + open: (spec: NotificationSpec) => NotificationApi; + close: () => void; + getNotifications: () => NotificationApi[]; +} +interface UploadFailure { + message: string; + remove?: boolean; +} +type ProgressFn = (percent: number) => void; +type UploadHandler = (blobInfo: BlobInfo, progress: ProgressFn) => Promise; +interface UploadResult$2 { + url: string; + blobInfo: BlobInfo; + status: boolean; + error?: UploadFailure; +} +type BlockPatternTrigger = 'enter' | 'space'; +interface RawPattern { + start?: any; + end?: any; + format?: any; + cmd?: any; + value?: any; + replacement?: any; + trigger?: BlockPatternTrigger; +} +interface InlineBasePattern { + readonly start: string; + readonly end: string; +} +interface InlineFormatPattern extends InlineBasePattern { + readonly type: 'inline-format'; + readonly format: string[]; +} +interface InlineCmdPattern extends InlineBasePattern { + readonly type: 'inline-command'; + readonly cmd: string; + readonly value?: any; +} +type InlinePattern = InlineFormatPattern | InlineCmdPattern; +interface BlockBasePattern { + readonly start: string; + readonly trigger: BlockPatternTrigger; +} +interface BlockFormatPattern extends BlockBasePattern { + readonly type: 'block-format'; + readonly format: string; +} +interface BlockCmdPattern extends BlockBasePattern { + readonly type: 'block-command'; + readonly cmd: string; + readonly value?: any; +} +type BlockPattern = BlockFormatPattern | BlockCmdPattern; +type Pattern = InlinePattern | BlockPattern; +interface DynamicPatternContext { + readonly text: string; + readonly block: Element; +} +type DynamicPatternsLookup = (ctx: DynamicPatternContext) => Pattern[]; +type RawDynamicPatternsLookup = (ctx: DynamicPatternContext) => RawPattern[]; +interface AlertBannerSpec { + type: 'alertbanner'; + level: 'info' | 'warn' | 'error' | 'success'; + text: string; + icon: string; + url?: string; +} +interface ButtonSpec { + type: 'button'; + text: string; + enabled?: boolean; + primary?: boolean; + name?: string; + icon?: string; + borderless?: boolean; + buttonType?: 'primary' | 'secondary' | 'toolbar'; +} +interface FormComponentSpec { + type: string; + name: string; +} +interface FormComponentWithLabelSpec extends FormComponentSpec { + label?: string; +} +interface CheckboxSpec extends FormComponentSpec { + type: 'checkbox'; + label: string; + enabled?: boolean; +} +interface CollectionSpec extends FormComponentWithLabelSpec { + type: 'collection'; +} +interface CollectionItem { + value: string; + text: string; + icon: string; +} +interface ColorInputSpec extends FormComponentWithLabelSpec { + type: 'colorinput'; + storageKey?: string; +} +interface ColorPickerSpec extends FormComponentWithLabelSpec { + type: 'colorpicker'; +} +interface CustomEditorInit { + setValue: (value: string) => void; + getValue: () => string; + destroy: () => void; +} +type CustomEditorInitFn = (elm: HTMLElement, settings: any) => Promise; +interface CustomEditorOldSpec extends FormComponentSpec { + type: 'customeditor'; + tag?: string; + init: (e: HTMLElement) => Promise; +} +interface CustomEditorNewSpec extends FormComponentSpec { + type: 'customeditor'; + tag?: string; + scriptId: string; + scriptUrl: string; + onFocus?: (e: HTMLElement) => void; + settings?: any; +} +type CustomEditorSpec = CustomEditorOldSpec | CustomEditorNewSpec; +interface DropZoneSpec extends FormComponentWithLabelSpec { + type: 'dropzone'; +} +interface GridSpec { + type: 'grid'; + columns: number; + items: BodyComponentSpec[]; +} +interface HtmlPanelSpec { + type: 'htmlpanel'; + html: string; + onInit?: (el: HTMLElement) => void; + presets?: 'presentation' | 'document'; + stretched?: boolean; +} +interface IframeSpec extends FormComponentWithLabelSpec { + type: 'iframe'; + border?: boolean; + sandboxed?: boolean; + streamContent?: boolean; + transparent?: boolean; +} +interface ImagePreviewSpec extends FormComponentSpec { + type: 'imagepreview'; + height?: string; +} +interface InputSpec extends FormComponentWithLabelSpec { + type: 'input'; + inputMode?: string; + placeholder?: string; + maximized?: boolean; + enabled?: boolean; +} +type Alignment = 'start' | 'center' | 'end'; +interface LabelSpec { + type: 'label'; + label: string; + items: BodyComponentSpec[]; + align?: Alignment; + for?: string; +} +interface ListBoxSingleItemSpec { + text: string; + value: string; +} +interface ListBoxNestedItemSpec { + text: string; + items: ListBoxItemSpec[]; +} +type ListBoxItemSpec = ListBoxNestedItemSpec | ListBoxSingleItemSpec; +interface ListBoxSpec extends FormComponentWithLabelSpec { + type: 'listbox'; + items: ListBoxItemSpec[]; + disabled?: boolean; +} +interface PanelSpec { + type: 'panel'; + classes?: string[]; + items: BodyComponentSpec[]; +} +interface SelectBoxItemSpec { + text: string; + value: string; +} +interface SelectBoxSpec extends FormComponentWithLabelSpec { + type: 'selectbox'; + items: SelectBoxItemSpec[]; + size?: number; + enabled?: boolean; +} +interface SizeInputSpec extends FormComponentWithLabelSpec { + type: 'sizeinput'; + constrain?: boolean; + enabled?: boolean; +} +interface SliderSpec extends FormComponentSpec { + type: 'slider'; + label: string; + min?: number; + max?: number; +} +interface TableSpec { + type: 'table'; + header: string[]; + cells: string[][]; +} +interface TextAreaSpec extends FormComponentWithLabelSpec { + type: 'textarea'; + placeholder?: string; + maximized?: boolean; + enabled?: boolean; +} +interface BaseToolbarButtonSpec { + enabled?: boolean; + tooltip?: string; + icon?: string; + text?: string; + onSetup?: (api: I) => (api: I) => void; +} +interface BaseToolbarButtonInstanceApi { + isEnabled: () => boolean; + setEnabled: (state: boolean) => void; + setText: (text: string) => void; + setIcon: (icon: string) => void; +} +interface ToolbarButtonSpec extends BaseToolbarButtonSpec { + type?: 'button'; + onAction: (api: ToolbarButtonInstanceApi) => void; + shortcut?: string; +} +interface ToolbarButtonInstanceApi extends BaseToolbarButtonInstanceApi { +} +interface ToolbarGroupSetting { + name: string; + items: string[]; +} +type ToolbarConfig = string | ToolbarGroupSetting[]; +interface GroupToolbarButtonInstanceApi extends BaseToolbarButtonInstanceApi { +} +interface GroupToolbarButtonSpec extends BaseToolbarButtonSpec { + type?: 'grouptoolbarbutton'; + items?: ToolbarConfig; +} +interface CardImageSpec { + type: 'cardimage'; + src: string; + alt?: string; + classes?: string[]; +} +interface CardTextSpec { + type: 'cardtext'; + text: string; + name?: string; + classes?: string[]; +} +type CardItemSpec = CardContainerSpec | CardImageSpec | CardTextSpec; +type CardContainerDirection = 'vertical' | 'horizontal'; +type CardContainerAlign = 'left' | 'right'; +type CardContainerValign = 'top' | 'middle' | 'bottom'; +interface CardContainerSpec { + type: 'cardcontainer'; + items: CardItemSpec[]; + direction?: CardContainerDirection; + align?: CardContainerAlign; + valign?: CardContainerValign; +} +interface CommonMenuItemSpec { + enabled?: boolean; + text?: string; + value?: string; + meta?: Record; + shortcut?: string; +} +interface CommonMenuItemInstanceApi { + isEnabled: () => boolean; + setEnabled: (state: boolean) => void; +} +interface CardMenuItemInstanceApi extends CommonMenuItemInstanceApi { +} +interface CardMenuItemSpec extends Omit { + type: 'cardmenuitem'; + label?: string; + items: CardItemSpec[]; + onSetup?: (api: CardMenuItemInstanceApi) => (api: CardMenuItemInstanceApi) => void; + onAction?: (api: CardMenuItemInstanceApi) => void; +} +interface ChoiceMenuItemSpec extends CommonMenuItemSpec { + type?: 'choiceitem'; + icon?: string; +} +interface ChoiceMenuItemInstanceApi extends CommonMenuItemInstanceApi { + isActive: () => boolean; + setActive: (state: boolean) => void; +} +interface ContextMenuItem extends CommonMenuItemSpec { + text: string; + icon?: string; + type?: 'item'; + onAction: () => void; +} +interface ContextSubMenu extends CommonMenuItemSpec { + type: 'submenu'; + text: string; + icon?: string; + getSubmenuItems: () => string | Array; +} +type ContextMenuContents = string | ContextMenuItem | SeparatorMenuItemSpec | ContextSubMenu; +interface ContextMenuApi { + update: (element: Element) => string | Array; +} +interface FancyActionArgsMap { + 'inserttable': { + numRows: number; + numColumns: number; + }; + 'colorswatch': { + value: string; + }; +} +interface BaseFancyMenuItemSpec { + type: 'fancymenuitem'; + fancytype: T; + initData?: Record; + onAction?: (data: FancyActionArgsMap[T]) => void; +} +interface InsertTableMenuItemSpec extends BaseFancyMenuItemSpec<'inserttable'> { + fancytype: 'inserttable'; + initData?: {}; +} +interface ColorSwatchMenuItemSpec extends BaseFancyMenuItemSpec<'colorswatch'> { + fancytype: 'colorswatch'; + select?: (value: string) => boolean; + initData?: { + allowCustomColors?: boolean; + colors?: ChoiceMenuItemSpec[]; + storageKey?: string; + }; +} +type FancyMenuItemSpec = InsertTableMenuItemSpec | ColorSwatchMenuItemSpec; +interface MenuItemSpec extends CommonMenuItemSpec { + type?: 'menuitem'; + icon?: string; + onSetup?: (api: MenuItemInstanceApi) => (api: MenuItemInstanceApi) => void; + onAction?: (api: MenuItemInstanceApi) => void; +} +interface MenuItemInstanceApi extends CommonMenuItemInstanceApi { +} +interface SeparatorMenuItemSpec { + type?: 'separator'; + text?: string; +} +interface ToggleMenuItemSpec extends CommonMenuItemSpec { + type?: 'togglemenuitem'; + icon?: string; + active?: boolean; + onSetup?: (api: ToggleMenuItemInstanceApi) => void; + onAction: (api: ToggleMenuItemInstanceApi) => void; +} +interface ToggleMenuItemInstanceApi extends CommonMenuItemInstanceApi { + isActive: () => boolean; + setActive: (state: boolean) => void; +} +type NestedMenuItemContents = string | MenuItemSpec | NestedMenuItemSpec | ToggleMenuItemSpec | SeparatorMenuItemSpec | FancyMenuItemSpec; +interface NestedMenuItemSpec extends CommonMenuItemSpec { + type?: 'nestedmenuitem'; + icon?: string; + getSubmenuItems: () => string | Array; + onSetup?: (api: NestedMenuItemInstanceApi) => (api: NestedMenuItemInstanceApi) => void; +} +interface NestedMenuItemInstanceApi extends CommonMenuItemInstanceApi { + setTooltip: (tooltip: string) => void; + setIconFill: (id: string, value: string) => void; +} +type MenuButtonItemTypes = NestedMenuItemContents; +type SuccessCallback$1 = (menu: string | MenuButtonItemTypes[]) => void; +interface MenuButtonFetchContext { + pattern: string; +} +interface BaseMenuButtonSpec { + text?: string; + tooltip?: string; + icon?: string; + search?: boolean | { + placeholder?: string; + }; + fetch: (success: SuccessCallback$1, fetchContext: MenuButtonFetchContext, api: BaseMenuButtonInstanceApi) => void; + onSetup?: (api: BaseMenuButtonInstanceApi) => (api: BaseMenuButtonInstanceApi) => void; +} +interface BaseMenuButtonInstanceApi { + isEnabled: () => boolean; + setEnabled: (state: boolean) => void; + isActive: () => boolean; + setActive: (state: boolean) => void; + setText: (text: string) => void; + setIcon: (icon: string) => void; +} +interface ToolbarMenuButtonSpec extends BaseMenuButtonSpec { + type?: 'menubutton'; + onSetup?: (api: ToolbarMenuButtonInstanceApi) => (api: ToolbarMenuButtonInstanceApi) => void; +} +interface ToolbarMenuButtonInstanceApi extends BaseMenuButtonInstanceApi { +} +type ToolbarSplitButtonItemTypes = ChoiceMenuItemSpec | SeparatorMenuItemSpec; +type SuccessCallback = (menu: ToolbarSplitButtonItemTypes[]) => void; +type SelectPredicate = (value: string) => boolean; +type PresetTypes = 'color' | 'normal' | 'listpreview'; +type ColumnTypes$1 = number | 'auto'; +interface ToolbarSplitButtonSpec { + type?: 'splitbutton'; + tooltip?: string; + icon?: string; + text?: string; + select?: SelectPredicate; + presets?: PresetTypes; + columns?: ColumnTypes$1; + fetch: (success: SuccessCallback) => void; + onSetup?: (api: ToolbarSplitButtonInstanceApi) => (api: ToolbarSplitButtonInstanceApi) => void; + onAction: (api: ToolbarSplitButtonInstanceApi) => void; + onItemAction: (api: ToolbarSplitButtonInstanceApi, value: string) => void; +} +interface ToolbarSplitButtonInstanceApi { + isEnabled: () => boolean; + setEnabled: (state: boolean) => void; + setIconFill: (id: string, value: string) => void; + isActive: () => boolean; + setActive: (state: boolean) => void; + setTooltip: (tooltip: string) => void; + setText: (text: string) => void; + setIcon: (icon: string) => void; +} +interface BaseToolbarToggleButtonSpec extends BaseToolbarButtonSpec { + active?: boolean; +} +interface BaseToolbarToggleButtonInstanceApi extends BaseToolbarButtonInstanceApi { + isActive: () => boolean; + setActive: (state: boolean) => void; +} +interface ToolbarToggleButtonSpec extends BaseToolbarToggleButtonSpec { + type?: 'togglebutton'; + onAction: (api: ToolbarToggleButtonInstanceApi) => void; + shortcut?: string; +} +interface ToolbarToggleButtonInstanceApi extends BaseToolbarToggleButtonInstanceApi { +} +type Id = string; +interface TreeSpec { + type: 'tree'; + items: TreeItemSpec[]; + onLeafAction?: (id: Id) => void; + defaultExpandedIds?: Id[]; + onToggleExpand?: (expandedIds: Id[], { expanded, node }: { + expanded: boolean; + node: Id; + }) => void; + defaultSelectedId?: Id; +} +interface BaseTreeItemSpec { + title: string; + id: Id; + menu?: ToolbarMenuButtonSpec; +} +interface DirectorySpec extends BaseTreeItemSpec { + type: 'directory'; + children: TreeItemSpec[]; +} +interface LeafSpec extends BaseTreeItemSpec { + type: 'leaf'; +} +type TreeItemSpec = DirectorySpec | LeafSpec; +interface UrlInputSpec extends FormComponentWithLabelSpec { + type: 'urlinput'; + filetype?: 'image' | 'media' | 'file'; + enabled?: boolean; + picker_text?: string; +} +interface UrlInputData { + value: string; + meta: { + text?: string; + }; +} +type BodyComponentSpec = BarSpec | ButtonSpec | CheckboxSpec | TextAreaSpec | InputSpec | ListBoxSpec | SelectBoxSpec | SizeInputSpec | SliderSpec | IframeSpec | HtmlPanelSpec | UrlInputSpec | DropZoneSpec | ColorInputSpec | GridSpec | ColorPickerSpec | ImagePreviewSpec | AlertBannerSpec | CollectionSpec | LabelSpec | TableSpec | TreeSpec | PanelSpec | CustomEditorSpec; +interface BarSpec { + type: 'bar'; + items: BodyComponentSpec[]; +} +interface DialogToggleMenuItemSpec extends CommonMenuItemSpec { + type?: 'togglemenuitem'; + name: string; +} +type DialogFooterMenuButtonItemSpec = DialogToggleMenuItemSpec; +interface BaseDialogFooterButtonSpec { + name?: string; + align?: 'start' | 'end'; + primary?: boolean; + enabled?: boolean; + icon?: string; + buttonType?: 'primary' | 'secondary'; +} +interface DialogFooterNormalButtonSpec extends BaseDialogFooterButtonSpec { + type: 'submit' | 'cancel' | 'custom'; + text: string; +} +interface DialogFooterMenuButtonSpec extends BaseDialogFooterButtonSpec { + type: 'menu'; + text?: string; + tooltip?: string; + icon?: string; + items: DialogFooterMenuButtonItemSpec[]; +} +interface DialogFooterToggleButtonSpec extends BaseDialogFooterButtonSpec { + type: 'togglebutton'; + tooltip?: string; + icon?: string; + text?: string; + active?: boolean; +} +type DialogFooterButtonSpec = DialogFooterNormalButtonSpec | DialogFooterMenuButtonSpec | DialogFooterToggleButtonSpec; +interface TabSpec { + name?: string; + title: string; + items: BodyComponentSpec[]; +} +interface TabPanelSpec { + type: 'tabpanel'; + tabs: TabSpec[]; +} +type DialogDataItem = any; +type DialogData = Record; +interface DialogInstanceApi { + getData: () => T; + setData: (data: Partial) => void; + setEnabled: (name: string, state: boolean) => void; + focus: (name: string) => void; + showTab: (name: string) => void; + redial: (nu: DialogSpec) => void; + block: (msg: string) => void; + unblock: () => void; + toggleFullscreen: () => void; + close: () => void; +} +interface DialogActionDetails { + name: string; + value?: any; +} +interface DialogChangeDetails { + name: keyof T; +} +interface DialogTabChangeDetails { + newTabName: string; + oldTabName: string; +} +type DialogActionHandler = (api: DialogInstanceApi, details: DialogActionDetails) => void; +type DialogChangeHandler = (api: DialogInstanceApi, details: DialogChangeDetails) => void; +type DialogSubmitHandler = (api: DialogInstanceApi) => void; +type DialogCloseHandler = () => void; +type DialogCancelHandler = (api: DialogInstanceApi) => void; +type DialogTabChangeHandler = (api: DialogInstanceApi, details: DialogTabChangeDetails) => void; +type DialogSize = 'normal' | 'medium' | 'large'; +interface DialogSpec { + title: string; + size?: DialogSize; + body: TabPanelSpec | PanelSpec; + buttons?: DialogFooterButtonSpec[]; + initialData?: Partial; + onAction?: DialogActionHandler; + onChange?: DialogChangeHandler; + onSubmit?: DialogSubmitHandler; + onClose?: DialogCloseHandler; + onCancel?: DialogCancelHandler; + onTabChange?: DialogTabChangeHandler; +} +interface UrlDialogInstanceApi { + block: (msg: string) => void; + unblock: () => void; + close: () => void; + sendMessage: (msg: any) => void; +} +interface UrlDialogActionDetails { + name: string; + value?: any; +} +interface UrlDialogMessage { + mceAction: string; + [key: string]: any; +} +type UrlDialogActionHandler = (api: UrlDialogInstanceApi, actions: UrlDialogActionDetails) => void; +type UrlDialogCloseHandler = () => void; +type UrlDialogCancelHandler = (api: UrlDialogInstanceApi) => void; +type UrlDialogMessageHandler = (api: UrlDialogInstanceApi, message: UrlDialogMessage) => void; +interface UrlDialogFooterButtonSpec extends DialogFooterNormalButtonSpec { + type: 'cancel' | 'custom'; +} +interface UrlDialogSpec { + title: string; + url: string; + height?: number; + width?: number; + buttons?: UrlDialogFooterButtonSpec[]; + onAction?: UrlDialogActionHandler; + onClose?: UrlDialogCloseHandler; + onCancel?: UrlDialogCancelHandler; + onMessage?: UrlDialogMessageHandler; +} +type ColumnTypes = number | 'auto'; +type SeparatorItemSpec = SeparatorMenuItemSpec; +interface AutocompleterItemSpec { + type?: 'autocompleteitem'; + value: string; + text?: string; + icon?: string; + meta?: Record; +} +type AutocompleterContents = SeparatorItemSpec | AutocompleterItemSpec | CardMenuItemSpec; +interface AutocompleterSpec { + type?: 'autocompleter'; + trigger: string; + minChars?: number; + columns?: ColumnTypes; + matches?: (rng: Range, text: string, pattern: string) => boolean; + fetch: (pattern: string, maxResults: number, fetchOptions: Record) => Promise; + onAction: (autocompleterApi: AutocompleterInstanceApi, rng: Range, value: string, meta: Record) => void; + maxResults?: number; + highlightOn?: string[]; +} +interface AutocompleterInstanceApi { + hide: () => void; + reload: (fetchOptions: Record) => void; +} +type ContextPosition = 'node' | 'selection' | 'line'; +type ContextScope = 'node' | 'editor'; +interface ContextBarSpec { + predicate?: (elem: Element) => boolean; + position?: ContextPosition; + scope?: ContextScope; +} +interface ContextFormLaunchButtonApi extends BaseToolbarButtonSpec { + type: 'contextformbutton'; +} +interface ContextFormLaunchToggleButtonSpec extends BaseToolbarToggleButtonSpec { + type: 'contextformtogglebutton'; +} +interface ContextFormButtonInstanceApi extends BaseToolbarButtonInstanceApi { +} +interface ContextFormToggleButtonInstanceApi extends BaseToolbarToggleButtonInstanceApi { +} +interface ContextFormButtonSpec extends BaseToolbarButtonSpec { + type?: 'contextformbutton'; + primary?: boolean; + onAction: (formApi: ContextFormInstanceApi, api: ContextFormButtonInstanceApi) => void; +} +interface ContextFormToggleButtonSpec extends BaseToolbarToggleButtonSpec { + type?: 'contextformtogglebutton'; + onAction: (formApi: ContextFormInstanceApi, buttonApi: ContextFormToggleButtonInstanceApi) => void; + primary?: boolean; +} +interface ContextFormInstanceApi { + hide: () => void; + getValue: () => string; +} +interface ContextFormSpec extends ContextBarSpec { + type?: 'contextform'; + initValue?: () => string; + label?: string; + launch?: ContextFormLaunchButtonApi | ContextFormLaunchToggleButtonSpec; + commands: Array; +} +interface ContextToolbarSpec extends ContextBarSpec { + type?: 'contexttoolbar'; + items: string; +} +type PublicDialog_d_AlertBannerSpec = AlertBannerSpec; +type PublicDialog_d_BarSpec = BarSpec; +type PublicDialog_d_BodyComponentSpec = BodyComponentSpec; +type PublicDialog_d_ButtonSpec = ButtonSpec; +type PublicDialog_d_CheckboxSpec = CheckboxSpec; +type PublicDialog_d_CollectionItem = CollectionItem; +type PublicDialog_d_CollectionSpec = CollectionSpec; +type PublicDialog_d_ColorInputSpec = ColorInputSpec; +type PublicDialog_d_ColorPickerSpec = ColorPickerSpec; +type PublicDialog_d_CustomEditorSpec = CustomEditorSpec; +type PublicDialog_d_CustomEditorInit = CustomEditorInit; +type PublicDialog_d_CustomEditorInitFn = CustomEditorInitFn; +type PublicDialog_d_DialogData = DialogData; +type PublicDialog_d_DialogSize = DialogSize; +type PublicDialog_d_DialogSpec = DialogSpec; +type PublicDialog_d_DialogInstanceApi = DialogInstanceApi; +type PublicDialog_d_DialogFooterButtonSpec = DialogFooterButtonSpec; +type PublicDialog_d_DialogActionDetails = DialogActionDetails; +type PublicDialog_d_DialogChangeDetails = DialogChangeDetails; +type PublicDialog_d_DialogTabChangeDetails = DialogTabChangeDetails; +type PublicDialog_d_DropZoneSpec = DropZoneSpec; +type PublicDialog_d_GridSpec = GridSpec; +type PublicDialog_d_HtmlPanelSpec = HtmlPanelSpec; +type PublicDialog_d_IframeSpec = IframeSpec; +type PublicDialog_d_ImagePreviewSpec = ImagePreviewSpec; +type PublicDialog_d_InputSpec = InputSpec; +type PublicDialog_d_LabelSpec = LabelSpec; +type PublicDialog_d_ListBoxSpec = ListBoxSpec; +type PublicDialog_d_ListBoxItemSpec = ListBoxItemSpec; +type PublicDialog_d_ListBoxNestedItemSpec = ListBoxNestedItemSpec; +type PublicDialog_d_ListBoxSingleItemSpec = ListBoxSingleItemSpec; +type PublicDialog_d_PanelSpec = PanelSpec; +type PublicDialog_d_SelectBoxSpec = SelectBoxSpec; +type PublicDialog_d_SelectBoxItemSpec = SelectBoxItemSpec; +type PublicDialog_d_SizeInputSpec = SizeInputSpec; +type PublicDialog_d_SliderSpec = SliderSpec; +type PublicDialog_d_TableSpec = TableSpec; +type PublicDialog_d_TabSpec = TabSpec; +type PublicDialog_d_TabPanelSpec = TabPanelSpec; +type PublicDialog_d_TextAreaSpec = TextAreaSpec; +type PublicDialog_d_TreeSpec = TreeSpec; +type PublicDialog_d_TreeItemSpec = TreeItemSpec; +type PublicDialog_d_UrlInputData = UrlInputData; +type PublicDialog_d_UrlInputSpec = UrlInputSpec; +type PublicDialog_d_UrlDialogSpec = UrlDialogSpec; +type PublicDialog_d_UrlDialogFooterButtonSpec = UrlDialogFooterButtonSpec; +type PublicDialog_d_UrlDialogInstanceApi = UrlDialogInstanceApi; +type PublicDialog_d_UrlDialogActionDetails = UrlDialogActionDetails; +type PublicDialog_d_UrlDialogMessage = UrlDialogMessage; +declare namespace PublicDialog_d { + export { PublicDialog_d_AlertBannerSpec as AlertBannerSpec, PublicDialog_d_BarSpec as BarSpec, PublicDialog_d_BodyComponentSpec as BodyComponentSpec, PublicDialog_d_ButtonSpec as ButtonSpec, PublicDialog_d_CheckboxSpec as CheckboxSpec, PublicDialog_d_CollectionItem as CollectionItem, PublicDialog_d_CollectionSpec as CollectionSpec, PublicDialog_d_ColorInputSpec as ColorInputSpec, PublicDialog_d_ColorPickerSpec as ColorPickerSpec, PublicDialog_d_CustomEditorSpec as CustomEditorSpec, PublicDialog_d_CustomEditorInit as CustomEditorInit, PublicDialog_d_CustomEditorInitFn as CustomEditorInitFn, PublicDialog_d_DialogData as DialogData, PublicDialog_d_DialogSize as DialogSize, PublicDialog_d_DialogSpec as DialogSpec, PublicDialog_d_DialogInstanceApi as DialogInstanceApi, PublicDialog_d_DialogFooterButtonSpec as DialogFooterButtonSpec, PublicDialog_d_DialogActionDetails as DialogActionDetails, PublicDialog_d_DialogChangeDetails as DialogChangeDetails, PublicDialog_d_DialogTabChangeDetails as DialogTabChangeDetails, PublicDialog_d_DropZoneSpec as DropZoneSpec, PublicDialog_d_GridSpec as GridSpec, PublicDialog_d_HtmlPanelSpec as HtmlPanelSpec, PublicDialog_d_IframeSpec as IframeSpec, PublicDialog_d_ImagePreviewSpec as ImagePreviewSpec, PublicDialog_d_InputSpec as InputSpec, PublicDialog_d_LabelSpec as LabelSpec, PublicDialog_d_ListBoxSpec as ListBoxSpec, PublicDialog_d_ListBoxItemSpec as ListBoxItemSpec, PublicDialog_d_ListBoxNestedItemSpec as ListBoxNestedItemSpec, PublicDialog_d_ListBoxSingleItemSpec as ListBoxSingleItemSpec, PublicDialog_d_PanelSpec as PanelSpec, PublicDialog_d_SelectBoxSpec as SelectBoxSpec, PublicDialog_d_SelectBoxItemSpec as SelectBoxItemSpec, PublicDialog_d_SizeInputSpec as SizeInputSpec, PublicDialog_d_SliderSpec as SliderSpec, PublicDialog_d_TableSpec as TableSpec, PublicDialog_d_TabSpec as TabSpec, PublicDialog_d_TabPanelSpec as TabPanelSpec, PublicDialog_d_TextAreaSpec as TextAreaSpec, PublicDialog_d_TreeSpec as TreeSpec, PublicDialog_d_TreeItemSpec as TreeItemSpec, DirectorySpec as TreeDirectorySpec, LeafSpec as TreeLeafSpec, PublicDialog_d_UrlInputData as UrlInputData, PublicDialog_d_UrlInputSpec as UrlInputSpec, PublicDialog_d_UrlDialogSpec as UrlDialogSpec, PublicDialog_d_UrlDialogFooterButtonSpec as UrlDialogFooterButtonSpec, PublicDialog_d_UrlDialogInstanceApi as UrlDialogInstanceApi, PublicDialog_d_UrlDialogActionDetails as UrlDialogActionDetails, PublicDialog_d_UrlDialogMessage as UrlDialogMessage, }; +} +type PublicInlineContent_d_AutocompleterSpec = AutocompleterSpec; +type PublicInlineContent_d_AutocompleterItemSpec = AutocompleterItemSpec; +type PublicInlineContent_d_AutocompleterContents = AutocompleterContents; +type PublicInlineContent_d_AutocompleterInstanceApi = AutocompleterInstanceApi; +type PublicInlineContent_d_ContextPosition = ContextPosition; +type PublicInlineContent_d_ContextScope = ContextScope; +type PublicInlineContent_d_ContextFormSpec = ContextFormSpec; +type PublicInlineContent_d_ContextFormInstanceApi = ContextFormInstanceApi; +type PublicInlineContent_d_ContextFormButtonSpec = ContextFormButtonSpec; +type PublicInlineContent_d_ContextFormButtonInstanceApi = ContextFormButtonInstanceApi; +type PublicInlineContent_d_ContextFormToggleButtonSpec = ContextFormToggleButtonSpec; +type PublicInlineContent_d_ContextFormToggleButtonInstanceApi = ContextFormToggleButtonInstanceApi; +type PublicInlineContent_d_ContextToolbarSpec = ContextToolbarSpec; +type PublicInlineContent_d_SeparatorItemSpec = SeparatorItemSpec; +declare namespace PublicInlineContent_d { + export { PublicInlineContent_d_AutocompleterSpec as AutocompleterSpec, PublicInlineContent_d_AutocompleterItemSpec as AutocompleterItemSpec, PublicInlineContent_d_AutocompleterContents as AutocompleterContents, PublicInlineContent_d_AutocompleterInstanceApi as AutocompleterInstanceApi, PublicInlineContent_d_ContextPosition as ContextPosition, PublicInlineContent_d_ContextScope as ContextScope, PublicInlineContent_d_ContextFormSpec as ContextFormSpec, PublicInlineContent_d_ContextFormInstanceApi as ContextFormInstanceApi, PublicInlineContent_d_ContextFormButtonSpec as ContextFormButtonSpec, PublicInlineContent_d_ContextFormButtonInstanceApi as ContextFormButtonInstanceApi, PublicInlineContent_d_ContextFormToggleButtonSpec as ContextFormToggleButtonSpec, PublicInlineContent_d_ContextFormToggleButtonInstanceApi as ContextFormToggleButtonInstanceApi, PublicInlineContent_d_ContextToolbarSpec as ContextToolbarSpec, PublicInlineContent_d_SeparatorItemSpec as SeparatorItemSpec, }; +} +type PublicMenu_d_MenuItemSpec = MenuItemSpec; +type PublicMenu_d_MenuItemInstanceApi = MenuItemInstanceApi; +type PublicMenu_d_NestedMenuItemContents = NestedMenuItemContents; +type PublicMenu_d_NestedMenuItemSpec = NestedMenuItemSpec; +type PublicMenu_d_NestedMenuItemInstanceApi = NestedMenuItemInstanceApi; +type PublicMenu_d_FancyMenuItemSpec = FancyMenuItemSpec; +type PublicMenu_d_ColorSwatchMenuItemSpec = ColorSwatchMenuItemSpec; +type PublicMenu_d_InsertTableMenuItemSpec = InsertTableMenuItemSpec; +type PublicMenu_d_ToggleMenuItemSpec = ToggleMenuItemSpec; +type PublicMenu_d_ToggleMenuItemInstanceApi = ToggleMenuItemInstanceApi; +type PublicMenu_d_ChoiceMenuItemSpec = ChoiceMenuItemSpec; +type PublicMenu_d_ChoiceMenuItemInstanceApi = ChoiceMenuItemInstanceApi; +type PublicMenu_d_SeparatorMenuItemSpec = SeparatorMenuItemSpec; +type PublicMenu_d_ContextMenuApi = ContextMenuApi; +type PublicMenu_d_ContextMenuContents = ContextMenuContents; +type PublicMenu_d_ContextMenuItem = ContextMenuItem; +type PublicMenu_d_ContextSubMenu = ContextSubMenu; +type PublicMenu_d_CardMenuItemSpec = CardMenuItemSpec; +type PublicMenu_d_CardMenuItemInstanceApi = CardMenuItemInstanceApi; +type PublicMenu_d_CardItemSpec = CardItemSpec; +type PublicMenu_d_CardContainerSpec = CardContainerSpec; +type PublicMenu_d_CardImageSpec = CardImageSpec; +type PublicMenu_d_CardTextSpec = CardTextSpec; +declare namespace PublicMenu_d { + export { PublicMenu_d_MenuItemSpec as MenuItemSpec, PublicMenu_d_MenuItemInstanceApi as MenuItemInstanceApi, PublicMenu_d_NestedMenuItemContents as NestedMenuItemContents, PublicMenu_d_NestedMenuItemSpec as NestedMenuItemSpec, PublicMenu_d_NestedMenuItemInstanceApi as NestedMenuItemInstanceApi, PublicMenu_d_FancyMenuItemSpec as FancyMenuItemSpec, PublicMenu_d_ColorSwatchMenuItemSpec as ColorSwatchMenuItemSpec, PublicMenu_d_InsertTableMenuItemSpec as InsertTableMenuItemSpec, PublicMenu_d_ToggleMenuItemSpec as ToggleMenuItemSpec, PublicMenu_d_ToggleMenuItemInstanceApi as ToggleMenuItemInstanceApi, PublicMenu_d_ChoiceMenuItemSpec as ChoiceMenuItemSpec, PublicMenu_d_ChoiceMenuItemInstanceApi as ChoiceMenuItemInstanceApi, PublicMenu_d_SeparatorMenuItemSpec as SeparatorMenuItemSpec, PublicMenu_d_ContextMenuApi as ContextMenuApi, PublicMenu_d_ContextMenuContents as ContextMenuContents, PublicMenu_d_ContextMenuItem as ContextMenuItem, PublicMenu_d_ContextSubMenu as ContextSubMenu, PublicMenu_d_CardMenuItemSpec as CardMenuItemSpec, PublicMenu_d_CardMenuItemInstanceApi as CardMenuItemInstanceApi, PublicMenu_d_CardItemSpec as CardItemSpec, PublicMenu_d_CardContainerSpec as CardContainerSpec, PublicMenu_d_CardImageSpec as CardImageSpec, PublicMenu_d_CardTextSpec as CardTextSpec, }; +} +interface SidebarInstanceApi { + element: () => HTMLElement; +} +interface SidebarSpec { + icon?: string; + tooltip?: string; + onShow?: (api: SidebarInstanceApi) => void; + onSetup?: (api: SidebarInstanceApi) => (api: SidebarInstanceApi) => void; + onHide?: (api: SidebarInstanceApi) => void; +} +type PublicSidebar_d_SidebarSpec = SidebarSpec; +type PublicSidebar_d_SidebarInstanceApi = SidebarInstanceApi; +declare namespace PublicSidebar_d { + export { PublicSidebar_d_SidebarSpec as SidebarSpec, PublicSidebar_d_SidebarInstanceApi as SidebarInstanceApi, }; +} +type PublicToolbar_d_ToolbarButtonSpec = ToolbarButtonSpec; +type PublicToolbar_d_ToolbarButtonInstanceApi = ToolbarButtonInstanceApi; +type PublicToolbar_d_ToolbarSplitButtonSpec = ToolbarSplitButtonSpec; +type PublicToolbar_d_ToolbarSplitButtonInstanceApi = ToolbarSplitButtonInstanceApi; +type PublicToolbar_d_ToolbarMenuButtonSpec = ToolbarMenuButtonSpec; +type PublicToolbar_d_ToolbarMenuButtonInstanceApi = ToolbarMenuButtonInstanceApi; +type PublicToolbar_d_ToolbarToggleButtonSpec = ToolbarToggleButtonSpec; +type PublicToolbar_d_ToolbarToggleButtonInstanceApi = ToolbarToggleButtonInstanceApi; +type PublicToolbar_d_GroupToolbarButtonSpec = GroupToolbarButtonSpec; +type PublicToolbar_d_GroupToolbarButtonInstanceApi = GroupToolbarButtonInstanceApi; +declare namespace PublicToolbar_d { + export { PublicToolbar_d_ToolbarButtonSpec as ToolbarButtonSpec, PublicToolbar_d_ToolbarButtonInstanceApi as ToolbarButtonInstanceApi, PublicToolbar_d_ToolbarSplitButtonSpec as ToolbarSplitButtonSpec, PublicToolbar_d_ToolbarSplitButtonInstanceApi as ToolbarSplitButtonInstanceApi, PublicToolbar_d_ToolbarMenuButtonSpec as ToolbarMenuButtonSpec, PublicToolbar_d_ToolbarMenuButtonInstanceApi as ToolbarMenuButtonInstanceApi, PublicToolbar_d_ToolbarToggleButtonSpec as ToolbarToggleButtonSpec, PublicToolbar_d_ToolbarToggleButtonInstanceApi as ToolbarToggleButtonInstanceApi, PublicToolbar_d_GroupToolbarButtonSpec as GroupToolbarButtonSpec, PublicToolbar_d_GroupToolbarButtonInstanceApi as GroupToolbarButtonInstanceApi, }; +} +interface ViewButtonApi { + setIcon: (newIcon: string) => void; +} +interface ViewToggleButtonApi extends ViewButtonApi { + isActive: () => boolean; + setActive: (state: boolean) => void; +} +interface BaseButtonSpec { + text?: string; + icon?: string; + tooltip?: string; + buttonType?: 'primary' | 'secondary'; + borderless?: boolean; + onAction: (api: Api) => void; +} +interface ViewNormalButtonSpec extends BaseButtonSpec { + text: string; + type: 'button'; +} +interface ViewToggleButtonSpec extends BaseButtonSpec { + type: 'togglebutton'; + active?: boolean; + onAction: (api: ViewToggleButtonApi) => void; +} +interface ViewButtonsGroupSpec { + type: 'group'; + buttons: Array; +} +type ViewButtonSpec = ViewNormalButtonSpec | ViewToggleButtonSpec | ViewButtonsGroupSpec; +interface ViewInstanceApi { + getContainer: () => HTMLElement; +} +interface ViewSpec { + buttons?: ViewButtonSpec[]; + onShow: (api: ViewInstanceApi) => void; + onHide: (api: ViewInstanceApi) => void; +} +type PublicView_d_ViewSpec = ViewSpec; +type PublicView_d_ViewInstanceApi = ViewInstanceApi; +declare namespace PublicView_d { + export { PublicView_d_ViewSpec as ViewSpec, PublicView_d_ViewInstanceApi as ViewInstanceApi, }; +} +interface Registry$1 { + addButton: (name: string, spec: ToolbarButtonSpec) => void; + addGroupToolbarButton: (name: string, spec: GroupToolbarButtonSpec) => void; + addToggleButton: (name: string, spec: ToolbarToggleButtonSpec) => void; + addMenuButton: (name: string, spec: ToolbarMenuButtonSpec) => void; + addSplitButton: (name: string, spec: ToolbarSplitButtonSpec) => void; + addMenuItem: (name: string, spec: MenuItemSpec) => void; + addNestedMenuItem: (name: string, spec: NestedMenuItemSpec) => void; + addToggleMenuItem: (name: string, spec: ToggleMenuItemSpec) => void; + addContextMenu: (name: string, spec: ContextMenuApi) => void; + addContextToolbar: (name: string, spec: ContextToolbarSpec) => void; + addContextForm: (name: string, spec: ContextFormSpec) => void; + addIcon: (name: string, svgData: string) => void; + addAutocompleter: (name: string, spec: AutocompleterSpec) => void; + addSidebar: (name: string, spec: SidebarSpec) => void; + addView: (name: string, spec: ViewSpec) => void; + getAll: () => { + buttons: Record; + menuItems: Record; + popups: Record; + contextMenus: Record; + contextToolbars: Record; + icons: Record; + sidebars: Record; + views: Record; + }; +} +interface AutocompleteLookupData { + readonly matchText: string; + readonly items: AutocompleterContents[]; + readonly columns: ColumnTypes; + readonly onAction: (autoApi: AutocompleterInstanceApi, rng: Range, value: string, meta: Record) => void; + readonly highlightOn: string[]; +} +interface AutocompleterEventArgs { + readonly lookupData: AutocompleteLookupData[]; +} +interface RangeLikeObject { + startContainer: Node; + startOffset: number; + endContainer: Node; + endOffset: number; +} +type ApplyFormat = BlockFormat | InlineFormat | SelectorFormat; +type RemoveFormat = RemoveBlockFormat | RemoveInlineFormat | RemoveSelectorFormat; +type Format = ApplyFormat | RemoveFormat; +type Formats = Record; +type FormatAttrOrStyleValue = string | ((vars?: FormatVars) => string | null); +type FormatVars = Record; +interface BaseFormat { + ceFalseOverride?: boolean; + classes?: string | string[]; + collapsed?: boolean; + exact?: boolean; + expand?: boolean; + links?: boolean; + mixed?: boolean; + block_expand?: boolean; + onmatch?: (node: Element, fmt: T, itemName: string) => boolean; + remove?: 'none' | 'empty' | 'all'; + remove_similar?: boolean; + split?: boolean; + deep?: boolean; + preserve_attributes?: string[]; +} +interface Block { + block: string; + list_block?: string; + wrapper?: boolean; +} +interface Inline { + inline: string; +} +interface Selector { + selector: string; + inherit?: boolean; +} +interface CommonFormat extends BaseFormat { + attributes?: Record; + styles?: Record; + toggle?: boolean; + preview?: string | false; + onformat?: (elm: Element, fmt: T, vars?: FormatVars, node?: Node | RangeLikeObject | null) => void; + clear_child_styles?: boolean; + merge_siblings?: boolean; + merge_with_parents?: boolean; +} +interface BlockFormat extends Block, CommonFormat { +} +interface InlineFormat extends Inline, CommonFormat { +} +interface SelectorFormat extends Selector, CommonFormat { +} +interface CommonRemoveFormat extends BaseFormat { + attributes?: string[] | Record; + styles?: string[] | Record; +} +interface RemoveBlockFormat extends Block, CommonRemoveFormat { +} +interface RemoveInlineFormat extends Inline, CommonRemoveFormat { +} +interface RemoveSelectorFormat extends Selector, CommonRemoveFormat { +} +interface Filter { + name: string; + callbacks: C[]; +} +interface ParserArgs { + getInner?: boolean | number; + forced_root_block?: boolean | string; + context?: string; + isRootContent?: boolean; + format?: string; + invalid?: boolean; + no_events?: boolean; + [key: string]: any; +} +type ParserFilterCallback = (nodes: AstNode[], name: string, args: ParserArgs) => void; +interface ParserFilter extends Filter { +} +interface DomParserSettings { + allow_html_data_urls?: boolean; + allow_svg_data_urls?: boolean; + allow_conditional_comments?: boolean; + allow_html_in_named_anchor?: boolean; + allow_script_urls?: boolean; + allow_unsafe_link_target?: boolean; + blob_cache?: BlobCache; + convert_fonts_to_spans?: boolean; + convert_unsafe_embeds?: boolean; + document?: Document; + fix_list_elements?: boolean; + font_size_legacy_values?: string; + forced_root_block?: boolean | string; + forced_root_block_attrs?: Record; + inline_styles?: boolean; + pad_empty_with_br?: boolean; + preserve_cdata?: boolean; + root_name?: string; + sandbox_iframes?: boolean; + sandbox_iframes_exclusions?: string[]; + sanitize?: boolean; + validate?: boolean; +} +interface DomParser { + schema: Schema; + addAttributeFilter: (name: string, callback: ParserFilterCallback) => void; + getAttributeFilters: () => ParserFilter[]; + removeAttributeFilter: (name: string, callback?: ParserFilterCallback) => void; + addNodeFilter: (name: string, callback: ParserFilterCallback) => void; + getNodeFilters: () => ParserFilter[]; + removeNodeFilter: (name: string, callback?: ParserFilterCallback) => void; + parse: (html: string, args?: ParserArgs) => AstNode; +} +interface StyleSheetLoaderSettings { + maxLoadTime?: number; + contentCssCors?: boolean; + referrerPolicy?: ReferrerPolicy; +} +interface StyleSheetLoader { + load: (url: string) => Promise; + loadRawCss: (key: string, css: string) => void; + loadAll: (urls: string[]) => Promise; + unload: (url: string) => void; + unloadRawCss: (key: string) => void; + unloadAll: (urls: string[]) => void; + _setReferrerPolicy: (referrerPolicy: ReferrerPolicy) => void; + _setContentCssCors: (contentCssCors: boolean) => void; +} +type Registry = Registry$1; +interface EditorUiApi { + show: () => void; + hide: () => void; + setEnabled: (state: boolean) => void; + isEnabled: () => boolean; +} +interface EditorUi extends EditorUiApi { + registry: Registry; + styleSheetLoader: StyleSheetLoader; +} +type Ui_d_Registry = Registry; +type Ui_d_EditorUiApi = EditorUiApi; +type Ui_d_EditorUi = EditorUi; +declare namespace Ui_d { + export { Ui_d_Registry as Registry, PublicDialog_d as Dialog, PublicInlineContent_d as InlineContent, PublicMenu_d as Menu, PublicView_d as View, PublicSidebar_d as Sidebar, PublicToolbar_d as Toolbar, Ui_d_EditorUiApi as EditorUiApi, Ui_d_EditorUi as EditorUi, }; +} +interface WindowParams { + readonly inline?: 'cursor' | 'toolbar' | 'bottom'; + readonly ariaAttrs?: boolean; + readonly persistent?: boolean; +} +type InstanceApi = UrlDialogInstanceApi | DialogInstanceApi; +interface WindowManagerImpl { + open: (config: DialogSpec, params: WindowParams | undefined, closeWindow: (dialog: DialogInstanceApi) => void) => DialogInstanceApi; + openUrl: (config: UrlDialogSpec, closeWindow: (dialog: UrlDialogInstanceApi) => void) => UrlDialogInstanceApi; + alert: (message: string, callback: () => void) => void; + confirm: (message: string, callback: (state: boolean) => void) => void; + close: (dialog: InstanceApi) => void; +} +interface WindowManager { + open: (config: DialogSpec, params?: WindowParams) => DialogInstanceApi; + openUrl: (config: UrlDialogSpec) => UrlDialogInstanceApi; + alert: (message: string, callback?: () => void, scope?: any) => void; + confirm: (message: string, callback?: (state: boolean) => void, scope?: any) => void; + close: () => void; +} +interface ExecCommandEvent { + command: string; + ui: boolean; + value?: any; +} +interface BeforeGetContentEvent extends GetContentArgs { + selection?: boolean; +} +interface GetContentEvent extends BeforeGetContentEvent { + content: string; +} +interface BeforeSetContentEvent extends SetContentArgs { + content: string; + selection?: boolean; +} +interface SetContentEvent extends BeforeSetContentEvent { + content: string; +} +interface SaveContentEvent extends GetContentEvent { + save: boolean; +} +interface NewBlockEvent { + newBlock: Element; +} +interface NodeChangeEvent { + element: Element; + parents: Node[]; + selectionChange?: boolean; + initial?: boolean; +} +interface FormatEvent { + format: string; + vars?: FormatVars; + node?: Node | RangeLikeObject | null; +} +interface ObjectResizeEvent { + target: HTMLElement; + width: number; + height: number; + origin: string; +} +interface ObjectSelectedEvent { + target: Node; + targetClone?: Node; +} +interface ScrollIntoViewEvent { + elm: HTMLElement; + alignToTop: boolean | undefined; +} +interface SetSelectionRangeEvent { + range: Range; + forward: boolean | undefined; +} +interface ShowCaretEvent { + target: Node; + direction: number; + before: boolean; +} +interface SwitchModeEvent { + mode: string; +} +interface ChangeEvent { + level: UndoLevel; + lastLevel: UndoLevel | undefined; +} +interface AddUndoEvent extends ChangeEvent { + originalEvent: Event | undefined; +} +interface UndoRedoEvent { + level: UndoLevel; +} +interface WindowEvent { + dialog: InstanceApi; +} +interface ProgressStateEvent { + state: boolean; + time?: number; +} +interface AfterProgressStateEvent { + state: boolean; +} +interface PlaceholderToggleEvent { + state: boolean; +} +interface LoadErrorEvent { + message: string; +} +interface PreProcessEvent extends ParserArgs { + node: Element; +} +interface PostProcessEvent extends ParserArgs { + content: string; +} +interface PastePlainTextToggleEvent { + state: boolean; +} +interface PastePreProcessEvent { + content: string; + readonly internal: boolean; +} +interface PastePostProcessEvent { + node: HTMLElement; + readonly internal: boolean; +} +interface EditableRootStateChangeEvent { + state: boolean; +} +interface NewTableRowEvent { + node: HTMLTableRowElement; +} +interface NewTableCellEvent { + node: HTMLTableCellElement; +} +interface TableEventData { + readonly structure: boolean; + readonly style: boolean; +} +interface TableModifiedEvent extends TableEventData { + readonly table: HTMLTableElement; +} +interface BeforeOpenNotificationEvent { + notification: NotificationSpec; +} +interface OpenNotificationEvent { + notification: NotificationApi; +} +interface EditorEventMap extends Omit { + 'activate': { + relatedTarget: Editor | null; + }; + 'deactivate': { + relatedTarget: Editor; + }; + 'focus': { + blurredEditor: Editor | null; + }; + 'blur': { + focusedEditor: Editor | null; + }; + 'resize': UIEvent; + 'scroll': UIEvent; + 'input': InputEvent; + 'beforeinput': InputEvent; + 'detach': {}; + 'remove': {}; + 'init': {}; + 'ScrollIntoView': ScrollIntoViewEvent; + 'AfterScrollIntoView': ScrollIntoViewEvent; + 'ObjectResized': ObjectResizeEvent; + 'ObjectResizeStart': ObjectResizeEvent; + 'SwitchMode': SwitchModeEvent; + 'ScrollWindow': Event; + 'ResizeWindow': UIEvent; + 'SkinLoaded': {}; + 'SkinLoadError': LoadErrorEvent; + 'PluginLoadError': LoadErrorEvent; + 'ModelLoadError': LoadErrorEvent; + 'IconsLoadError': LoadErrorEvent; + 'ThemeLoadError': LoadErrorEvent; + 'LanguageLoadError': LoadErrorEvent; + 'BeforeExecCommand': ExecCommandEvent; + 'ExecCommand': ExecCommandEvent; + 'NodeChange': NodeChangeEvent; + 'FormatApply': FormatEvent; + 'FormatRemove': FormatEvent; + 'ShowCaret': ShowCaretEvent; + 'SelectionChange': {}; + 'ObjectSelected': ObjectSelectedEvent; + 'BeforeObjectSelected': ObjectSelectedEvent; + 'GetSelectionRange': { + range: Range; + }; + 'SetSelectionRange': SetSelectionRangeEvent; + 'AfterSetSelectionRange': SetSelectionRangeEvent; + 'BeforeGetContent': BeforeGetContentEvent; + 'GetContent': GetContentEvent; + 'BeforeSetContent': BeforeSetContentEvent; + 'SetContent': SetContentEvent; + 'SaveContent': SaveContentEvent; + 'RawSaveContent': SaveContentEvent; + 'LoadContent': { + load: boolean; + element: HTMLElement; + }; + 'PreviewFormats': {}; + 'AfterPreviewFormats': {}; + 'ScriptsLoaded': {}; + 'PreInit': {}; + 'PostRender': {}; + 'NewBlock': NewBlockEvent; + 'ClearUndos': {}; + 'TypingUndo': {}; + 'Redo': UndoRedoEvent; + 'Undo': UndoRedoEvent; + 'BeforeAddUndo': AddUndoEvent; + 'AddUndo': AddUndoEvent; + 'change': ChangeEvent; + 'CloseWindow': WindowEvent; + 'OpenWindow': WindowEvent; + 'ProgressState': ProgressStateEvent; + 'AfterProgressState': AfterProgressStateEvent; + 'PlaceholderToggle': PlaceholderToggleEvent; + 'tap': TouchEvent; + 'longpress': TouchEvent; + 'longpresscancel': {}; + 'PreProcess': PreProcessEvent; + 'PostProcess': PostProcessEvent; + 'AutocompleterStart': AutocompleterEventArgs; + 'AutocompleterUpdate': AutocompleterEventArgs; + 'AutocompleterEnd': {}; + 'PastePlainTextToggle': PastePlainTextToggleEvent; + 'PastePreProcess': PastePreProcessEvent; + 'PastePostProcess': PastePostProcessEvent; + 'TableModified': TableModifiedEvent; + 'NewRow': NewTableRowEvent; + 'NewCell': NewTableCellEvent; + 'SetAttrib': SetAttribEvent; + 'hide': {}; + 'show': {}; + 'dirty': {}; + 'BeforeOpenNotification': BeforeOpenNotificationEvent; + 'OpenNotification': OpenNotificationEvent; +} +interface EditorManagerEventMap { + 'AddEditor': { + editor: Editor; + }; + 'RemoveEditor': { + editor: Editor; + }; + 'BeforeUnload': { + returnValue: any; + }; +} +type EventTypes_d_ExecCommandEvent = ExecCommandEvent; +type EventTypes_d_BeforeGetContentEvent = BeforeGetContentEvent; +type EventTypes_d_GetContentEvent = GetContentEvent; +type EventTypes_d_BeforeSetContentEvent = BeforeSetContentEvent; +type EventTypes_d_SetContentEvent = SetContentEvent; +type EventTypes_d_SaveContentEvent = SaveContentEvent; +type EventTypes_d_NewBlockEvent = NewBlockEvent; +type EventTypes_d_NodeChangeEvent = NodeChangeEvent; +type EventTypes_d_FormatEvent = FormatEvent; +type EventTypes_d_ObjectResizeEvent = ObjectResizeEvent; +type EventTypes_d_ObjectSelectedEvent = ObjectSelectedEvent; +type EventTypes_d_ScrollIntoViewEvent = ScrollIntoViewEvent; +type EventTypes_d_SetSelectionRangeEvent = SetSelectionRangeEvent; +type EventTypes_d_ShowCaretEvent = ShowCaretEvent; +type EventTypes_d_SwitchModeEvent = SwitchModeEvent; +type EventTypes_d_ChangeEvent = ChangeEvent; +type EventTypes_d_AddUndoEvent = AddUndoEvent; +type EventTypes_d_UndoRedoEvent = UndoRedoEvent; +type EventTypes_d_WindowEvent = WindowEvent; +type EventTypes_d_ProgressStateEvent = ProgressStateEvent; +type EventTypes_d_AfterProgressStateEvent = AfterProgressStateEvent; +type EventTypes_d_PlaceholderToggleEvent = PlaceholderToggleEvent; +type EventTypes_d_LoadErrorEvent = LoadErrorEvent; +type EventTypes_d_PreProcessEvent = PreProcessEvent; +type EventTypes_d_PostProcessEvent = PostProcessEvent; +type EventTypes_d_PastePlainTextToggleEvent = PastePlainTextToggleEvent; +type EventTypes_d_PastePreProcessEvent = PastePreProcessEvent; +type EventTypes_d_PastePostProcessEvent = PastePostProcessEvent; +type EventTypes_d_EditableRootStateChangeEvent = EditableRootStateChangeEvent; +type EventTypes_d_NewTableRowEvent = NewTableRowEvent; +type EventTypes_d_NewTableCellEvent = NewTableCellEvent; +type EventTypes_d_TableEventData = TableEventData; +type EventTypes_d_TableModifiedEvent = TableModifiedEvent; +type EventTypes_d_BeforeOpenNotificationEvent = BeforeOpenNotificationEvent; +type EventTypes_d_OpenNotificationEvent = OpenNotificationEvent; +type EventTypes_d_EditorEventMap = EditorEventMap; +type EventTypes_d_EditorManagerEventMap = EditorManagerEventMap; +declare namespace EventTypes_d { + export { EventTypes_d_ExecCommandEvent as ExecCommandEvent, EventTypes_d_BeforeGetContentEvent as BeforeGetContentEvent, EventTypes_d_GetContentEvent as GetContentEvent, EventTypes_d_BeforeSetContentEvent as BeforeSetContentEvent, EventTypes_d_SetContentEvent as SetContentEvent, EventTypes_d_SaveContentEvent as SaveContentEvent, EventTypes_d_NewBlockEvent as NewBlockEvent, EventTypes_d_NodeChangeEvent as NodeChangeEvent, EventTypes_d_FormatEvent as FormatEvent, EventTypes_d_ObjectResizeEvent as ObjectResizeEvent, EventTypes_d_ObjectSelectedEvent as ObjectSelectedEvent, EventTypes_d_ScrollIntoViewEvent as ScrollIntoViewEvent, EventTypes_d_SetSelectionRangeEvent as SetSelectionRangeEvent, EventTypes_d_ShowCaretEvent as ShowCaretEvent, EventTypes_d_SwitchModeEvent as SwitchModeEvent, EventTypes_d_ChangeEvent as ChangeEvent, EventTypes_d_AddUndoEvent as AddUndoEvent, EventTypes_d_UndoRedoEvent as UndoRedoEvent, EventTypes_d_WindowEvent as WindowEvent, EventTypes_d_ProgressStateEvent as ProgressStateEvent, EventTypes_d_AfterProgressStateEvent as AfterProgressStateEvent, EventTypes_d_PlaceholderToggleEvent as PlaceholderToggleEvent, EventTypes_d_LoadErrorEvent as LoadErrorEvent, EventTypes_d_PreProcessEvent as PreProcessEvent, EventTypes_d_PostProcessEvent as PostProcessEvent, EventTypes_d_PastePlainTextToggleEvent as PastePlainTextToggleEvent, EventTypes_d_PastePreProcessEvent as PastePreProcessEvent, EventTypes_d_PastePostProcessEvent as PastePostProcessEvent, EventTypes_d_EditableRootStateChangeEvent as EditableRootStateChangeEvent, EventTypes_d_NewTableRowEvent as NewTableRowEvent, EventTypes_d_NewTableCellEvent as NewTableCellEvent, EventTypes_d_TableEventData as TableEventData, EventTypes_d_TableModifiedEvent as TableModifiedEvent, EventTypes_d_BeforeOpenNotificationEvent as BeforeOpenNotificationEvent, EventTypes_d_OpenNotificationEvent as OpenNotificationEvent, EventTypes_d_EditorEventMap as EditorEventMap, EventTypes_d_EditorManagerEventMap as EditorManagerEventMap, }; +} +type Format_d_Formats = Formats; +type Format_d_Format = Format; +type Format_d_ApplyFormat = ApplyFormat; +type Format_d_BlockFormat = BlockFormat; +type Format_d_InlineFormat = InlineFormat; +type Format_d_SelectorFormat = SelectorFormat; +type Format_d_RemoveFormat = RemoveFormat; +type Format_d_RemoveBlockFormat = RemoveBlockFormat; +type Format_d_RemoveInlineFormat = RemoveInlineFormat; +type Format_d_RemoveSelectorFormat = RemoveSelectorFormat; +declare namespace Format_d { + export { Format_d_Formats as Formats, Format_d_Format as Format, Format_d_ApplyFormat as ApplyFormat, Format_d_BlockFormat as BlockFormat, Format_d_InlineFormat as InlineFormat, Format_d_SelectorFormat as SelectorFormat, Format_d_RemoveFormat as RemoveFormat, Format_d_RemoveBlockFormat as RemoveBlockFormat, Format_d_RemoveInlineFormat as RemoveInlineFormat, Format_d_RemoveSelectorFormat as RemoveSelectorFormat, }; +} +type StyleFormat = BlockStyleFormat | InlineStyleFormat | SelectorStyleFormat; +type AllowedFormat = Separator | FormatReference | StyleFormat | NestedFormatting; +interface Separator { + title: string; +} +interface FormatReference { + title: string; + format: string; + icon?: string; +} +interface NestedFormatting { + title: string; + items: Array; +} +interface CommonStyleFormat { + name?: string; + title: string; + icon?: string; +} +interface BlockStyleFormat extends BlockFormat, CommonStyleFormat { +} +interface InlineStyleFormat extends InlineFormat, CommonStyleFormat { +} +interface SelectorStyleFormat extends SelectorFormat, CommonStyleFormat { +} +type EntityEncoding = 'named' | 'numeric' | 'raw' | 'named,numeric' | 'named+numeric' | 'numeric,named' | 'numeric+named'; +interface ContentLanguage { + readonly title: string; + readonly code: string; + readonly customCode?: string; +} +type ThemeInitFunc = (editor: Editor, elm: HTMLElement) => { + editorContainer: HTMLElement; + iframeContainer: HTMLElement; + height?: number; + iframeHeight?: number; + api?: EditorUiApi; +}; +type SetupCallback = (editor: Editor) => void; +type FilePickerCallback = (callback: (value: string, meta?: Record) => void, value: string, meta: Record) => void; +type FilePickerValidationStatus = 'valid' | 'unknown' | 'invalid' | 'none'; +type FilePickerValidationCallback = (info: { + type: string; + url: string; +}, callback: (validation: { + status: FilePickerValidationStatus; + message: string; +}) => void) => void; +type PastePreProcessFn = (editor: Editor, args: PastePreProcessEvent) => void; +type PastePostProcessFn = (editor: Editor, args: PastePostProcessEvent) => void; +type URLConverter = (url: string, name: string, elm?: string | Element) => string; +type URLConverterCallback = (url: string, node: Node | string | undefined, on_save: boolean, name: string) => string; +interface ToolbarGroup { + name?: string; + items: string[]; +} +type ToolbarMode = 'floating' | 'sliding' | 'scrolling' | 'wrap'; +type ToolbarLocation = 'top' | 'bottom' | 'auto'; +interface BaseEditorOptions { + a11y_advanced_options?: boolean; + add_form_submit_trigger?: boolean; + add_unload_trigger?: boolean; + allow_conditional_comments?: boolean; + allow_html_data_urls?: boolean; + allow_html_in_named_anchor?: boolean; + allow_script_urls?: boolean; + allow_svg_data_urls?: boolean; + allow_unsafe_link_target?: boolean; + anchor_bottom?: false | string; + anchor_top?: false | string; + auto_focus?: string | true; + automatic_uploads?: boolean; + base_url?: string; + block_formats?: string; + block_unsupported_drop?: boolean; + body_id?: string; + body_class?: string; + br_in_pre?: boolean; + br_newline_selector?: string; + browser_spellcheck?: boolean; + branding?: boolean; + cache_suffix?: string; + color_cols?: number; + color_cols_foreground?: number; + color_cols_background?: number; + color_map?: string[]; + color_map_foreground?: string[]; + color_map_background?: string[]; + color_default_foreground?: string; + color_default_background?: string; + content_css?: boolean | string | string[]; + content_css_cors?: boolean; + content_security_policy?: string; + content_style?: string; + content_langs?: ContentLanguage[]; + contextmenu?: string | string[] | false; + contextmenu_never_use_native?: boolean; + convert_fonts_to_spans?: boolean; + convert_unsafe_embeds?: boolean; + convert_urls?: boolean; + custom_colors?: boolean; + custom_elements?: string | Record; + custom_ui_selector?: string; + custom_undo_redo_levels?: number; + default_font_stack?: string[]; + deprecation_warnings?: boolean; + directionality?: 'ltr' | 'rtl'; + doctype?: string; + document_base_url?: string; + draggable_modal?: boolean; + editable_class?: string; + editable_root?: boolean; + element_format?: 'xhtml' | 'html'; + elementpath?: boolean; + encoding?: string; + end_container_on_empty_block?: boolean | string; + entities?: string; + entity_encoding?: EntityEncoding; + extended_valid_elements?: string; + event_root?: string; + file_picker_callback?: FilePickerCallback; + file_picker_types?: string; + file_picker_validator_handler?: FilePickerValidationCallback; + fix_list_elements?: boolean; + fixed_toolbar_container?: string; + fixed_toolbar_container_target?: HTMLElement; + font_css?: string | string[]; + font_family_formats?: string; + font_size_classes?: string; + font_size_legacy_values?: string; + font_size_style_values?: string; + font_size_formats?: string; + font_size_input_default_unit?: string; + forced_root_block?: string; + forced_root_block_attrs?: Record; + formats?: Formats; + format_noneditable_selector?: string; + height?: number | string; + help_accessibility?: boolean; + hidden_input?: boolean; + highlight_on_focus?: boolean; + icons?: string; + icons_url?: string; + id?: string; + iframe_aria_text?: string; + iframe_attrs?: Record; + images_file_types?: string; + images_replace_blob_uris?: boolean; + images_reuse_filename?: boolean; + images_upload_base_path?: string; + images_upload_credentials?: boolean; + images_upload_handler?: UploadHandler; + images_upload_url?: string; + indent?: boolean; + indent_after?: string; + indent_before?: string; + indent_use_margin?: boolean; + indentation?: string; + init_instance_callback?: SetupCallback; + inline?: boolean; + inline_boundaries?: boolean; + inline_boundaries_selector?: string; + inline_styles?: boolean; + invalid_elements?: string; + invalid_styles?: string | Record; + keep_styles?: boolean; + language?: string; + language_load?: boolean; + language_url?: string; + line_height_formats?: string; + max_height?: number; + max_width?: number; + menu?: Record; + menubar?: boolean | string; + min_height?: number; + min_width?: number; + model?: string; + model_url?: string; + newdocument_content?: string; + newline_behavior?: 'block' | 'linebreak' | 'invert' | 'default'; + no_newline_selector?: string; + noneditable_class?: string; + noneditable_regexp?: RegExp | RegExp[]; + nowrap?: boolean; + object_resizing?: boolean | string; + pad_empty_with_br?: boolean; + paste_as_text?: boolean; + paste_block_drop?: boolean; + paste_data_images?: boolean; + paste_merge_formats?: boolean; + paste_postprocess?: PastePostProcessFn; + paste_preprocess?: PastePreProcessFn; + paste_remove_styles_if_webkit?: boolean; + paste_tab_spaces?: number; + paste_webkit_styles?: string; + placeholder?: string; + preserve_cdata?: boolean; + preview_styles?: false | string; + promotion?: boolean; + protect?: RegExp[]; + readonly?: boolean; + referrer_policy?: ReferrerPolicy; + relative_urls?: boolean; + remove_script_host?: boolean; + remove_trailing_brs?: boolean; + removed_menuitems?: string; + resize?: boolean | 'both'; + resize_img_proportional?: boolean; + root_name?: string; + sandbox_iframes?: boolean; + sandbox_iframes_exclusions?: string[]; + schema?: SchemaType; + selector?: string; + setup?: SetupCallback; + sidebar_show?: string; + skin?: boolean | string; + skin_url?: string; + smart_paste?: boolean; + statusbar?: boolean; + style_formats?: AllowedFormat[]; + style_formats_autohide?: boolean; + style_formats_merge?: boolean; + submit_patch?: boolean; + suffix?: string; + table_tab_navigation?: boolean; + target?: HTMLElement; + text_patterns?: RawPattern[] | false; + text_patterns_lookup?: RawDynamicPatternsLookup; + theme?: string | ThemeInitFunc | false; + theme_url?: string; + toolbar?: boolean | string | string[] | Array; + toolbar1?: string; + toolbar2?: string; + toolbar3?: string; + toolbar4?: string; + toolbar5?: string; + toolbar6?: string; + toolbar7?: string; + toolbar8?: string; + toolbar9?: string; + toolbar_groups?: Record; + toolbar_location?: ToolbarLocation; + toolbar_mode?: ToolbarMode; + toolbar_sticky?: boolean; + toolbar_sticky_offset?: number; + typeahead_urls?: boolean; + ui_mode?: 'combined' | 'split'; + url_converter?: URLConverter; + url_converter_scope?: any; + urlconverter_callback?: URLConverterCallback; + valid_children?: string; + valid_classes?: string | Record; + valid_elements?: string; + valid_styles?: string | Record; + verify_html?: boolean; + visual?: boolean; + visual_anchor_class?: string; + visual_table_class?: string; + width?: number | string; + xss_sanitization?: boolean; + license_key?: string; + disable_nodechange?: boolean; + forced_plugins?: string | string[]; + plugin_base_urls?: Record; + service_message?: string; + [key: string]: any; +} +interface RawEditorOptions extends BaseEditorOptions { + external_plugins?: Record; + mobile?: RawEditorOptions; + plugins?: string | string[]; +} +interface NormalizedEditorOptions extends BaseEditorOptions { + external_plugins: Record; + forced_plugins: string[]; + plugins: string[]; +} +interface EditorOptions extends NormalizedEditorOptions { + a11y_advanced_options: boolean; + allow_unsafe_link_target: boolean; + anchor_bottom: string; + anchor_top: string; + automatic_uploads: boolean; + block_formats: string; + body_class: string; + body_id: string; + br_newline_selector: string; + color_map: string[]; + color_cols: number; + color_cols_foreground: number; + color_cols_background: number; + color_default_background: string; + color_default_foreground: string; + content_css: string[]; + contextmenu: string[]; + convert_unsafe_embeds: boolean; + custom_colors: boolean; + default_font_stack: string[]; + document_base_url: string; + init_content_sync: boolean; + draggable_modal: boolean; + editable_class: string; + editable_root: boolean; + font_css: string[]; + font_family_formats: string; + font_size_classes: string; + font_size_formats: string; + font_size_input_default_unit: string; + font_size_legacy_values: string; + font_size_style_values: string; + forced_root_block: string; + forced_root_block_attrs: Record; + format_noneditable_selector: string; + height: number | string; + highlight_on_focus: boolean; + iframe_attrs: Record; + images_file_types: string; + images_upload_base_path: string; + images_upload_credentials: boolean; + images_upload_url: string; + indent_use_margin: boolean; + indentation: string; + inline: boolean; + inline_boundaries_selector: string; + language: string; + language_load: boolean; + language_url: string; + line_height_formats: string; + menu: Record; + menubar: boolean | string; + model: string; + newdocument_content: string; + no_newline_selector: string; + noneditable_class: string; + noneditable_regexp: RegExp[]; + object_resizing: string; + pad_empty_with_br: boolean; + paste_as_text: boolean; + preview_styles: string; + promotion: boolean; + readonly: boolean; + removed_menuitems: string; + sandbox_iframes: boolean; + sandbox_iframes_exclusions: string[]; + toolbar: boolean | string | string[] | Array; + toolbar_groups: Record; + toolbar_location: ToolbarLocation; + toolbar_mode: ToolbarMode; + toolbar_persist: boolean; + toolbar_sticky: boolean; + toolbar_sticky_offset: number; + text_patterns: Pattern[]; + text_patterns_lookup: DynamicPatternsLookup; + visual: boolean; + visual_anchor_class: string; + visual_table_class: string; + width: number | string; + xss_sanitization: boolean; +} +type StyleMap = Record; +interface StylesSettings { + allow_script_urls?: boolean; + allow_svg_data_urls?: boolean; + url_converter?: URLConverter; + url_converter_scope?: any; +} +interface Styles { + parse: (css: string | undefined) => Record; + serialize: (styles: StyleMap, elementName?: string) => string; +} +type EventUtilsCallback = (event: EventUtilsEvent) => void | boolean; +type EventUtilsEvent = NormalizedEvent & { + metaKey: boolean; +}; +interface Callback$1 { + func: EventUtilsCallback; + scope: any; +} +interface CallbackList extends Array> { + fakeName: string | false; + capture: boolean; + nativeHandler: EventListener; +} +interface EventUtilsConstructor { + readonly prototype: EventUtils; + new (): EventUtils; + Event: EventUtils; +} +declare class EventUtils { + static Event: EventUtils; + domLoaded: boolean; + events: Record>>; + private readonly expando; + private hasFocusIn; + private count; + constructor(); + bind(target: any, name: K, callback: EventUtilsCallback, scope?: any): EventUtilsCallback; + bind(target: any, names: string, callback: EventUtilsCallback, scope?: any): EventUtilsCallback; + unbind(target: any, name: K, callback?: EventUtilsCallback): this; + unbind(target: any, names: string, callback?: EventUtilsCallback): this; + unbind(target: any): this; + fire(target: any, name: string, args?: {}): this; + dispatch(target: any, name: string, args?: {}): this; + clean(target: any): this; + destroy(): void; + cancel(e: EventUtilsEvent): boolean; + private executeHandlers; +} +interface SetAttribEvent { + attrElm: HTMLElement; + attrName: string; + attrValue: string | boolean | number | null; +} +interface DOMUtilsSettings { + schema: Schema; + url_converter: URLConverter; + url_converter_scope: any; + ownEvents: boolean; + keep_values: boolean; + update_styles: boolean; + root_element: HTMLElement | null; + collect: boolean; + onSetAttrib: (event: SetAttribEvent) => void; + contentCssCors: boolean; + referrerPolicy: ReferrerPolicy; +} +type Target = Node | Window; +type RunArguments = string | T | Array | null; +type BoundEvent = [ + Target, + string, + EventUtilsCallback, + any +]; +type Callback = EventUtilsCallback>; +type RunResult = T extends Array ? R[] : false | R; +interface DOMUtils { + doc: Document; + settings: Partial; + win: Window; + files: Record; + stdMode: boolean; + boxModel: boolean; + styleSheetLoader: StyleSheetLoader; + boundEvents: BoundEvent[]; + styles: Styles; + schema: Schema; + events: EventUtils; + root: Node | null; + isBlock: { + (node: Node | null): node is HTMLElement; + (node: string): boolean; + }; + clone: (node: Node, deep: boolean) => Node; + getRoot: () => HTMLElement; + getViewPort: (argWin?: Window) => GeomRect; + getRect: (elm: string | HTMLElement) => GeomRect; + getSize: (elm: string | HTMLElement) => { + w: number; + h: number; + }; + getParent: { + (node: string | Node | null, selector: K, root?: Node): HTMLElementTagNameMap[K] | null; + (node: string | Node | null, selector: string | ((node: Node) => node is T), root?: Node): T | null; + (node: string | Node | null, selector?: string | ((node: Node) => boolean | void), root?: Node): Node | null; + }; + getParents: { + (elm: string | HTMLElementTagNameMap[K] | null, selector: K, root?: Node, collect?: boolean): Array; + (node: string | Node | null, selector: string | ((node: Node) => node is T), root?: Node, collect?: boolean): T[]; + (elm: string | Node | null, selector?: string | ((node: Node) => boolean | void), root?: Node, collect?: boolean): Node[]; + }; + get: { + (elm: T): T; + (elm: string): HTMLElement | null; + }; + getNext: (node: Node | null, selector: string | ((node: Node) => boolean)) => Node | null; + getPrev: (node: Node | null, selector: string | ((node: Node) => boolean)) => Node | null; + select: { + (selector: K, scope?: string | Node): Array; + (selector: string, scope?: string | Node): T[]; + }; + is: { + (elm: Node | Node[] | null, selector: string): elm is T; + (elm: Node | Node[] | null, selector: string): boolean; + }; + add: (parentElm: RunArguments, name: string | Element, attrs?: Record, html?: string | Node | null, create?: boolean) => HTMLElement; + create: { + (name: K, attrs?: Record, html?: string | Node | null): HTMLElementTagNameMap[K]; + (name: string, attrs?: Record, html?: string | Node | null): HTMLElement; + }; + createHTML: (name: string, attrs?: Record, html?: string) => string; + createFragment: (html?: string) => DocumentFragment; + remove: { + (node: T | T[], keepChildren?: boolean): typeof node extends Array ? T[] : T; + (node: string, keepChildren?: boolean): T | false; + }; + getStyle: { + (elm: Element, name: string, computed: true): string; + (elm: string | Element | null, name: string, computed?: boolean): string | undefined; + }; + setStyle: (elm: string | Element | Element[], name: string, value: string | number | null) => void; + setStyles: (elm: string | Element | Element[], stylesArg: StyleMap) => void; + removeAllAttribs: (e: RunArguments) => void; + setAttrib: (elm: RunArguments, name: string, value: string | boolean | number | null) => void; + setAttribs: (elm: RunArguments, attrs: Record) => void; + getAttrib: (elm: string | Element | null, name: string, defaultVal?: string) => string; + getAttribs: (elm: string | Element) => NamedNodeMap | Attr[]; + getPos: (elm: string | Element, rootElm?: Node) => { + x: number; + y: number; + }; + parseStyle: (cssText: string) => Record; + serializeStyle: (stylesArg: StyleMap, name?: string) => string; + addStyle: (cssText: string) => void; + loadCSS: (url: string) => void; + hasClass: (elm: string | Element, cls: string) => boolean; + addClass: (elm: RunArguments, cls: string) => void; + removeClass: (elm: RunArguments, cls: string) => void; + toggleClass: (elm: RunArguments, cls: string, state?: boolean) => void; + show: (elm: string | Node | Node[]) => void; + hide: (elm: string | Node | Node[]) => void; + isHidden: (elm: string | Node) => boolean; + uniqueId: (prefix?: string) => string; + setHTML: (elm: RunArguments, html: string) => void; + getOuterHTML: (elm: string | Node) => string; + setOuterHTML: (elm: string | Node | Node[], html: string) => void; + decode: (text: string) => string; + encode: (text: string) => string; + insertAfter: { + (node: T | T[], reference: string | Node): T; + (node: RunArguments, reference: string | Node): RunResult; + }; + replace: { + (newElm: Node, oldElm: T | T[], keepChildren?: boolean): T; + (newElm: Node, oldElm: RunArguments, keepChildren?: boolean): false | T; + }; + rename: { + (elm: Element, name: K): HTMLElementTagNameMap[K]; + (elm: Element, name: string): Element; + }; + findCommonAncestor: (a: Node, b: Node) => Node | null; + run(this: DOMUtils, elm: T | T[], func: (node: T) => R, scope?: any): typeof elm extends Array ? R[] : R; + run(this: DOMUtils, elm: RunArguments, func: (node: T) => R, scope?: any): RunResult; + isEmpty: (node: Node, elements?: Record, options?: IsEmptyOptions) => boolean; + createRng: () => Range; + nodeIndex: (node: Node, normalized?: boolean) => number; + split: { + (parentElm: Node, splitElm: Node, replacementElm: T): T | undefined; + (parentElm: Node, splitElm: T): T | undefined; + }; + bind: { + (target: Target, name: K, func: Callback, scope?: any): Callback; + (target: Target[], name: K, func: Callback, scope?: any): Callback[]; + }; + unbind: { + (target: Target, name?: K, func?: EventUtilsCallback>): EventUtils; + (target: Target[], name?: K, func?: EventUtilsCallback>): EventUtils[]; + }; + fire: (target: Node | Window, name: string, evt?: {}) => EventUtils; + dispatch: (target: Node | Window, name: string, evt?: {}) => EventUtils; + getContentEditable: (node: Node) => string | null; + getContentEditableParent: (node: Node) => string | null; + isEditable: (node: Node | null | undefined) => boolean; + destroy: () => void; + isChildOf: (node: Node, parent: Node) => boolean; + dumpRng: (r: Range) => string; +} +interface ClientRect { + left: number; + top: number; + bottom: number; + right: number; + width: number; + height: number; +} +interface BookmarkManager { + getBookmark: (type?: number, normalized?: boolean) => Bookmark; + moveToBookmark: (bookmark: Bookmark) => void; +} +interface ControlSelection { + isResizable: (elm: Element) => boolean; + showResizeRect: (elm: HTMLElement) => void; + hideResizeRect: () => void; + updateResizeRect: (evt: EditorEvent) => void; + destroy: () => void; +} +interface WriterSettings { + element_format?: 'xhtml' | 'html'; + entities?: string; + entity_encoding?: EntityEncoding; + indent?: boolean; + indent_after?: string; + indent_before?: string; +} +type Attributes = Array<{ + name: string; + value: string; +}>; +interface Writer { + cdata: (text: string) => void; + comment: (text: string) => void; + doctype: (text: string) => void; + end: (name: string) => void; + getContent: () => string; + pi: (name: string, text?: string) => void; + reset: () => void; + start: (name: string, attrs?: Attributes | null, empty?: boolean) => void; + text: (text: string, raw?: boolean) => void; +} +interface HtmlSerializerSettings extends WriterSettings { + inner?: boolean; + validate?: boolean; +} +interface HtmlSerializer { + serialize: (node: AstNode) => string; +} +interface DomSerializerSettings extends DomParserSettings, WriterSettings, SchemaSettings, HtmlSerializerSettings { + remove_trailing_brs?: boolean; + url_converter?: URLConverter; + url_converter_scope?: {}; +} +interface DomSerializerImpl { + schema: Schema; + addNodeFilter: (name: string, callback: ParserFilterCallback) => void; + addAttributeFilter: (name: string, callback: ParserFilterCallback) => void; + getNodeFilters: () => ParserFilter[]; + getAttributeFilters: () => ParserFilter[]; + removeNodeFilter: (name: string, callback?: ParserFilterCallback) => void; + removeAttributeFilter: (name: string, callback?: ParserFilterCallback) => void; + serialize: { + (node: Element, parserArgs: { + format: 'tree'; + } & ParserArgs): AstNode; + (node: Element, parserArgs?: ParserArgs): string; + }; + addRules: (rules: string) => void; + setRules: (rules: string) => void; + addTempAttr: (name: string) => void; + getTempAttrs: () => string[]; +} +interface DomSerializer extends DomSerializerImpl { +} +interface EditorSelection { + bookmarkManager: BookmarkManager; + controlSelection: ControlSelection; + dom: DOMUtils; + win: Window; + serializer: DomSerializer; + editor: Editor; + collapse: (toStart?: boolean) => void; + setCursorLocation: { + (node: Node, offset: number): void; + (): void; + }; + getContent: { + (args: { + format: 'tree'; + } & Partial): AstNode; + (args?: Partial): string; + }; + setContent: (content: string, args?: Partial) => void; + getBookmark: (type?: number, normalized?: boolean) => Bookmark; + moveToBookmark: (bookmark: Bookmark) => void; + select: (node: Node, content?: boolean) => Node; + isCollapsed: () => boolean; + isEditable: () => boolean; + isForward: () => boolean; + setNode: (elm: Element) => Element; + getNode: () => HTMLElement; + getSel: () => Selection | null; + setRng: (rng: Range, forward?: boolean) => void; + getRng: () => Range; + getStart: (real?: boolean) => Element; + getEnd: (real?: boolean) => Element; + getSelectedBlocks: (startElm?: Element, endElm?: Element) => Element[]; + normalize: () => Range; + selectorChanged: (selector: string, callback: (active: boolean, args: { + node: Node; + selector: String; + parents: Node[]; + }) => void) => EditorSelection; + selectorChangedWithUnbind: (selector: string, callback: (active: boolean, args: { + node: Node; + selector: String; + parents: Node[]; + }) => void) => { + unbind: () => void; + }; + getScrollContainer: () => HTMLElement | undefined; + scrollIntoView: (elm?: HTMLElement, alignToTop?: boolean) => void; + placeCaretAt: (clientX: number, clientY: number) => void; + getBoundingClientRect: () => ClientRect | DOMRect; + destroy: () => void; + expand: (options?: { + type: 'word'; + }) => void; +} +type EditorCommandCallback = (this: S, ui: boolean, value: any) => void; +type EditorCommandsCallback = (command: string, ui: boolean, value?: any) => void; +interface Commands { + state: Record boolean>; + exec: Record; + value: Record string>; +} +interface ExecCommandArgs { + skip_focus?: boolean; +} +interface EditorCommandsConstructor { + readonly prototype: EditorCommands; + new (editor: Editor): EditorCommands; +} +declare class EditorCommands { + private readonly editor; + private commands; + constructor(editor: Editor); + execCommand(command: string, ui?: boolean, value?: any, args?: ExecCommandArgs): boolean; + queryCommandState(command: string): boolean; + queryCommandValue(command: string): string; + addCommands(commandList: Commands[K], type: K): void; + addCommands(commandList: Record): void; + addCommand(command: string, callback: EditorCommandCallback, scope: S): void; + addCommand(command: string, callback: EditorCommandCallback): void; + queryCommandSupported(command: string): boolean; + addQueryStateHandler(command: string, callback: (this: S) => boolean, scope: S): void; + addQueryStateHandler(command: string, callback: (this: Editor) => boolean): void; + addQueryValueHandler(command: string, callback: (this: S) => string, scope: S): void; + addQueryValueHandler(command: string, callback: (this: Editor) => string): void; +} +interface RawString { + raw: string; +} +type Primitive = string | number | boolean | Record | Function; +type TokenisedString = [ + string, + ...Primitive[] +]; +type Untranslated = Primitive | TokenisedString | RawString | null | undefined; +type TranslatedString = string; +interface I18n { + getData: () => Record>; + setCode: (newCode: string) => void; + getCode: () => string; + add: (code: string, items: Record) => void; + translate: (text: Untranslated) => TranslatedString; + isRtl: () => boolean; + hasCode: (code: string) => boolean; +} +interface Observable { + fire>(name: K, args?: U, bubble?: boolean): EditorEvent; + dispatch>(name: K, args?: U, bubble?: boolean): EditorEvent; + on(name: K, callback: (event: EditorEvent>) => void, prepend?: boolean): EventDispatcher; + off(name?: K, callback?: (event: EditorEvent>) => void): EventDispatcher; + once(name: K, callback: (event: EditorEvent>) => void): EventDispatcher; + hasEventListeners(name: string): boolean; +} +interface URISettings { + base_uri?: URI; +} +interface URIConstructor { + readonly prototype: URI; + new (url: string, settings?: URISettings): URI; + getDocumentBaseUrl: (loc: { + protocol: string; + host?: string; + href?: string; + pathname?: string; + }) => string; + parseDataUri: (uri: string) => { + type: string; + data: string; + }; +} +interface SafeUriOptions { + readonly allow_html_data_urls?: boolean; + readonly allow_script_urls?: boolean; + readonly allow_svg_data_urls?: boolean; +} +declare class URI { + static parseDataUri(uri: string): { + type: string | undefined; + data: string; + }; + static isDomSafe(uri: string, context?: string, options?: SafeUriOptions): boolean; + static getDocumentBaseUrl(loc: { + protocol: string; + host?: string; + href?: string; + pathname?: string; + }): string; + source: string; + protocol: string | undefined; + authority: string | undefined; + userInfo: string | undefined; + user: string | undefined; + password: string | undefined; + host: string | undefined; + port: string | undefined; + relative: string | undefined; + path: string; + directory: string; + file: string | undefined; + query: string | undefined; + anchor: string | undefined; + settings: URISettings; + constructor(url: string, settings?: URISettings); + setPath(path: string): void; + toRelative(uri: string): string; + toAbsolute(uri: string, noHost?: boolean): string; + isSameOrigin(uri: URI): boolean; + toRelPath(base: string, path: string): string; + toAbsPath(base: string, path: string): string; + getURI(noProtoHost?: boolean): string; +} +interface EditorManager extends Observable { + defaultOptions: RawEditorOptions; + majorVersion: string; + minorVersion: string; + releaseDate: string; + activeEditor: Editor | null; + focusedEditor: Editor | null; + baseURI: URI; + baseURL: string; + documentBaseURL: string; + i18n: I18n; + suffix: string; + add(this: EditorManager, editor: Editor): Editor; + addI18n: (code: string, item: Record) => void; + createEditor(this: EditorManager, id: string, options: RawEditorOptions): Editor; + execCommand(this: EditorManager, cmd: string, ui: boolean, value: any): boolean; + get(this: EditorManager): Editor[]; + get(this: EditorManager, id: number | string): Editor | null; + init(this: EditorManager, options: RawEditorOptions): Promise; + overrideDefaults(this: EditorManager, defaultOptions: Partial): void; + remove(this: EditorManager): void; + remove(this: EditorManager, selector: string): void; + remove(this: EditorManager, editor: Editor): Editor | null; + setActive(this: EditorManager, editor: Editor): void; + setup(this: EditorManager): void; + translate: (text: Untranslated) => TranslatedString; + triggerSave: () => void; + _setBaseUrl(this: EditorManager, baseUrl: string): void; +} +interface EditorObservable extends Observable { + bindPendingEventDelegates(this: Editor): void; + toggleNativeEvent(this: Editor, name: string, state: boolean): void; + unbindAllNativeEvents(this: Editor): void; +} +interface ProcessorSuccess { + valid: true; + value: T; +} +interface ProcessorError { + valid: false; + message: string; +} +type SimpleProcessor = (value: unknown) => boolean; +type Processor = (value: unknown) => ProcessorSuccess | ProcessorError; +interface BuiltInOptionTypeMap { + 'string': string; + 'number': number; + 'boolean': boolean; + 'array': any[]; + 'function': Function; + 'object': any; + 'string[]': string[]; + 'object[]': any[]; + 'regexp': RegExp; +} +type BuiltInOptionType = keyof BuiltInOptionTypeMap; +interface BaseOptionSpec { + immutable?: boolean; + deprecated?: boolean; + docsUrl?: string; +} +interface BuiltInOptionSpec extends BaseOptionSpec { + processor: K; + default?: BuiltInOptionTypeMap[K]; +} +interface SimpleOptionSpec extends BaseOptionSpec { + processor: SimpleProcessor; + default?: T; +} +interface OptionSpec extends BaseOptionSpec { + processor: Processor; + default?: T; +} +interface Options { + register: { + (name: string, spec: BuiltInOptionSpec): void; + (name: K, spec: OptionSpec | SimpleOptionSpec): void; + (name: string, spec: OptionSpec): void; + (name: string, spec: SimpleOptionSpec): void; + }; + isRegistered: (name: string) => boolean; + get: { + (name: K): EditorOptions[K]; + (name: string): T | undefined; + }; + set: (name: K, value: K extends keyof NormalizedEditorOptions ? NormalizedEditorOptions[K] : T) => boolean; + unset: (name: string) => boolean; + isSet: (name: string) => boolean; + debug: () => void; +} +interface UploadResult$1 { + element: HTMLImageElement; + status: boolean; + blobInfo: BlobInfo; + uploadUri: string; + removed: boolean; +} +interface EditorUpload { + blobCache: BlobCache; + addFilter: (filter: (img: HTMLImageElement) => boolean) => void; + uploadImages: () => Promise; + uploadImagesAuto: () => Promise; + scanForImages: () => Promise; + destroy: () => void; +} +type FormatChangeCallback = (state: boolean, data: { + node: Node; + format: string; + parents: Element[]; +}) => void; +interface FormatRegistry { + get: { + (name: string): Format[] | undefined; + (): Record; + }; + has: (name: string) => boolean; + register: (name: string | Formats, format?: Format[] | Format) => void; + unregister: (name: string) => Formats; +} +interface Formatter extends FormatRegistry { + apply: (name: string, vars?: FormatVars, node?: Node | RangeLikeObject | null) => void; + remove: (name: string, vars?: FormatVars, node?: Node | Range, similar?: boolean) => void; + toggle: (name: string, vars?: FormatVars, node?: Node) => void; + match: (name: string, vars?: FormatVars, node?: Node, similar?: boolean) => boolean; + closest: (names: string[]) => string | null; + matchAll: (names: string[], vars?: FormatVars) => string[]; + matchNode: (node: Node | null, name: string, vars?: FormatVars, similar?: boolean) => Format | undefined; + canApply: (name: string) => boolean; + formatChanged: (names: string, callback: FormatChangeCallback, similar?: boolean, vars?: FormatVars) => { + unbind: () => void; + }; + getCssText: (format: string | ApplyFormat) => string; +} +interface EditorMode { + isReadOnly: () => boolean; + set: (mode: string) => void; + get: () => string; + register: (mode: string, api: EditorModeApi) => void; +} +interface EditorModeApi { + activate: () => void; + deactivate: () => void; + editorReadOnly: boolean; +} +interface Model { + readonly table: { + readonly getSelectedCells: () => HTMLTableCellElement[]; + readonly clearSelectedCells: (container: Node) => void; + }; +} +type ModelManager = AddOnManager; +interface Plugin { + getMetadata?: () => { + name: string; + url: string; + }; + init?: (editor: Editor, url: string) => void; + [key: string]: any; +} +type PluginManager = AddOnManager; +interface ShortcutsConstructor { + readonly prototype: Shortcuts; + new (editor: Editor): Shortcuts; +} +type CommandFunc = string | [ + string, + boolean, + any +] | (() => void); +declare class Shortcuts { + private readonly editor; + private readonly shortcuts; + private pendingPatterns; + constructor(editor: Editor); + add(pattern: string, desc: string | null, cmdFunc: CommandFunc, scope?: any): boolean; + remove(pattern: string): boolean; + private normalizeCommandFunc; + private createShortcut; + private hasModifier; + private isFunctionKey; + private matchShortcut; + private executeShortcutAction; +} +interface RenderResult { + iframeContainer?: HTMLElement; + editorContainer: HTMLElement; + api?: Partial; +} +interface Theme { + ui?: any; + inline?: any; + execCommand?: (command: string, ui?: boolean, value?: any) => boolean; + destroy?: () => void; + init?: (editor: Editor, url: string) => void; + renderUI?: () => Promise | RenderResult; + getNotificationManagerImpl?: () => NotificationManagerImpl; + getWindowManagerImpl?: () => WindowManagerImpl; +} +type ThemeManager = AddOnManager; +interface EditorConstructor { + readonly prototype: Editor; + new (id: string, options: RawEditorOptions, editorManager: EditorManager): Editor; +} +declare class Editor implements EditorObservable { + documentBaseUrl: string; + baseUri: URI; + id: string; + plugins: Record; + documentBaseURI: URI; + baseURI: URI; + contentCSS: string[]; + contentStyles: string[]; + ui: EditorUi; + mode: EditorMode; + options: Options; + editorUpload: EditorUpload; + shortcuts: Shortcuts; + loadedCSS: Record; + editorCommands: EditorCommands; + suffix: string; + editorManager: EditorManager; + hidden: boolean; + inline: boolean; + hasVisual: boolean; + isNotDirty: boolean; + annotator: Annotator; + bodyElement: HTMLElement | undefined; + bookmark: any; + composing: boolean; + container: HTMLElement; + contentAreaContainer: HTMLElement; + contentDocument: Document; + contentWindow: Window; + delegates: Record> | undefined; + destroyed: boolean; + dom: DOMUtils; + editorContainer: HTMLElement; + eventRoot: Element | undefined; + formatter: Formatter; + formElement: HTMLElement | undefined; + formEventDelegate: ((e: Event) => void) | undefined; + hasHiddenInput: boolean; + iframeElement: HTMLIFrameElement | null; + iframeHTML: string | undefined; + initialized: boolean; + notificationManager: NotificationManager; + orgDisplay: string; + orgVisibility: string | undefined; + parser: DomParser; + quirks: Quirks; + readonly: boolean; + removed: boolean; + schema: Schema; + selection: EditorSelection; + serializer: DomSerializer; + startContent: string; + targetElm: HTMLElement; + theme: Theme; + model: Model; + undoManager: UndoManager; + windowManager: WindowManager; + _beforeUnload: (() => void) | undefined; + _eventDispatcher: EventDispatcher | undefined; + _nodeChangeDispatcher: NodeChange; + _pendingNativeEvents: string[]; + _selectionOverrides: SelectionOverrides; + _skinLoaded: boolean; + _editableRoot: boolean; + bindPendingEventDelegates: EditorObservable['bindPendingEventDelegates']; + toggleNativeEvent: EditorObservable['toggleNativeEvent']; + unbindAllNativeEvents: EditorObservable['unbindAllNativeEvents']; + fire: EditorObservable['fire']; + dispatch: EditorObservable['dispatch']; + on: EditorObservable['on']; + off: EditorObservable['off']; + once: EditorObservable['once']; + hasEventListeners: EditorObservable['hasEventListeners']; + constructor(id: string, options: RawEditorOptions, editorManager: EditorManager); + render(): void; + focus(skipFocus?: boolean): void; + hasFocus(): boolean; + translate(text: Untranslated): TranslatedString; + getParam(name: string, defaultVal: BuiltInOptionTypeMap[K], type: K): BuiltInOptionTypeMap[K]; + getParam(name: K, defaultVal?: NormalizedEditorOptions[K], type?: BuiltInOptionType): NormalizedEditorOptions[K]; + getParam(name: string, defaultVal: T, type?: BuiltInOptionType): T; + hasPlugin(name: string, loaded?: boolean): boolean; + nodeChanged(args?: any): void; + addCommand(name: string, callback: EditorCommandCallback, scope: S): void; + addCommand(name: string, callback: EditorCommandCallback): void; + addQueryStateHandler(name: string, callback: (this: S) => boolean, scope?: S): void; + addQueryStateHandler(name: string, callback: (this: Editor) => boolean): void; + addQueryValueHandler(name: string, callback: (this: S) => string, scope: S): void; + addQueryValueHandler(name: string, callback: (this: Editor) => string): void; + addShortcut(pattern: string, desc: string, cmdFunc: string | [ + string, + boolean, + any + ] | (() => void), scope?: any): void; + execCommand(cmd: string, ui?: boolean, value?: any, args?: ExecCommandArgs): boolean; + queryCommandState(cmd: string): boolean; + queryCommandValue(cmd: string): string; + queryCommandSupported(cmd: string): boolean; + show(): void; + hide(): void; + isHidden(): boolean; + setProgressState(state: boolean, time?: number): void; + load(args?: Partial): string; + save(args?: Partial): string; + setContent(content: string, args?: Partial): string; + setContent(content: AstNode, args?: Partial): AstNode; + setContent(content: Content, args?: Partial): Content; + getContent(args: { + format: 'tree'; + } & Partial): AstNode; + getContent(args?: Partial): string; + insertContent(content: string, args?: any): void; + resetContent(initialContent?: string): void; + isDirty(): boolean; + setDirty(state: boolean): void; + getContainer(): HTMLElement; + getContentAreaContainer(): HTMLElement; + getElement(): HTMLElement; + getWin(): Window; + getDoc(): Document; + getBody(): HTMLElement; + convertURL(url: string, name: string, elm?: string | Element): string; + addVisual(elm?: HTMLElement): void; + setEditableRoot(state: boolean): void; + hasEditableRoot(): boolean; + remove(): void; + destroy(automatic?: boolean): void; + uploadImages(): Promise; + _scanForImages(): Promise; +} +interface UrlObject { + prefix: string; + resource: string; + suffix: string; +} +type WaitState = 'added' | 'loaded'; +type AddOnConstructor = (editor: Editor, url: string) => T; +interface AddOnManager { + items: AddOnConstructor[]; + urls: Record; + lookup: Record; + }>; + get: (name: string) => AddOnConstructor | undefined; + requireLangPack: (name: string, languages?: string) => void; + add: (id: string, addOn: AddOnConstructor) => AddOnConstructor; + remove: (name: string) => void; + createUrl: (baseUrl: UrlObject, dep: string | UrlObject) => UrlObject; + load: (name: string, addOnUrl: string | UrlObject) => Promise; + waitFor: (name: string, state?: WaitState) => Promise; +} +interface RangeUtils { + walk: (rng: Range, callback: (nodes: Node[]) => void) => void; + split: (rng: Range) => RangeLikeObject; + normalize: (rng: Range) => boolean; + expand: (rng: Range, options?: { + type: 'word'; + }) => Range; +} +interface ScriptLoaderSettings { + referrerPolicy?: ReferrerPolicy; +} +interface ScriptLoaderConstructor { + readonly prototype: ScriptLoader; + new (): ScriptLoader; + ScriptLoader: ScriptLoader; +} +declare class ScriptLoader { + static ScriptLoader: ScriptLoader; + private settings; + private states; + private queue; + private scriptLoadedCallbacks; + private queueLoadedCallbacks; + private loading; + constructor(settings?: ScriptLoaderSettings); + _setReferrerPolicy(referrerPolicy: ReferrerPolicy): void; + loadScript(url: string): Promise; + isDone(url: string): boolean; + markDone(url: string): void; + add(url: string): Promise; + load(url: string): Promise; + remove(url: string): void; + loadQueue(): Promise; + loadScripts(scripts: string[]): Promise; +} +type TextProcessCallback = (node: Text, offset: number, text: string) => number; +interface Spot { + container: Text; + offset: number; +} +interface TextSeeker { + backwards: (node: Node, offset: number, process: TextProcessCallback, root?: Node) => Spot | null; + forwards: (node: Node, offset: number, process: TextProcessCallback, root?: Node) => Spot | null; +} +interface DomTreeWalkerConstructor { + readonly prototype: DomTreeWalker; + new (startNode: Node, rootNode: Node): DomTreeWalker; +} +declare class DomTreeWalker { + private readonly rootNode; + private node; + constructor(startNode: Node, rootNode: Node); + current(): Node | null | undefined; + next(shallow?: boolean): Node | null | undefined; + prev(shallow?: boolean): Node | null | undefined; + prev2(shallow?: boolean): Node | null | undefined; + private findSibling; + private findPreviousNode; +} +interface Version { + major: number; + minor: number; +} +interface Env { + transparentSrc: string; + documentMode: number; + cacheSuffix: any; + container: any; + canHaveCSP: boolean; + windowsPhone: boolean; + browser: { + current: string | undefined; + version: Version; + isEdge: () => boolean; + isChromium: () => boolean; + isIE: () => boolean; + isOpera: () => boolean; + isFirefox: () => boolean; + isSafari: () => boolean; + }; + os: { + current: string | undefined; + version: Version; + isWindows: () => boolean; + isiOS: () => boolean; + isAndroid: () => boolean; + isMacOS: () => boolean; + isLinux: () => boolean; + isSolaris: () => boolean; + isFreeBSD: () => boolean; + isChromeOS: () => boolean; + }; + deviceType: { + isiPad: () => boolean; + isiPhone: () => boolean; + isTablet: () => boolean; + isPhone: () => boolean; + isTouch: () => boolean; + isWebView: () => boolean; + isDesktop: () => boolean; + }; +} +interface FakeClipboardItem { + readonly items: Record; + readonly types: ReadonlyArray; + readonly getType: (type: string) => D | undefined; +} +interface FakeClipboard { + readonly FakeClipboardItem: (items: Record) => FakeClipboardItem; + readonly write: (data: FakeClipboardItem[]) => void; + readonly read: () => FakeClipboardItem[] | undefined; + readonly clear: () => void; +} +interface FocusManager { + isEditorUIElement: (elm: Element) => boolean; +} +interface EntitiesMap { + [name: string]: string; +} +interface Entities { + encodeRaw: (text: string, attr?: boolean) => string; + encodeAllRaw: (text: string) => string; + encodeNumeric: (text: string, attr?: boolean) => string; + encodeNamed: (text: string, attr?: boolean, entities?: EntitiesMap) => string; + getEncodeFunc: (name: string, entities?: string) => (text: string, attr?: boolean) => string; + decode: (text: string) => string; +} +interface IconPack { + icons: Record; +} +interface IconManager { + add: (id: string, iconPack: IconPack) => void; + get: (id: string) => IconPack; + has: (id: string) => boolean; +} +interface Resource { + load: (id: string, url: string) => Promise; + add: (id: string, data: any) => void; + has: (id: string) => boolean; + get: (id: string) => any; + unload: (id: string) => void; +} +type TextPatterns_d_Pattern = Pattern; +type TextPatterns_d_RawPattern = RawPattern; +type TextPatterns_d_DynamicPatternsLookup = DynamicPatternsLookup; +type TextPatterns_d_RawDynamicPatternsLookup = RawDynamicPatternsLookup; +type TextPatterns_d_DynamicPatternContext = DynamicPatternContext; +type TextPatterns_d_BlockCmdPattern = BlockCmdPattern; +type TextPatterns_d_BlockPattern = BlockPattern; +type TextPatterns_d_BlockFormatPattern = BlockFormatPattern; +type TextPatterns_d_InlineCmdPattern = InlineCmdPattern; +type TextPatterns_d_InlinePattern = InlinePattern; +type TextPatterns_d_InlineFormatPattern = InlineFormatPattern; +declare namespace TextPatterns_d { + export { TextPatterns_d_Pattern as Pattern, TextPatterns_d_RawPattern as RawPattern, TextPatterns_d_DynamicPatternsLookup as DynamicPatternsLookup, TextPatterns_d_RawDynamicPatternsLookup as RawDynamicPatternsLookup, TextPatterns_d_DynamicPatternContext as DynamicPatternContext, TextPatterns_d_BlockCmdPattern as BlockCmdPattern, TextPatterns_d_BlockPattern as BlockPattern, TextPatterns_d_BlockFormatPattern as BlockFormatPattern, TextPatterns_d_InlineCmdPattern as InlineCmdPattern, TextPatterns_d_InlinePattern as InlinePattern, TextPatterns_d_InlineFormatPattern as InlineFormatPattern, }; +} +interface Delay { + setEditorInterval: (editor: Editor, callback: () => void, time?: number) => number; + setEditorTimeout: (editor: Editor, callback: () => void, time?: number) => number; +} +type UploadResult = UploadResult$2; +interface ImageUploader { + upload: (blobInfos: BlobInfo[], showNotification?: boolean) => Promise; +} +type ArrayCallback$1 = (this: any, x: T, i: number, xs: ArrayLike) => R; +type ObjCallback$1 = (this: any, value: T, key: string, obj: Record) => R; +type ArrayCallback = ArrayCallback$1; +type ObjCallback = ObjCallback$1; +type WalkCallback = (this: any, o: T, i: string, n: keyof T | undefined) => boolean | void; +interface Tools { + is: (obj: any, type?: string) => boolean; + isArray: (arr: any) => arr is Array; + inArray: (arr: ArrayLike, value: T) => number; + grep: { + (arr: ArrayLike | null | undefined, pred?: ArrayCallback): T[]; + (arr: Record | null | undefined, pred?: ObjCallback): T[]; + }; + trim: (str: string | null | undefined) => string; + toArray: (obj: ArrayLike) => T[]; + hasOwn: (obj: any, name: string) => boolean; + makeMap: (items: ArrayLike | string | undefined, delim?: string | RegExp, map?: Record) => Record; + each: { + (arr: ArrayLike | null | undefined, cb: ArrayCallback, scope?: any): boolean; + (obj: Record | null | undefined, cb: ObjCallback, scope?: any): boolean; + }; + map: { + (arr: ArrayLike | null | undefined, cb: ArrayCallback): R[]; + (obj: Record | null | undefined, cb: ObjCallback): R[]; + }; + extend: (obj: Object, ext: Object, ...objs: Object[]) => any; + walk: >(obj: T, f: WalkCallback, n?: keyof T, scope?: any) => void; + resolve: (path: string, o?: Object) => any; + explode: (s: string | string[], d?: string | RegExp) => string[]; + _addCacheSuffix: (url: string) => string; +} +interface KeyboardLikeEvent { + shiftKey: boolean; + ctrlKey: boolean; + altKey: boolean; + metaKey: boolean; +} +interface VK { + BACKSPACE: number; + DELETE: number; + DOWN: number; + ENTER: number; + ESC: number; + LEFT: number; + RIGHT: number; + SPACEBAR: number; + TAB: number; + UP: number; + PAGE_UP: number; + PAGE_DOWN: number; + END: number; + HOME: number; + modifierPressed: (e: KeyboardLikeEvent) => boolean; + metaKeyPressed: (e: KeyboardLikeEvent) => boolean; +} +interface DOMUtilsNamespace { + (doc: Document, settings: Partial): DOMUtils; + DOM: DOMUtils; + nodeIndex: (node: Node, normalized?: boolean) => number; +} +interface RangeUtilsNamespace { + (dom: DOMUtils): RangeUtils; + compareRanges: (rng1: RangeLikeObject, rng2: RangeLikeObject) => boolean; + getCaretRangeFromPoint: (clientX: number, clientY: number, doc: Document) => Range; + getSelectedNode: (range: Range) => Node; + getNode: (container: Node, offset: number) => Node; +} +interface AddOnManagerNamespace { + (): AddOnManager; + language: string | undefined; + languageLoad: boolean; + baseURL: string; + PluginManager: PluginManager; + ThemeManager: ThemeManager; + ModelManager: ModelManager; +} +interface BookmarkManagerNamespace { + (selection: EditorSelection): BookmarkManager; + isBookmarkNode: (node: Node) => boolean; +} +interface TinyMCE extends EditorManager { + geom: { + Rect: Rect; + }; + util: { + Delay: Delay; + Tools: Tools; + VK: VK; + URI: URIConstructor; + EventDispatcher: EventDispatcherConstructor; + Observable: Observable; + I18n: I18n; + LocalStorage: Storage; + ImageUploader: ImageUploader; + }; + dom: { + EventUtils: EventUtilsConstructor; + TreeWalker: DomTreeWalkerConstructor; + TextSeeker: (dom: DOMUtils, isBlockBoundary?: (node: Node) => boolean) => TextSeeker; + DOMUtils: DOMUtilsNamespace; + ScriptLoader: ScriptLoaderConstructor; + RangeUtils: RangeUtilsNamespace; + Serializer: (settings: DomSerializerSettings, editor?: Editor) => DomSerializer; + ControlSelection: (selection: EditorSelection, editor: Editor) => ControlSelection; + BookmarkManager: BookmarkManagerNamespace; + Selection: (dom: DOMUtils, win: Window, serializer: DomSerializer, editor: Editor) => EditorSelection; + StyleSheetLoader: (documentOrShadowRoot: Document | ShadowRoot, settings: StyleSheetLoaderSettings) => StyleSheetLoader; + Event: EventUtils; + }; + html: { + Styles: (settings?: StylesSettings, schema?: Schema) => Styles; + Entities: Entities; + Node: AstNodeConstructor; + Schema: (settings?: SchemaSettings) => Schema; + DomParser: (settings?: DomParserSettings, schema?: Schema) => DomParser; + Writer: (settings?: WriterSettings) => Writer; + Serializer: (settings?: HtmlSerializerSettings, schema?: Schema) => HtmlSerializer; + }; + AddOnManager: AddOnManagerNamespace; + Annotator: (editor: Editor) => Annotator; + Editor: EditorConstructor; + EditorCommands: EditorCommandsConstructor; + EditorManager: EditorManager; + EditorObservable: EditorObservable; + Env: Env; + FocusManager: FocusManager; + Formatter: (editor: Editor) => Formatter; + NotificationManager: (editor: Editor) => NotificationManager; + Shortcuts: ShortcutsConstructor; + UndoManager: (editor: Editor) => UndoManager; + WindowManager: (editor: Editor) => WindowManager; + DOM: DOMUtils; + ScriptLoader: ScriptLoader; + PluginManager: PluginManager; + ThemeManager: ThemeManager; + ModelManager: ModelManager; + IconManager: IconManager; + Resource: Resource; + FakeClipboard: FakeClipboard; + trim: Tools['trim']; + isArray: Tools['isArray']; + is: Tools['is']; + toArray: Tools['toArray']; + makeMap: Tools['makeMap']; + each: Tools['each']; + map: Tools['map']; + grep: Tools['grep']; + inArray: Tools['inArray']; + extend: Tools['extend']; + walk: Tools['walk']; + resolve: Tools['resolve']; + explode: Tools['explode']; + _addCacheSuffix: Tools['_addCacheSuffix']; +} +declare const tinymce: TinyMCE; +export { + AddOnConstructor, + AddOnManager, + AddOnManagerNamespace, + AddUndoEvent, + AfterProgressStateEvent, + AlertBannerSpec, + Alignment, + AllowedFormat, + AnnotationListener, + AnnotationListenerApi, + Annotator, + AnnotatorSettings, + ApplyFormat, + ArrayCallback, + ArrayCallback$1, + AstNodeConstructor, + Attribute, + AttributePattern, + Attributes, + Attributes$1, + AutocompleteLookupData, + AutocompleterContents, + AutocompleterEventArgs, + AutocompleterInstanceApi, + AutocompleterItemSpec, + AutocompleterSpec, + BarSpec, + BaseButtonSpec, + BaseDialogFooterButtonSpec, + BaseEditorOptions, + BaseFancyMenuItemSpec, + BaseFormat, + BaseMenuButtonInstanceApi, + BaseMenuButtonSpec, + BaseOptionSpec, + BaseToolbarButtonInstanceApi, + BaseToolbarButtonSpec, + BaseToolbarToggleButtonInstanceApi, + BaseToolbarToggleButtonSpec, + BaseTreeItemSpec, + BaseUndoLevel, + BeforeGetContentEvent, + BeforeOpenNotificationEvent, + BeforeSetContentEvent, + BlobCache, + BlobInfo, + BlobInfoData, + BlobInfoImagePair, + Block, + BlockBasePattern, + BlockCmdPattern, + BlockFormat, + BlockFormatPattern, + BlockPattern, + BlockPatternTrigger, + BlockStyleFormat, + BodyComponentSpec, + Bookmark, + BookmarkManager, + BookmarkManagerNamespace, + BoundEvent, + BuiltInOptionSpec, + BuiltInOptionType, + BuiltInOptionTypeMap, + ButtonSpec, + Callback, + Callback$1, + CallbackList, + CardContainerAlign, + CardContainerDirection, + CardContainerSpec, + CardContainerValign, + CardImageSpec, + CardItemSpec, + CardMenuItemInstanceApi, + CardMenuItemSpec, + CardTextSpec, + ChangeEvent, + CheckboxSpec, + ChoiceMenuItemInstanceApi, + ChoiceMenuItemSpec, + ClientRect, + CollectionItem, + CollectionSpec, + ColorInputSpec, + ColorPickerSpec, + ColorSwatchMenuItemSpec, + ColumnTypes, + ColumnTypes$1, + CommandFunc, + Commands, + CommonFormat, + CommonMenuItemInstanceApi, + CommonMenuItemSpec, + CommonRemoveFormat, + CommonStyleFormat, + CompleteUndoLevel, + Content, + ContentFormat, + ContentLanguage, + ContextBarSpec, + ContextFormButtonInstanceApi, + ContextFormButtonSpec, + ContextFormInstanceApi, + ContextFormLaunchButtonApi, + ContextFormLaunchToggleButtonSpec, + ContextFormSpec, + ContextFormToggleButtonInstanceApi, + ContextFormToggleButtonSpec, + ContextMenuApi, + ContextMenuContents, + ContextMenuItem, + ContextPosition, + ContextScope, + ContextSubMenu, + ContextToolbarSpec, + ControlSelection, + CustomEditorInit, + CustomEditorInitFn, + CustomEditorNewSpec, + CustomEditorOldSpec, + CustomEditorSpec, + CustomElementSpec, + Decorator, + DecoratorData, + DefaultAttribute, + Delay, + DialogActionDetails, + DialogActionHandler, + DialogCancelHandler, + DialogChangeDetails, + DialogChangeHandler, + DialogCloseHandler, + DialogData, + DialogDataItem, + DialogFooterButtonSpec, + DialogFooterMenuButtonItemSpec, + DialogFooterMenuButtonSpec, + DialogFooterNormalButtonSpec, + DialogFooterToggleButtonSpec, + DialogInstanceApi, + DialogSize, + DialogSpec, + DialogSubmitHandler, + DialogTabChangeDetails, + DialogTabChangeHandler, + DialogToggleMenuItemSpec, + DirectorySpec, + DomParser, + DomParserSettings, + DomSerializer, + DomSerializerImpl, + DomSerializerSettings, + DomTreeWalkerConstructor, + DOMUtils, + DOMUtilsNamespace, + DOMUtilsSettings, + DropZoneSpec, + DynamicPatternContext, + DynamicPatternsLookup, + EditableRootStateChangeEvent, + EditorCommandCallback, + EditorCommandsCallback, + EditorCommandsConstructor, + EditorConstructor, + EditorEvent, + EditorEventMap, + EditorManager, + EditorManagerEventMap, + EditorMode, + EditorModeApi, + EditorObservable, + EditorOptions, + EditorSelection, + EditorUi, + EditorUiApi, + EditorUpload, + ElementRule, + ElementSettings, + Entities, + EntitiesMap, + EntityEncoding, + Env, + EventDispatcherConstructor, + EventDispatcherSettings, + EventTypes_d_AddUndoEvent, + EventTypes_d_AfterProgressStateEvent, + EventTypes_d_BeforeGetContentEvent, + EventTypes_d_BeforeOpenNotificationEvent, + EventTypes_d_BeforeSetContentEvent, + EventTypes_d_ChangeEvent, + EventTypes_d_EditableRootStateChangeEvent, + EventTypes_d_EditorEventMap, + EventTypes_d_EditorManagerEventMap, + EventTypes_d_ExecCommandEvent, + EventTypes_d_FormatEvent, + EventTypes_d_GetContentEvent, + EventTypes_d_LoadErrorEvent, + EventTypes_d_NewBlockEvent, + EventTypes_d_NewTableCellEvent, + EventTypes_d_NewTableRowEvent, + EventTypes_d_NodeChangeEvent, + EventTypes_d_ObjectResizeEvent, + EventTypes_d_ObjectSelectedEvent, + EventTypes_d_OpenNotificationEvent, + EventTypes_d_PastePlainTextToggleEvent, + EventTypes_d_PastePostProcessEvent, + EventTypes_d_PastePreProcessEvent, + EventTypes_d_PlaceholderToggleEvent, + EventTypes_d_PostProcessEvent, + EventTypes_d_PreProcessEvent, + EventTypes_d_ProgressStateEvent, + EventTypes_d_SaveContentEvent, + EventTypes_d_ScrollIntoViewEvent, + EventTypes_d_SetContentEvent, + EventTypes_d_SetSelectionRangeEvent, + EventTypes_d_ShowCaretEvent, + EventTypes_d_SwitchModeEvent, + EventTypes_d_TableEventData, + EventTypes_d_TableModifiedEvent, + EventTypes_d_UndoRedoEvent, + EventTypes_d_WindowEvent, + EventUtilsCallback, + EventUtilsConstructor, + EventUtilsEvent, + ExecCommandArgs, + ExecCommandEvent, + FakeClipboard, + FakeClipboardItem, + FancyActionArgsMap, + FancyMenuItemSpec, + FilePickerCallback, + FilePickerValidationCallback, + FilePickerValidationStatus, + Filter, + FocusManager, + Format, + Format_d_ApplyFormat, + Format_d_BlockFormat, + Format_d_Format, + Format_d_Formats, + Format_d_InlineFormat, + Format_d_RemoveBlockFormat, + Format_d_RemoveFormat, + Format_d_RemoveInlineFormat, + Format_d_RemoveSelectorFormat, + Format_d_SelectorFormat, + FormatAttrOrStyleValue, + FormatChangeCallback, + FormatEvent, + FormatReference, + FormatRegistry, + Formats, + Formatter, + FormatVars, + FormComponentSpec, + FormComponentWithLabelSpec, + FragmentedUndoLevel, + GeomRect, + GetContentArgs, + GetContentEvent, + GetSelectionContentArgs, + GridSpec, + GroupToolbarButtonInstanceApi, + GroupToolbarButtonSpec, + HtmlPanelSpec, + HtmlSerializer, + HtmlSerializerSettings, + I18n, + IconManager, + IconPack, + Id, + IdBookmark, + IframeSpec, + ImagePreviewSpec, + ImageUploader, + IndexBookmark, + Inline, + InlineBasePattern, + InlineCmdPattern, + InlineFormat, + InlineFormatPattern, + InlinePattern, + InlineStyleFormat, + InputSpec, + InsertTableMenuItemSpec, + InstanceApi, + IsEmptyOptions, + KeyboardLikeEvent, + LabelSpec, + LeafSpec, + ListBoxItemSpec, + ListBoxNestedItemSpec, + ListBoxSingleItemSpec, + ListBoxSpec, + LoadErrorEvent, + MappedEvent, + MenuButtonFetchContext, + MenuButtonItemTypes, + MenuItemInstanceApi, + MenuItemSpec, + Model, + ModelManager, + NativeEventMap, + NestedFormatting, + NestedMenuItemContents, + NestedMenuItemInstanceApi, + NestedMenuItemSpec, + NewBlockEvent, + NewTableCellEvent, + NewTableRowEvent, + NewUndoLevel, + NodeChangeEvent, + NormalizedEditorOptions, + NormalizedEvent, + NotificationApi, + NotificationManager, + NotificationManagerImpl, + NotificationSpec, + ObjCallback, + ObjCallback$1, + ObjectResizeEvent, + ObjectSelectedEvent, + Observable, + OpenNotificationEvent, + Options, + OptionSpec, + PanelSpec, + ParserArgs, + ParserFilter, + ParserFilterCallback, + PastePlainTextToggleEvent, + PastePostProcessEvent, + PastePostProcessFn, + PastePreProcessEvent, + PastePreProcessFn, + PathBookmark, + Pattern, + PlaceholderToggleEvent, + Plugin, + PluginManager, + PostProcessEvent, + PreProcessEvent, + PresetTypes, + Primitive, + Processor, + ProcessorError, + ProcessorSuccess, + ProgressFn, + ProgressStateEvent, + PublicDialog_d_AlertBannerSpec, + PublicDialog_d_BarSpec, + PublicDialog_d_BodyComponentSpec, + PublicDialog_d_ButtonSpec, + PublicDialog_d_CheckboxSpec, + PublicDialog_d_CollectionItem, + PublicDialog_d_CollectionSpec, + PublicDialog_d_ColorInputSpec, + PublicDialog_d_ColorPickerSpec, + PublicDialog_d_CustomEditorInit, + PublicDialog_d_CustomEditorInitFn, + PublicDialog_d_CustomEditorSpec, + PublicDialog_d_DialogActionDetails, + PublicDialog_d_DialogChangeDetails, + PublicDialog_d_DialogData, + PublicDialog_d_DialogFooterButtonSpec, + PublicDialog_d_DialogInstanceApi, + PublicDialog_d_DialogSize, + PublicDialog_d_DialogSpec, + PublicDialog_d_DialogTabChangeDetails, + PublicDialog_d_DropZoneSpec, + PublicDialog_d_GridSpec, + PublicDialog_d_HtmlPanelSpec, + PublicDialog_d_IframeSpec, + PublicDialog_d_ImagePreviewSpec, + PublicDialog_d_InputSpec, + PublicDialog_d_LabelSpec, + PublicDialog_d_ListBoxItemSpec, + PublicDialog_d_ListBoxNestedItemSpec, + PublicDialog_d_ListBoxSingleItemSpec, + PublicDialog_d_ListBoxSpec, + PublicDialog_d_PanelSpec, + PublicDialog_d_SelectBoxItemSpec, + PublicDialog_d_SelectBoxSpec, + PublicDialog_d_SizeInputSpec, + PublicDialog_d_SliderSpec, + PublicDialog_d_TableSpec, + PublicDialog_d_TabPanelSpec, + PublicDialog_d_TabSpec, + PublicDialog_d_TextAreaSpec, + PublicDialog_d_TreeItemSpec, + PublicDialog_d_TreeSpec, + PublicDialog_d_UrlDialogActionDetails, + PublicDialog_d_UrlDialogFooterButtonSpec, + PublicDialog_d_UrlDialogInstanceApi, + PublicDialog_d_UrlDialogMessage, + PublicDialog_d_UrlDialogSpec, + PublicDialog_d_UrlInputData, + PublicDialog_d_UrlInputSpec, + PublicInlineContent_d_AutocompleterContents, + PublicInlineContent_d_AutocompleterInstanceApi, + PublicInlineContent_d_AutocompleterItemSpec, + PublicInlineContent_d_AutocompleterSpec, + PublicInlineContent_d_ContextFormButtonInstanceApi, + PublicInlineContent_d_ContextFormButtonSpec, + PublicInlineContent_d_ContextFormInstanceApi, + PublicInlineContent_d_ContextFormSpec, + PublicInlineContent_d_ContextFormToggleButtonInstanceApi, + PublicInlineContent_d_ContextFormToggleButtonSpec, + PublicInlineContent_d_ContextPosition, + PublicInlineContent_d_ContextScope, + PublicInlineContent_d_ContextToolbarSpec, + PublicInlineContent_d_SeparatorItemSpec, + PublicMenu_d_CardContainerSpec, + PublicMenu_d_CardImageSpec, + PublicMenu_d_CardItemSpec, + PublicMenu_d_CardMenuItemInstanceApi, + PublicMenu_d_CardMenuItemSpec, + PublicMenu_d_CardTextSpec, + PublicMenu_d_ChoiceMenuItemInstanceApi, + PublicMenu_d_ChoiceMenuItemSpec, + PublicMenu_d_ColorSwatchMenuItemSpec, + PublicMenu_d_ContextMenuApi, + PublicMenu_d_ContextMenuContents, + PublicMenu_d_ContextMenuItem, + PublicMenu_d_ContextSubMenu, + PublicMenu_d_FancyMenuItemSpec, + PublicMenu_d_InsertTableMenuItemSpec, + PublicMenu_d_MenuItemInstanceApi, + PublicMenu_d_MenuItemSpec, + PublicMenu_d_NestedMenuItemContents, + PublicMenu_d_NestedMenuItemInstanceApi, + PublicMenu_d_NestedMenuItemSpec, + PublicMenu_d_SeparatorMenuItemSpec, + PublicMenu_d_ToggleMenuItemInstanceApi, + PublicMenu_d_ToggleMenuItemSpec, + PublicSidebar_d_SidebarInstanceApi, + PublicSidebar_d_SidebarSpec, + PublicToolbar_d_GroupToolbarButtonInstanceApi, + PublicToolbar_d_GroupToolbarButtonSpec, + PublicToolbar_d_ToolbarButtonInstanceApi, + PublicToolbar_d_ToolbarButtonSpec, + PublicToolbar_d_ToolbarMenuButtonInstanceApi, + PublicToolbar_d_ToolbarMenuButtonSpec, + PublicToolbar_d_ToolbarSplitButtonInstanceApi, + PublicToolbar_d_ToolbarSplitButtonSpec, + PublicToolbar_d_ToolbarToggleButtonInstanceApi, + PublicToolbar_d_ToolbarToggleButtonSpec, + PublicView_d_ViewInstanceApi, + PublicView_d_ViewSpec, + Quirks, + RangeBookmark, + RangeLikeObject, + RangeUtils, + RangeUtilsNamespace, + RawDynamicPatternsLookup, + RawEditorOptions, + RawPattern, + RawString, + Rect, + Registry, + Registry$1, + RemoveBlockFormat, + RemoveFormat, + RemoveInlineFormat, + RemoveSelectorFormat, + RenderResult, + Resource, + RunArguments, + RunResult, + SafeUriOptions, + SaveContentEvent, + Schema, + SchemaElement, + SchemaMap, + SchemaRegExpMap, + SchemaSettings, + SchemaType, + ScriptLoaderConstructor, + ScriptLoaderSettings, + ScrollIntoViewEvent, + SelectBoxItemSpec, + SelectBoxSpec, + SelectionOverrides, + Selector, + SelectorFormat, + SelectorStyleFormat, + SelectPredicate, + Separator, + SeparatorItemSpec, + SeparatorMenuItemSpec, + SetAttribEvent, + SetContentArgs, + SetContentEvent, + SetSelectionContentArgs, + SetSelectionRangeEvent, + SetupCallback, + ShortcutsConstructor, + ShowCaretEvent, + SidebarInstanceApi, + SidebarSpec, + SimpleOptionSpec, + SimpleProcessor, + SizeInputSpec, + SliderSpec, + Spot, + StringPathBookmark, + StyleFormat, + StyleMap, + Styles, + StyleSheetLoader, + StyleSheetLoaderSettings, + StylesSettings, + SuccessCallback, + SuccessCallback$1, + SwitchModeEvent, + TableEventData, + TableModifiedEvent, + TableSpec, + TabPanelSpec, + TabSpec, + Target, + TextAreaSpec, + TextPatterns_d_BlockCmdPattern, + TextPatterns_d_BlockFormatPattern, + TextPatterns_d_BlockPattern, + TextPatterns_d_DynamicPatternContext, + TextPatterns_d_DynamicPatternsLookup, + TextPatterns_d_InlineCmdPattern, + TextPatterns_d_InlineFormatPattern, + TextPatterns_d_InlinePattern, + TextPatterns_d_Pattern, + TextPatterns_d_RawDynamicPatternsLookup, + TextPatterns_d_RawPattern, + TextProcessCallback, + TextSeeker, + Theme, + ThemeInitFunc, + ThemeManager, + TinyMCE, + ToggleMenuItemInstanceApi, + ToggleMenuItemSpec, + TokenisedString, + ToolbarButtonInstanceApi, + ToolbarButtonSpec, + ToolbarConfig, + ToolbarGroup, + ToolbarGroupSetting, + ToolbarLocation, + ToolbarMenuButtonInstanceApi, + ToolbarMenuButtonSpec, + ToolbarMode, + ToolbarSplitButtonInstanceApi, + ToolbarSplitButtonItemTypes, + ToolbarSplitButtonSpec, + ToolbarToggleButtonInstanceApi, + ToolbarToggleButtonSpec, + Tools, + TranslatedString, + TreeItemSpec, + TreeSpec, + Ui_d_EditorUi, + Ui_d_EditorUiApi, + Ui_d_Registry, + UndoLevel, + UndoLevelType, + UndoManager, + UndoRedoEvent, + Untranslated, + UploadFailure, + UploadHandler, + UploadResult, + UploadResult$1, + UploadResult$2, + URIConstructor, + URISettings, + URLConverter, + URLConverterCallback, + UrlDialogActionDetails, + UrlDialogActionHandler, + UrlDialogCancelHandler, + UrlDialogCloseHandler, + UrlDialogFooterButtonSpec, + UrlDialogInstanceApi, + UrlDialogMessage, + UrlDialogMessageHandler, + UrlDialogSpec, + UrlInputData, + UrlInputSpec, + UrlObject, + Version, + ViewButtonApi, + ViewButtonsGroupSpec, + ViewButtonSpec, + ViewInstanceApi, + ViewNormalButtonSpec, + ViewSpec, + ViewToggleButtonApi, + ViewToggleButtonSpec, + VK, + WaitState, + WalkCallback, + WindowEvent, + WindowManager, + WindowManagerImpl, + WindowParams, + Writer, + WriterSettings, +}; diff --git a/integreat_cms/static/src/js/utils/caching-functions.ts b/integreat_cms/static/src/js/utils/caching-functions.ts new file mode 100644 index 0000000000..b6a00de28a --- /dev/null +++ b/integreat_cms/static/src/js/utils/caching-functions.ts @@ -0,0 +1,12 @@ +export function evaluateOnceDecorator(fn: ()=>T): ()=>T { + let value: T | null = null; + // Save whether we computed the value as a separate boolean, in case we ever literally compute null + let computed = false; + return (): T => { + if (!computed) { + value = fn(); + computed = true; + } + return value; + }; +}; diff --git a/integreat_cms/static/src/js/utils/debounce.ts b/integreat_cms/static/src/js/utils/debounce.ts new file mode 100644 index 0000000000..e75c5c97df --- /dev/null +++ b/integreat_cms/static/src/js/utils/debounce.ts @@ -0,0 +1,21 @@ + +export function debounce< + Fn extends (this: any, ...args: any[]) => any +>( + func: Fn, + wait: number, + immediate: boolean = false +): (...args: Parameters) => void | undefined { + let timeout: number | undefined; + return function(this: ThisParameterType, ...args: Parameters) { + const callNow = immediate && !timeout; + window.clearTimeout(timeout); + timeout = window.setTimeout(() => { + timeout = undefined; + if (!immediate) { + func.apply(this, args); + } + }, wait); + if (callNow) func.apply(this, args); + } +} diff --git a/integreat_cms/static/src/js/utils/url-tools.ts b/integreat_cms/static/src/js/utils/url-tools.ts new file mode 100644 index 0000000000..73f5e07acc --- /dev/null +++ b/integreat_cms/static/src/js/utils/url-tools.ts @@ -0,0 +1,3 @@ +export function stripProtocol(url: string) { + return url.replace(/^[^:/]*:\/\//, ""); +} diff --git a/tsconfig.json b/tsconfig.json index a076ecc1fb..bcb97d634c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,8 @@ "target": "es6", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ "module": "commonjs", + /* Specifies the set of built-in type definitions to include, with 'esnext' enabling latest ECMAScript features and 'dom.iterable' adding support for iterable DOM types. */ + "lib": ["esnext", "dom.iterable"], /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ "jsx": "react-jsx", /* Declares the module specifier to be used for importing the jsx and jsxs factory functions when using jsx as "react-jsx". */