feat(17-02): richeditor field on TipTap with the untouched rule
- 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
This commit is contained in:
52
admin/src/components/form/richeditor/lossy.ts
Normal file
52
admin/src/components/form/richeditor/lossy.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
// 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))
|
||||
}
|
||||
Reference in New Issue
Block a user