@openleaf-editor/core 0.1.0-beta.2 → 0.1.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +127 -0
- package/dist/autolink.d.ts +7 -4
- package/dist/autolink.d.ts.map +1 -1
- package/dist/autolink.js +90 -11
- package/dist/autolink.js.map +1 -1
- package/dist/command-helpers.d.ts +8 -0
- package/dist/command-helpers.d.ts.map +1 -0
- package/dist/command-helpers.js +36 -0
- package/dist/command-helpers.js.map +1 -0
- package/dist/commands.d.ts +21 -63
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +430 -346
- package/dist/commands.js.map +1 -1
- package/dist/css.d.ts +1 -175
- package/dist/css.d.ts.map +1 -1
- package/dist/css.js +1 -516
- package/dist/css.js.map +1 -1
- package/dist/disclosure.d.ts +21 -0
- package/dist/disclosure.d.ts.map +1 -0
- package/dist/disclosure.js +144 -0
- package/dist/disclosure.js.map +1 -0
- package/dist/elements.d.ts +2 -0
- package/dist/elements.d.ts.map +1 -0
- package/dist/elements.js +2 -0
- package/dist/elements.js.map +1 -0
- package/dist/embed.d.ts +1 -51
- package/dist/embed.d.ts.map +1 -1
- package/dist/embed.js +1 -115
- package/dist/embed.js.map +1 -1
- package/dist/errors.d.ts +38 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +40 -0
- package/dist/errors.js.map +1 -0
- package/dist/extensions.d.ts +46 -5
- package/dist/extensions.d.ts.map +1 -1
- package/dist/extensions.js +272 -52
- package/dist/extensions.js.map +1 -1
- package/dist/figure-drag.d.ts +26 -0
- package/dist/figure-drag.d.ts.map +1 -0
- package/dist/figure-drag.js +75 -0
- package/dist/figure-drag.js.map +1 -0
- package/dist/formats.d.ts.map +1 -1
- package/dist/formats.js +19 -7
- package/dist/formats.js.map +1 -1
- package/dist/gapcursor.d.ts +14 -0
- package/dist/gapcursor.d.ts.map +1 -0
- package/dist/gapcursor.js +17 -0
- package/dist/gapcursor.js.map +1 -0
- package/dist/html.d.ts +32 -2
- package/dist/html.d.ts.map +1 -1
- package/dist/html.js +185 -31
- package/dist/html.js.map +1 -1
- package/dist/index.d.ts +23 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -6
- package/dist/index.js.map +1 -1
- package/dist/insert-commands.d.ts +172 -0
- package/dist/insert-commands.d.ts.map +1 -0
- package/dist/insert-commands.js +560 -0
- package/dist/insert-commands.js.map +1 -0
- package/dist/isolating-selection.d.ts +59 -0
- package/dist/isolating-selection.d.ts.map +1 -0
- package/dist/isolating-selection.js +251 -0
- package/dist/isolating-selection.js.map +1 -0
- package/dist/noneditable.d.ts.map +1 -1
- package/dist/noneditable.js +18 -4
- package/dist/noneditable.js.map +1 -1
- package/dist/plugins.d.ts +20 -2
- package/dist/plugins.d.ts.map +1 -1
- package/dist/plugins.js +27 -2
- package/dist/plugins.js.map +1 -1
- package/dist/preserve.d.ts +48 -17
- package/dist/preserve.d.ts.map +1 -1
- package/dist/preserve.js +214 -43
- package/dist/preserve.js.map +1 -1
- package/dist/schema.d.ts +15 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +157 -28
- package/dist/schema.js.map +1 -1
- package/dist/structure.d.ts +32 -0
- package/dist/structure.d.ts.map +1 -1
- package/dist/structure.js +53 -10
- package/dist/structure.js.map +1 -1
- package/dist/tables.d.ts +47 -1
- package/dist/tables.d.ts.map +1 -1
- package/dist/tables.js +417 -40
- package/dist/tables.js.map +1 -1
- package/dist/testing.d.ts +3 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +3 -0
- package/dist/testing.js.map +1 -0
- package/dist/tokens.d.ts +8 -0
- package/dist/tokens.d.ts.map +1 -1
- package/dist/tokens.js +54 -6
- package/dist/tokens.js.map +1 -1
- package/dist/url.d.ts +1 -25
- package/dist/url.d.ts.map +1 -1
- package/dist/url.js +1 -77
- package/dist/url.js.map +1 -1
- package/dist/visual-aids.d.ts +11 -1
- package/dist/visual-aids.d.ts.map +1 -1
- package/dist/visual-aids.js +124 -36
- package/dist/visual-aids.js.map +1 -1
- package/package.json +17 -1
package/dist/preserve.js
CHANGED
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
* and is preserved intact.
|
|
28
28
|
*/
|
|
29
29
|
import { isFullyModelledStyle, safeLang } from './css.js';
|
|
30
|
-
import {
|
|
30
|
+
import { DROP_WITH_CONTENT } from './elements.js';
|
|
31
|
+
import { URL_ATTRIBUTES, isEventHandlerAttribute, isNeverCarriedAttribute, isSafeUrl, } from './url.js';
|
|
31
32
|
/**
|
|
32
33
|
* Elements that are never preserved, and whose contents are discarded with them.
|
|
33
34
|
*
|
|
@@ -45,27 +46,36 @@ import { URL_ATTRIBUTES, isEventHandlerAttribute, isSafeUrl } from './url.js';
|
|
|
45
46
|
* `ignore` rather than `skip`: the element AND its contents go. Skipping would
|
|
46
47
|
* unwrap `<script>alert(1)</script>` into the literal text "alert(1)" appearing
|
|
47
48
|
* in the document, which is a different kind of wrong.
|
|
49
|
+
*
|
|
50
|
+
* Spread from `@openleaf-editor/content-policy` rather than written out here.
|
|
51
|
+
* This list and the sanitize policy's `dropWithContent` are the same decision
|
|
52
|
+
* made in two packages, they were maintained by hand, and they drifted: `<svg>`
|
|
53
|
+
* and `<math>` were on the sanitizer's list and missing from this one, so the
|
|
54
|
+
* editor stored exactly the markup the server was configured to delete. Sharing
|
|
55
|
+
* the constant is what makes that drift impossible rather than merely unlikely.
|
|
56
|
+
*
|
|
57
|
+
* Exported so the divergence can be asserted in a test as well as prevented by
|
|
58
|
+
* construction. Not re-exported from the package index: it is an internal
|
|
59
|
+
* invariant, and the shared list itself is public in content-policy.
|
|
48
60
|
*/
|
|
49
|
-
const NEVER_PRESERVE = [
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
'template',
|
|
68
|
-
];
|
|
61
|
+
export const NEVER_PRESERVE = [...DROP_WITH_CONTENT];
|
|
62
|
+
/**
|
|
63
|
+
* Tags that must not survive *inside* a preserved subtree either.
|
|
64
|
+
*
|
|
65
|
+
* `iframe` is here and not in `NEVER_PRESERVE` because the two answer different
|
|
66
|
+
* questions. At the top level an iframe is claimed by the modelled embed node
|
|
67
|
+
* when its `src` is an allowlisted player, and ignored otherwise -- a
|
|
68
|
+
* priority-100 drop rule would outrank the embed node and delete legitimate
|
|
69
|
+
* players. Inside preserved markup there is no such question: the subtree is
|
|
70
|
+
* stored as an opaque string and re-emitted verbatim, so nothing re-checks the
|
|
71
|
+
* frame on the way out.
|
|
72
|
+
*
|
|
73
|
+
* That gap was the bypass. `<iframe src="https://evil.example/">` on its own was
|
|
74
|
+
* dropped; wrapped in a `<div class="c">` it round-tripped byte-identical, with
|
|
75
|
+
* `allow="camera; microphone; geolocation"` intact. One attribute on a wrapper
|
|
76
|
+
* defeated both the host allowlist and the permissions filter.
|
|
77
|
+
*/
|
|
78
|
+
const NEVER_INSIDE_PRESERVED = new Set([...NEVER_PRESERVE, 'iframe']);
|
|
69
79
|
/** Parse rules that drop dangerous elements before any other rule sees them. */
|
|
70
80
|
const dropRules = NEVER_PRESERVE.map((tag) => ({ tag, ignore: true, priority: 100 }));
|
|
71
81
|
/**
|
|
@@ -140,7 +150,7 @@ export function scrub(el) {
|
|
|
140
150
|
const clone = el.cloneNode(true);
|
|
141
151
|
const visit = (node) => {
|
|
142
152
|
for (const child of Array.from(node.children)) {
|
|
143
|
-
if (
|
|
153
|
+
if (NEVER_INSIDE_PRESERVED.has(child.nodeName.toLowerCase())) {
|
|
144
154
|
child.remove();
|
|
145
155
|
continue;
|
|
146
156
|
}
|
|
@@ -151,6 +161,13 @@ export function scrub(el) {
|
|
|
151
161
|
node.removeAttribute(attr.name);
|
|
152
162
|
continue;
|
|
153
163
|
}
|
|
164
|
+
// Before the URL question, not as a case of it: `srcdoc` is a document
|
|
165
|
+
// rather than a URL, and asking a scheme checker about a document gets
|
|
166
|
+
// "no scheme, therefore relative, therefore safe".
|
|
167
|
+
if (isNeverCarriedAttribute(attr.name)) {
|
|
168
|
+
node.removeAttribute(attr.name);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
154
171
|
if (URL_ATTRIBUTES.has(attr.name.toLowerCase()) && !isSafeUrl(attr.value)) {
|
|
155
172
|
node.removeAttribute(attr.name);
|
|
156
173
|
}
|
|
@@ -231,13 +248,63 @@ export function isLosslesslyUnwrappable(el) {
|
|
|
231
248
|
}
|
|
232
249
|
return true;
|
|
233
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Parsed preserved markup, per Document, keyed on the exact stored string.
|
|
253
|
+
*
|
|
254
|
+
* `toDOM` runs on every render of every preserved atom, and an HTML parse is the
|
|
255
|
+
* most expensive thing in it: a document that is half preserved markup spent
|
|
256
|
+
* roughly 4x as long serializing as the same document in plain paragraphs.
|
|
257
|
+
*
|
|
258
|
+
* Caching one is only sound because the key is the whole input. `html` is a node
|
|
259
|
+
* attribute, ProseMirror nodes are immutable, and the string was produced by
|
|
260
|
+
* `scrub` before it was ever stored -- so two calls with the same string must
|
|
261
|
+
* produce the same element, and there is no edit that can invalidate an entry
|
|
262
|
+
* without changing the key.
|
|
263
|
+
*
|
|
264
|
+
* Three properties this must keep, none of them optional:
|
|
265
|
+
*
|
|
266
|
+
* A CLONE every time. Handing out the cached node would let one caller's
|
|
267
|
+
* mutation -- ProseMirror appending it, a decoration setting an attribute --
|
|
268
|
+
* poison every later render of that markup.
|
|
269
|
+
*
|
|
270
|
+
* PER DOCUMENT. Serialization can target a Document that is not the global
|
|
271
|
+
* one, and a node has to be owned by the document it is going into. Scoping by
|
|
272
|
+
* Document also means the entries die with it, so a throwaway serialization
|
|
273
|
+
* document is still collectable.
|
|
274
|
+
*
|
|
275
|
+
* BOUNDED. The keys are document content, so an unbounded map is a leak that
|
|
276
|
+
* grows with what the user pastes. A cap with clear-on-overflow keeps it flat;
|
|
277
|
+
* very large strings are not cached at all, because they are both the least
|
|
278
|
+
* likely to repeat and the most expensive to hold.
|
|
279
|
+
*/
|
|
280
|
+
const PARSE_CACHE_ENTRIES = 256;
|
|
281
|
+
const PARSE_CACHE_MAX_LENGTH = 4096;
|
|
282
|
+
const parseCaches = new WeakMap();
|
|
234
283
|
/** Rebuild a DOM element from stored markup. `<template>` is used because
|
|
235
284
|
* its parsing context permits otherwise-illegal fragments such as a bare
|
|
236
285
|
* `<tr>`, which a `<div>` container would silently discard. */
|
|
237
286
|
function elementFromHtml(html, doc) {
|
|
287
|
+
let cache = parseCaches.get(doc);
|
|
288
|
+
const hit = cache?.get(html);
|
|
289
|
+
if (hit)
|
|
290
|
+
return hit.cloneNode(true);
|
|
238
291
|
const tpl = doc.createElement('template');
|
|
239
292
|
tpl.innerHTML = html;
|
|
240
|
-
|
|
293
|
+
const parsed = tpl.content.firstElementChild;
|
|
294
|
+
if (!parsed)
|
|
295
|
+
return null;
|
|
296
|
+
if (html.length > PARSE_CACHE_MAX_LENGTH)
|
|
297
|
+
return parsed;
|
|
298
|
+
if (!cache) {
|
|
299
|
+
cache = new Map();
|
|
300
|
+
parseCaches.set(doc, cache);
|
|
301
|
+
}
|
|
302
|
+
if (cache.size >= PARSE_CACHE_ENTRIES)
|
|
303
|
+
cache.clear();
|
|
304
|
+
// The master copy is the clone, so the element handed back is the freshly
|
|
305
|
+
// parsed one and the cache holds something no caller has ever touched.
|
|
306
|
+
cache.set(html, parsed.cloneNode(true));
|
|
307
|
+
return parsed;
|
|
241
308
|
}
|
|
242
309
|
/**
|
|
243
310
|
* ProseMirror's `toDOM` does not receive the `document` passed to
|
|
@@ -258,25 +325,6 @@ export function withSerializationDocument(doc, fn) {
|
|
|
258
325
|
serializationDocument = previous;
|
|
259
326
|
}
|
|
260
327
|
}
|
|
261
|
-
/**
|
|
262
|
-
* True while `serializeHtml` is running.
|
|
263
|
-
*
|
|
264
|
-
* A node whose `toDOM` needs to render differently for the editor than for the
|
|
265
|
-
* saved HTML has no other way to tell which one it is building. The table spec
|
|
266
|
-
* needs exactly that: a preserved `<caption>` must be `contenteditable="false"`
|
|
267
|
-
* on screen, because it sits inside the editable area but outside the node's
|
|
268
|
-
* `contentDOM`, and letting a caret into it means typing that ProseMirror will
|
|
269
|
-
* silently revert. That attribute must NOT reach the saved HTML, where it would
|
|
270
|
-
* be our editor scribbling on the author's markup.
|
|
271
|
-
*
|
|
272
|
-
* Stripping it afterwards was the other option and is worse: it cannot tell the
|
|
273
|
-
* attribute it just added from the same attribute in somebody's document -- the
|
|
274
|
-
* collision the preserved-element WeakSet above exists to avoid. Not emitting it
|
|
275
|
-
* at all has no such failure mode.
|
|
276
|
-
*/
|
|
277
|
-
export function isSerializing() {
|
|
278
|
-
return serializationDocument !== undefined;
|
|
279
|
-
}
|
|
280
328
|
/**
|
|
281
329
|
* The Document that serialization should build into.
|
|
282
330
|
*
|
|
@@ -377,6 +425,114 @@ export const unknownBlock = {
|
|
|
377
425
|
return rebuildOrCarry(node.attrs['html'], 'div');
|
|
378
426
|
},
|
|
379
427
|
};
|
|
428
|
+
/**
|
|
429
|
+
* Where the inline catch-all is allowed to fire.
|
|
430
|
+
*
|
|
431
|
+
* ProseMirror matches `context` against the parent node the content is being
|
|
432
|
+
* parsed INTO, and the naive list -- paragraph and heading -- described the
|
|
433
|
+
* wrong thing. It named the nodes that hold inline content, when what the rule
|
|
434
|
+
* needs to name is every node that can END UP holding it.
|
|
435
|
+
*
|
|
436
|
+
* `list_item` is `paragraph block*`, so its implicit paragraph does not exist
|
|
437
|
+
* until some inline content arrives to force it. An unknown element that is the
|
|
438
|
+
* FIRST child of an `<li>` therefore sees a context of `.../list_item/`, the
|
|
439
|
+
* inline rule declines, and `unknownBlock` claims a block-level atom inside a
|
|
440
|
+
* container whose content expression cannot hold one. ProseMirror's recovery is
|
|
441
|
+
* to close the list and emit the atom as a sibling, so
|
|
442
|
+
* `<ol><li><ins>a</ins></li><li>b</li></ol>` came back as an emptied `<ol>`, an
|
|
443
|
+
* escaped `<ins>`, and a `<ul>` -- the list's contents gone and its very type
|
|
444
|
+
* changed. The identical element with a single character of text before it
|
|
445
|
+
* round-tripped perfectly, which is what kept this invisible: position was the
|
|
446
|
+
* whole trigger.
|
|
447
|
+
*
|
|
448
|
+
* `summary` and `figcaption` fail the same way for the same reason, and
|
|
449
|
+
* `blockquote`, `table_cell` and `table_header` are listed so that a first-child
|
|
450
|
+
* unknown *inline* element is wrapped in a paragraph exactly as it is when text
|
|
451
|
+
* precedes it. Those three can also hold a block atom legally. Wrapping a
|
|
452
|
+
* p-closing element (`div`, `section`, …) in that paragraph is the other
|
|
453
|
+
* failure: the HTML parser closes the `<p>` on the next parse, leaves empty
|
|
454
|
+
* paragraphs on both sides, and the document grows by two blanks on every save.
|
|
455
|
+
* `unknownInline` declines those tags so `unknownBlock` claims them instead.
|
|
456
|
+
*
|
|
457
|
+
* Naming containers explicitly rather than dropping the context altogether:
|
|
458
|
+
* without it the inline rule would outrank `unknownBlock` everywhere, and a
|
|
459
|
+
* genuinely block-level unknown element at the top of the document would become
|
|
460
|
+
* an inline atom inside a paragraph the author never had.
|
|
461
|
+
*/
|
|
462
|
+
const INLINE_CONTEXT = [
|
|
463
|
+
'paragraph/',
|
|
464
|
+
'heading/',
|
|
465
|
+
'summary/',
|
|
466
|
+
'figcaption/',
|
|
467
|
+
'list_item/',
|
|
468
|
+
'table_cell/',
|
|
469
|
+
'table_header/',
|
|
470
|
+
'blockquote/',
|
|
471
|
+
].join('|');
|
|
472
|
+
/**
|
|
473
|
+
* Start tags that close an open `<p>` in the HTML "in body" insertion mode.
|
|
474
|
+
*
|
|
475
|
+
* Serializing one of these as an inline atom wraps it in `<p>…</p>`. The next
|
|
476
|
+
* parse cannot keep it there, so it splits into an empty paragraph, the
|
|
477
|
+
* element, and another empty paragraph -- and the following save wraps it
|
|
478
|
+
* again. The same unbounded growth `<plaintext>` caused by having no end tag.
|
|
479
|
+
*
|
|
480
|
+
* Shared with anything else that must not claim a p-closing element as inline
|
|
481
|
+
* (`figcaption` being inline is the other door into this). Custom elements are
|
|
482
|
+
* not on the list: they do not close a `<p>`, so `<drupal-media>` inside a
|
|
483
|
+
* blockquote is correctly an inline atom.
|
|
484
|
+
*
|
|
485
|
+
* Source: WHATWG HTML parsing, "in body" -- every start tag that runs
|
|
486
|
+
* "if the stack of open elements has a p element in button scope, then close
|
|
487
|
+
* a p element" before inserting.
|
|
488
|
+
*/
|
|
489
|
+
export const CLOSES_OPEN_P = new Set([
|
|
490
|
+
'address',
|
|
491
|
+
'article',
|
|
492
|
+
'aside',
|
|
493
|
+
'blockquote',
|
|
494
|
+
'center',
|
|
495
|
+
'details',
|
|
496
|
+
'dialog',
|
|
497
|
+
'dir',
|
|
498
|
+
'div',
|
|
499
|
+
'dl',
|
|
500
|
+
'dt',
|
|
501
|
+
'dd',
|
|
502
|
+
'fieldset',
|
|
503
|
+
'figcaption',
|
|
504
|
+
'figure',
|
|
505
|
+
'footer',
|
|
506
|
+
'header',
|
|
507
|
+
'hgroup',
|
|
508
|
+
'main',
|
|
509
|
+
'menu',
|
|
510
|
+
'nav',
|
|
511
|
+
'ol',
|
|
512
|
+
'p',
|
|
513
|
+
'search',
|
|
514
|
+
'section',
|
|
515
|
+
'summary',
|
|
516
|
+
'ul',
|
|
517
|
+
'h1',
|
|
518
|
+
'h2',
|
|
519
|
+
'h3',
|
|
520
|
+
'h4',
|
|
521
|
+
'h5',
|
|
522
|
+
'h6',
|
|
523
|
+
'listing',
|
|
524
|
+
'plaintext',
|
|
525
|
+
'pre',
|
|
526
|
+
'form',
|
|
527
|
+
'li',
|
|
528
|
+
'button',
|
|
529
|
+
'table',
|
|
530
|
+
'hr',
|
|
531
|
+
'xmp',
|
|
532
|
+
'applet',
|
|
533
|
+
'marquee',
|
|
534
|
+
'object',
|
|
535
|
+
]);
|
|
380
536
|
/**
|
|
381
537
|
* Inline preserved content, for unrecognised markup appearing inside a
|
|
382
538
|
* paragraph -- the `<o:p>` and `<w:sdt>` debris of a Word paste, custom
|
|
@@ -401,11 +557,26 @@ export const unknownInline = {
|
|
|
401
557
|
// the block rule cannot claim inline debris and split the paragraph
|
|
402
558
|
// that contained it.
|
|
403
559
|
priority: 1,
|
|
404
|
-
context:
|
|
560
|
+
context: INLINE_CONTEXT,
|
|
405
561
|
getAttrs(dom) {
|
|
406
562
|
const el = dom;
|
|
407
563
|
if (isLosslesslyUnwrappable(el))
|
|
408
564
|
return false;
|
|
565
|
+
// A block-level element inside a paragraph-holding container must not
|
|
566
|
+
// become an inline atom: emitting it inside `<p>` is markup the HTML
|
|
567
|
+
// parser will not re-parse as itself. Declining lets unknownBlock
|
|
568
|
+
// claim it, which blockquote (`block+`) and list_item (`paragraph
|
|
569
|
+
// block*`) can hold. `<ins>` and custom elements stay on this path.
|
|
570
|
+
// `figcaption` is on CLOSES_OPEN_P, but declining it in summary/heading
|
|
571
|
+
// sends it to unknownBlock, which those containers cannot hold.
|
|
572
|
+
const tag = el.nodeName.toLowerCase();
|
|
573
|
+
if (tag === 'figcaption') {
|
|
574
|
+
if (el.parentElement?.nodeName.toLowerCase() === 'p')
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
else if (CLOSES_OPEN_P.has(tag)) {
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
409
580
|
return { html: scrub(el), tag: el.nodeName.toLowerCase() };
|
|
410
581
|
},
|
|
411
582
|
},
|
package/dist/preserve.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preserve.js","sourceRoot":"","sources":["../src/preserve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AACzD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"preserve.js","sourceRoot":"","sources":["../src/preserve.ts"],"names":[],"mappings":"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"}
|
package/dist/schema.d.ts
CHANGED
|
@@ -8,6 +8,21 @@
|
|
|
8
8
|
* there rather than at the package boundary.
|
|
9
9
|
*/
|
|
10
10
|
import { Schema, type MarkSpec, type NodeSpec } from 'prosemirror-model';
|
|
11
|
+
/**
|
|
12
|
+
* The `start` of an ordered list, or null when the attribute is not a number.
|
|
13
|
+
*
|
|
14
|
+
* `Number('abc')` is `NaN`, and `NaN` is a perfectly good value to store on a
|
|
15
|
+
* node -- so `<ol start="abc">` was stored as `NaN` and serialized as the string
|
|
16
|
+
* `start="NaN"`, turning a value the browser ignores into one it still ignores
|
|
17
|
+
* but that is now in the customer's database with our fingerprints on it.
|
|
18
|
+
* Returning null instead lets extensions.ts carry the original spelling as
|
|
19
|
+
* residue, so the attribute survives without being interpreted.
|
|
20
|
+
*
|
|
21
|
+
* Exported so that the carry mechanism asks the same question this does. Two
|
|
22
|
+
* copies of "is this a valid start" is how they would disagree and emit the
|
|
23
|
+
* attribute twice, or neither time.
|
|
24
|
+
*/
|
|
25
|
+
export declare function listStart(value: string | null | undefined): number | null;
|
|
11
26
|
/** The base node specs. Extensions are appended to these. */
|
|
12
27
|
export declare const coreNodes: Record<string, NodeSpec>;
|
|
13
28
|
/** The base mark specs. */
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,MAAM,EAAkC,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,MAAM,EAAkC,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAqHxG;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAIzE;AA8BD,6DAA6D;AAC7D,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CA4L9C,CAAA;AAgDD,2BAA2B;AAC3B,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAwQ9C,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,wBAAqD,CAAA"}
|
package/dist/schema.js
CHANGED
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
* there rather than at the package boundary.
|
|
9
9
|
*/
|
|
10
10
|
import { Schema } from 'prosemirror-model';
|
|
11
|
-
import { applyStyleAttribute, indentCss, indentLevels, isFullyModelledStyle, parseDeclarations, safeAlign, safeColor, safeDir, safeFontFamily, safeFontSize, safeLang, safeLineHeight, safeListStyle, serializeDeclarations } from './css.js';
|
|
12
|
-
import { serializationTarget, unknownBlock, unknownInline } from './preserve.js';
|
|
13
|
-
import { audio, details, figcaption, figure, iframe, imageDomAttrs, imageParseAttrs, named_anchor, page_break, summary, video, } from './structure.js';
|
|
11
|
+
import { INLINE_STYLE_PROPERTIES, applyStyleAttribute, indentCss, indentLevels, isFullyModelledStyle, modelledValue, parseDeclarations, safeAlign, safeColor, safeDir, safeFontFamily, safeFontSize, safeLang, safeLineHeight, safeListStyle, serializeDeclarations } from './css.js';
|
|
12
|
+
import { isLosslesslyUnwrappable, serializationTarget, unknownBlock, unknownInline } from './preserve.js';
|
|
13
|
+
import { audio, details, figcaption, figure, iframe, imageDomAttrs, imageParseAttrs, isEmptyNamedAnchorElement, named_anchor, page_break, summary, video, } from './structure.js';
|
|
14
14
|
import { table, table_cell, table_header, table_row } from './tables.js';
|
|
15
15
|
import { safeId } from './tokens.js';
|
|
16
16
|
import { isSafeUrl } from './url.js';
|
|
@@ -108,6 +108,26 @@ function textBlockToDOM(tag, attrs) {
|
|
|
108
108
|
return elementWithStyle(tag, domAttrs);
|
|
109
109
|
return Object.keys(domAttrs).length > 0 ? [tag, domAttrs, 0] : [tag, 0];
|
|
110
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* The `start` of an ordered list, or null when the attribute is not a number.
|
|
113
|
+
*
|
|
114
|
+
* `Number('abc')` is `NaN`, and `NaN` is a perfectly good value to store on a
|
|
115
|
+
* node -- so `<ol start="abc">` was stored as `NaN` and serialized as the string
|
|
116
|
+
* `start="NaN"`, turning a value the browser ignores into one it still ignores
|
|
117
|
+
* but that is now in the customer's database with our fingerprints on it.
|
|
118
|
+
* Returning null instead lets extensions.ts carry the original spelling as
|
|
119
|
+
* residue, so the attribute survives without being interpreted.
|
|
120
|
+
*
|
|
121
|
+
* Exported so that the carry mechanism asks the same question this does. Two
|
|
122
|
+
* copies of "is this a valid start" is how they would disagree and emit the
|
|
123
|
+
* attribute twice, or neither time.
|
|
124
|
+
*/
|
|
125
|
+
export function listStart(value) {
|
|
126
|
+
if (value === null || value === undefined)
|
|
127
|
+
return null;
|
|
128
|
+
const parsed = Number.parseInt(value.trim(), 10);
|
|
129
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
130
|
+
}
|
|
111
131
|
function listAttrs(el, ordered) {
|
|
112
132
|
const declarations = parseDeclarations(el.getAttribute('style'));
|
|
113
133
|
const fromStyle = safeListStyle(declarations.get('list-style-type') ?? null);
|
|
@@ -115,8 +135,7 @@ function listAttrs(el, ordered) {
|
|
|
115
135
|
const listStyle = fromStyle ?? fromType;
|
|
116
136
|
if (!ordered)
|
|
117
137
|
return { listStyle };
|
|
118
|
-
|
|
119
|
-
return { start: start ? Number(start) : 1, listStyle };
|
|
138
|
+
return { start: listStart(el.getAttribute('start')) ?? 1, listStyle };
|
|
120
139
|
}
|
|
121
140
|
function listToDOM(tag, attrs) {
|
|
122
141
|
const out = {};
|
|
@@ -316,6 +335,55 @@ export const coreNodes = {
|
|
|
316
335
|
unknown_block: unknownBlock,
|
|
317
336
|
unknown_inline: unknownInline,
|
|
318
337
|
};
|
|
338
|
+
/**
|
|
339
|
+
* True when a `<span>` carries nothing but formatting the mark set can express.
|
|
340
|
+
*
|
|
341
|
+
* The question a TAG rule on a mark has to ask, and the reason it is not
|
|
342
|
+
* `isFullyModelledStyle` alone: `background` is the shorthand Word writes, and
|
|
343
|
+
* the rule below reads it through `el.style.backgroundColor`, so the CSSOM has
|
|
344
|
+
* already expanded it and it is genuinely modelled even though the declaration
|
|
345
|
+
* name is not in the modelled list. Refusing it would turn every highlighted
|
|
346
|
+
* Word paste into an opaque atom.
|
|
347
|
+
*
|
|
348
|
+
* Any other attribute, or any declaration a mark cannot hold, and the answer is
|
|
349
|
+
* no -- because a mark that claims the element has nowhere to keep the rest.
|
|
350
|
+
*/
|
|
351
|
+
function isSpanOnlyStyling(el) {
|
|
352
|
+
for (const attr of Array.from(el.attributes)) {
|
|
353
|
+
if (attr.name !== 'style')
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
const declarations = parseDeclarations(el.getAttribute('style'));
|
|
357
|
+
if (declarations.size === 0)
|
|
358
|
+
return false;
|
|
359
|
+
for (const [name, value] of declarations) {
|
|
360
|
+
if (name === 'background')
|
|
361
|
+
continue;
|
|
362
|
+
if (!INLINE_STYLE_PROPERTIES.includes(name))
|
|
363
|
+
return false;
|
|
364
|
+
if (modelledValue(name, value) === null)
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* True when a colour/font mark must not fire on this element.
|
|
371
|
+
*
|
|
372
|
+
* Those marks used to be `style:` rules. A style rule does not consume the
|
|
373
|
+
* element, which is why `<p style="color:red">` and a `<div>` wrapper still
|
|
374
|
+
* yield a mark -- and why a `<span style="color:red" class="hl">` yielded
|
|
375
|
+
* BOTH the mark and a preserved atom, nested. The preservation catch-all
|
|
376
|
+
* keeps any span `isLosslesslyUnwrappable` refuses, so the mark has to
|
|
377
|
+
* refuse the same elements.
|
|
378
|
+
*
|
|
379
|
+
* The replacement is a `tag: '*'` rule with `consuming: false`: same
|
|
380
|
+
* "applies to whoever carries the declaration" behaviour, but `getAttrs`
|
|
381
|
+
* receives the element and can decline. Hosts the schema models are not
|
|
382
|
+
* spans the catch-all would keep, so they still produce the mark.
|
|
383
|
+
*/
|
|
384
|
+
function declineStyleMarkOnPreservedAtom(el) {
|
|
385
|
+
return el.nodeName.toLowerCase() === 'span' && !isLosslesslyUnwrappable(el);
|
|
386
|
+
}
|
|
319
387
|
/** The base mark specs. */
|
|
320
388
|
export const coreMarks = {
|
|
321
389
|
strong: {
|
|
@@ -351,26 +419,36 @@ export const coreMarks = {
|
|
|
351
419
|
* would have the second command overwrite the first's value with a default.
|
|
352
420
|
* Two marks compose the way the toolbar's two controls do.
|
|
353
421
|
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
-
*
|
|
422
|
+
* Parsed through a `tag: '*'` rule that does not consume the element, not a
|
|
423
|
+
* `span[style]` tag rule. The un-consuming match is load-bearing: it is how
|
|
424
|
+
* `<span style="color:red">`, `<p style="color:red">` and a `<div>` wrapper
|
|
425
|
+
* all yield the same mark -- whereas a `span[style]` rule would claim the
|
|
426
|
+
* span, drop it, and quietly lose any other declaration on it.
|
|
427
|
+
*
|
|
428
|
+
* It is not a `style:` rule either. Those also do not consume, but they
|
|
429
|
+
* never see the element, so they cannot decline when preservation is going
|
|
430
|
+
* to keep it. `declineStyleMarkOnPreservedAtom` is that decline.
|
|
359
431
|
*
|
|
360
432
|
* The value is normalized by `safeColor`, which folds `rgb(255, 0, 0)` back to
|
|
361
|
-
* `#ff0000`. That is not cosmetic:
|
|
362
|
-
*
|
|
363
|
-
* without the fold every hex colour in an archive would be
|
|
364
|
-
* the first save.
|
|
433
|
+
* `#ff0000`. That is not cosmetic: the rule still reads through the CSSOM
|
|
434
|
+
* (`el.style.color`), which returns the functional form for an authored hex
|
|
435
|
+
* colour, so without the fold every hex colour in an archive would be
|
|
436
|
+
* rewritten longer on the first save.
|
|
365
437
|
*/
|
|
366
438
|
text_color: {
|
|
367
439
|
attrs: { color: {} },
|
|
368
440
|
parseDOM: [
|
|
369
441
|
{
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
442
|
+
tag: '*',
|
|
443
|
+
consuming: false,
|
|
444
|
+
getAttrs(dom) {
|
|
445
|
+
const el = dom;
|
|
446
|
+
const color = safeColor(el.style.color);
|
|
447
|
+
if (!color)
|
|
448
|
+
return false;
|
|
449
|
+
if (declineStyleMarkOnPreservedAtom(el))
|
|
450
|
+
return false;
|
|
451
|
+
return { color };
|
|
374
452
|
},
|
|
375
453
|
},
|
|
376
454
|
{
|
|
@@ -403,6 +481,17 @@ export const coreMarks = {
|
|
|
403
481
|
tag: 'span',
|
|
404
482
|
getAttrs(dom) {
|
|
405
483
|
const el = dom;
|
|
484
|
+
// Only when the span is nothing but formatting this mark can hold.
|
|
485
|
+
// A tag rule CONSUMES the element. Character marks now carry leftover
|
|
486
|
+
// attributes the way nodes do, but this rule still declines a span
|
|
487
|
+
// that is more than a highlight: claiming
|
|
488
|
+
// `<span class="hl" style="background-color:#ffff00;letter-spacing:1px">`
|
|
489
|
+
// would swallow declarations other style-marks and the preservation
|
|
490
|
+
// layer are meant to see. Declining leaves the element whole. This is
|
|
491
|
+
// the same bargain `font[color]` and `span[lang]` already strike a
|
|
492
|
+
// few lines away.
|
|
493
|
+
if (!isSpanOnlyStyling(el))
|
|
494
|
+
return false;
|
|
406
495
|
const fromAttr = parseDeclarations(el.getAttribute('style')).get('background-color');
|
|
407
496
|
const color = safeColor(el.style.backgroundColor) ?? safeColor(fromAttr);
|
|
408
497
|
return color ? { color } : false;
|
|
@@ -415,10 +504,16 @@ export const coreMarks = {
|
|
|
415
504
|
attrs: { family: {} },
|
|
416
505
|
parseDOM: [
|
|
417
506
|
{
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
507
|
+
tag: '*',
|
|
508
|
+
consuming: false,
|
|
509
|
+
getAttrs(dom) {
|
|
510
|
+
const el = dom;
|
|
511
|
+
const family = safeFontFamily(el.style.fontFamily);
|
|
512
|
+
if (!family)
|
|
513
|
+
return false;
|
|
514
|
+
if (declineStyleMarkOnPreservedAtom(el))
|
|
515
|
+
return false;
|
|
516
|
+
return { family };
|
|
422
517
|
},
|
|
423
518
|
},
|
|
424
519
|
{
|
|
@@ -440,10 +535,16 @@ export const coreMarks = {
|
|
|
440
535
|
attrs: { size: {} },
|
|
441
536
|
parseDOM: [
|
|
442
537
|
{
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
538
|
+
tag: '*',
|
|
539
|
+
consuming: false,
|
|
540
|
+
getAttrs(dom) {
|
|
541
|
+
const el = dom;
|
|
542
|
+
const size = safeFontSize(el.style.fontSize);
|
|
543
|
+
if (!size)
|
|
544
|
+
return false;
|
|
545
|
+
if (declineStyleMarkOnPreservedAtom(el))
|
|
546
|
+
return false;
|
|
547
|
+
return { size };
|
|
447
548
|
},
|
|
448
549
|
},
|
|
449
550
|
],
|
|
@@ -460,6 +561,8 @@ export const coreMarks = {
|
|
|
460
561
|
toDOM: () => ['sup', 0],
|
|
461
562
|
},
|
|
462
563
|
language: {
|
|
564
|
+
// Content, not decoration -- the same claim `dir` makes as a node
|
|
565
|
+
// attribute. `clearFormatting` keeps this mark for that reason.
|
|
463
566
|
attrs: { lang: {} },
|
|
464
567
|
inclusive: false,
|
|
465
568
|
parseDOM: [
|
|
@@ -485,7 +588,9 @@ export const coreMarks = {
|
|
|
485
588
|
},
|
|
486
589
|
link: {
|
|
487
590
|
attrs: {
|
|
488
|
-
|
|
591
|
+
// Null when the mark is a wrapped named destination (`<a id>` with
|
|
592
|
+
// text, no href). Empty `<a id></a>` is the `named_anchor` atom.
|
|
593
|
+
href: { default: null },
|
|
489
594
|
title: { default: null },
|
|
490
595
|
target: { default: null },
|
|
491
596
|
rel: { default: null },
|
|
@@ -511,10 +616,34 @@ export const coreMarks = {
|
|
|
511
616
|
};
|
|
512
617
|
},
|
|
513
618
|
},
|
|
619
|
+
{
|
|
620
|
+
tag: 'a[id]',
|
|
621
|
+
getAttrs(dom) {
|
|
622
|
+
const el = dom;
|
|
623
|
+
if (el.hasAttribute('href'))
|
|
624
|
+
return false;
|
|
625
|
+
const id = safeId(el.getAttribute('id'));
|
|
626
|
+
if (!id)
|
|
627
|
+
return false;
|
|
628
|
+
// Empty `<a id>` belongs to `named_anchor`. This rule is the
|
|
629
|
+
// wrapped-text spelling, so the heading stays in the document.
|
|
630
|
+
if (isEmptyNamedAnchorElement(el))
|
|
631
|
+
return false;
|
|
632
|
+
return {
|
|
633
|
+
href: null,
|
|
634
|
+
title: el.getAttribute('title'),
|
|
635
|
+
target: el.getAttribute('target'),
|
|
636
|
+
rel: el.getAttribute('rel'),
|
|
637
|
+
id,
|
|
638
|
+
};
|
|
639
|
+
},
|
|
640
|
+
},
|
|
514
641
|
],
|
|
515
642
|
toDOM(node) {
|
|
516
643
|
const { href, title, target, rel, id } = node.attrs;
|
|
517
|
-
const attrs = {
|
|
644
|
+
const attrs = {};
|
|
645
|
+
if (href !== null)
|
|
646
|
+
attrs['href'] = href;
|
|
518
647
|
if (title !== null)
|
|
519
648
|
attrs['title'] = title;
|
|
520
649
|
if (target !== null)
|