- RichEditorField: Reka toolbar, link popover with the http/https/mailto/tel and relative URL policy, Edit HTML view, lossy note, skeleton while the lazily imported TipTap chunk loads, HTML view fallback when it fails - untouched values are submitted byte-identical; after an edit the value is the editor HTML (empty paragraph -> ""); a value from outside (the save's sanitized answer) reloads the editor - read-only renders through a client display allowlist, never as stored - TipTap 3.31.4 (core, pm, starter-kit) pinned exact after the package check - richeditor.* phrase keys (en, pl), smoke specs, forms docs
53 lines
1.5 KiB
TypeScript
53 lines
1.5 KiB
TypeScript
// What the richeditor's TipTap schema can show (UI-SPEC RichEditorField,
|
|
// "Lossy note"). Stored HTML with any other element still opens: the field
|
|
// keeps the stored string until the administrator edits (the untouched
|
|
// rule), and the HTML view edits it without loss. This module has no TipTap
|
|
// import, so the field can decide before the editor chunk loads.
|
|
|
|
/** Elements the editor shows; compared by lower-cased tag name. */
|
|
export const EDITOR_TAGS: ReadonlySet<string> = new Set([
|
|
'p',
|
|
'br',
|
|
'strong',
|
|
'b',
|
|
'em',
|
|
'i',
|
|
'u',
|
|
's',
|
|
'h3',
|
|
'ul',
|
|
'ol',
|
|
'li',
|
|
'blockquote',
|
|
'a',
|
|
'div',
|
|
'span',
|
|
])
|
|
|
|
/**
|
|
* Parses html into an inert template: nothing in it runs or loads (no
|
|
* scripts, no image requests), so stored markup can be inspected safely.
|
|
*/
|
|
export function inertFragment(html: string): DocumentFragment {
|
|
const template = document.createElement('template')
|
|
template.innerHTML = html
|
|
return template.content
|
|
}
|
|
|
|
/** The distinct lower-cased tag names of html's elements, in document order. */
|
|
export function tagNames(html: string): string[] {
|
|
if (html === '') {
|
|
return []
|
|
}
|
|
const out = new Set<string>()
|
|
for (const element of Array.from(inertFragment(html).querySelectorAll('*'))) {
|
|
out.add(element.localName.toLowerCase())
|
|
}
|
|
return [...out]
|
|
}
|
|
|
|
/** Whether html holds an element the editor cannot show (the lossy note). */
|
|
export function isLossy(html: string): boolean {
|
|
return tagNames(html).some((name) => !EDITOR_TAGS.has(name))
|
|
}
|