@aicayzer/inkkit 0.3.6 → 0.4.1
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/dist/chunks/{core-DtLNFuDg.js → core-CisgnAp-.js} +2023 -125
- package/dist/chunks/{highlight-wulvkYDg.js → highlight-CqtvWKCp.js} +1 -1
- package/dist/core.d.ts +1 -1
- package/dist/core.js +1 -1
- package/dist/editor.d.ts +37 -19
- package/dist/formats/convert.d.ts +17 -0
- package/dist/formats/index.d.ts +5 -3
- package/dist/formats/rich-text-import.d.ts +11 -0
- package/dist/formats/rich-text-model.d.ts +56 -0
- package/dist/formats/rich-text-prose.d.ts +23 -0
- package/dist/formats/rich-text.d.ts +5 -0
- package/dist/formats/types.d.ts +40 -4
- package/dist/index.js +1 -1
- package/dist/types.d.ts +94 -7
- package/package.json +1 -1
|
@@ -6759,7 +6759,7 @@ function tokenizeThematicBreak(effects, ok, nok) {
|
|
|
6759
6759
|
* } from 'micromark-util-types'
|
|
6760
6760
|
*/
|
|
6761
6761
|
/** @type {Construct} */
|
|
6762
|
-
var list$
|
|
6762
|
+
var list$4 = {
|
|
6763
6763
|
continuation: { tokenize: tokenizeListContinuation },
|
|
6764
6764
|
exit: tokenizeListEnd,
|
|
6765
6765
|
name: "list",
|
|
@@ -6879,7 +6879,7 @@ function tokenizeListContinuation(effects, ok, nok) {
|
|
|
6879
6879
|
function notInCurrentItem(code) {
|
|
6880
6880
|
self.containerState._closeFlow = true;
|
|
6881
6881
|
self.interrupt = void 0;
|
|
6882
|
-
return factorySpace(effects, effects.attempt(list$
|
|
6882
|
+
return factorySpace(effects, effects.attempt(list$4, ok, nok), "linePrefix", self.parser.constructs.disable.null.includes("codeIndented") ? void 0 : 4)(code);
|
|
6883
6883
|
}
|
|
6884
6884
|
}
|
|
6885
6885
|
/**
|
|
@@ -7313,19 +7313,19 @@ var constructs_exports = /* @__PURE__ */ __exportAll({
|
|
|
7313
7313
|
});
|
|
7314
7314
|
/** @satisfies {Extension['document']} */
|
|
7315
7315
|
var document$1 = {
|
|
7316
|
-
[42]: list$
|
|
7317
|
-
[43]: list$
|
|
7318
|
-
[45]: list$
|
|
7319
|
-
[48]: list$
|
|
7320
|
-
[49]: list$
|
|
7321
|
-
[50]: list$
|
|
7322
|
-
[51]: list$
|
|
7323
|
-
[52]: list$
|
|
7324
|
-
[53]: list$
|
|
7325
|
-
[54]: list$
|
|
7326
|
-
[55]: list$
|
|
7327
|
-
[56]: list$
|
|
7328
|
-
[57]: list$
|
|
7316
|
+
[42]: list$4,
|
|
7317
|
+
[43]: list$4,
|
|
7318
|
+
[45]: list$4,
|
|
7319
|
+
[48]: list$4,
|
|
7320
|
+
[49]: list$4,
|
|
7321
|
+
[50]: list$4,
|
|
7322
|
+
[51]: list$4,
|
|
7323
|
+
[52]: list$4,
|
|
7324
|
+
[53]: list$4,
|
|
7325
|
+
[54]: list$4,
|
|
7326
|
+
[55]: list$4,
|
|
7327
|
+
[56]: list$4,
|
|
7328
|
+
[57]: list$4,
|
|
7329
7329
|
[62]: blockQuote
|
|
7330
7330
|
};
|
|
7331
7331
|
/** @satisfies {Extension['contentInitial']} */
|
|
@@ -9452,10 +9452,10 @@ function configure(base, extension) {
|
|
|
9452
9452
|
case "extensions": break;
|
|
9453
9453
|
/* c8 ignore next 4 */
|
|
9454
9454
|
case "unsafe":
|
|
9455
|
-
list$
|
|
9455
|
+
list$3(base[key], extension[key]);
|
|
9456
9456
|
break;
|
|
9457
9457
|
case "join":
|
|
9458
|
-
list$
|
|
9458
|
+
list$3(base[key], extension[key]);
|
|
9459
9459
|
break;
|
|
9460
9460
|
case "handlers":
|
|
9461
9461
|
map$2(base[key], extension[key]);
|
|
@@ -9476,7 +9476,7 @@ function configure(base, extension) {
|
|
|
9476
9476
|
* @returns {undefined}
|
|
9477
9477
|
* Nothing.
|
|
9478
9478
|
*/
|
|
9479
|
-
function list$
|
|
9479
|
+
function list$3(left, right) {
|
|
9480
9480
|
if (right) left.push(...right);
|
|
9481
9481
|
}
|
|
9482
9482
|
/**
|
|
@@ -11096,7 +11096,7 @@ function checkRule(state) {
|
|
|
11096
11096
|
* @returns {string}
|
|
11097
11097
|
* Serialized markdown.
|
|
11098
11098
|
*/
|
|
11099
|
-
function list$
|
|
11099
|
+
function list$2(node, parent, state, info) {
|
|
11100
11100
|
const exit = state.enter("list");
|
|
11101
11101
|
const bulletCurrent = state.bulletCurrent;
|
|
11102
11102
|
/** @type {string} */
|
|
@@ -11416,7 +11416,7 @@ var handle = {
|
|
|
11416
11416
|
inlineCode,
|
|
11417
11417
|
link: link$1,
|
|
11418
11418
|
linkReference,
|
|
11419
|
-
list: list$
|
|
11419
|
+
list: list$2,
|
|
11420
11420
|
listItem,
|
|
11421
11421
|
paragraph: paragraph$1,
|
|
11422
11422
|
root: root$1,
|
|
@@ -12759,7 +12759,7 @@ function toMarkdown(tree, options) {
|
|
|
12759
12759
|
configure(state, settings);
|
|
12760
12760
|
if (state.options.tightDefinitions) state.join.push(joinDefinition);
|
|
12761
12761
|
state.handle = zwitch("type", {
|
|
12762
|
-
invalid,
|
|
12762
|
+
invalid: invalid$1,
|
|
12763
12763
|
unknown,
|
|
12764
12764
|
handlers: state.handlers
|
|
12765
12765
|
});
|
|
@@ -12806,7 +12806,7 @@ function toMarkdown(tree, options) {
|
|
|
12806
12806
|
* @throws {Error}
|
|
12807
12807
|
* Always.
|
|
12808
12808
|
*/
|
|
12809
|
-
function invalid(value) {
|
|
12809
|
+
function invalid$1(value) {
|
|
12810
12810
|
throw new Error("Cannot handle value `" + value + "`, expected node");
|
|
12811
12811
|
}
|
|
12812
12812
|
/**
|
|
@@ -17808,7 +17808,7 @@ var DOMParser$1 = class DOMParser$1 {
|
|
|
17808
17808
|
return schema.cached.domParser || (schema.cached.domParser = new DOMParser$1(schema, DOMParser$1.schemaRules(schema)));
|
|
17809
17809
|
}
|
|
17810
17810
|
};
|
|
17811
|
-
var blockTags = {
|
|
17811
|
+
var blockTags$1 = {
|
|
17812
17812
|
address: true,
|
|
17813
17813
|
article: true,
|
|
17814
17814
|
aside: true,
|
|
@@ -17904,7 +17904,7 @@ var NodeContext = class {
|
|
|
17904
17904
|
inlineContext(node) {
|
|
17905
17905
|
if (this.type) return this.type.inlineContent;
|
|
17906
17906
|
if (this.content.length) return this.content[0].isInline;
|
|
17907
|
-
return node.parentNode && !blockTags.hasOwnProperty(node.parentNode.nodeName.toLowerCase());
|
|
17907
|
+
return node.parentNode && !blockTags$1.hasOwnProperty(node.parentNode.nodeName.toLowerCase());
|
|
17908
17908
|
}
|
|
17909
17909
|
};
|
|
17910
17910
|
var ParseContext = class {
|
|
@@ -17968,7 +17968,7 @@ var ParseContext = class {
|
|
|
17968
17968
|
if (rule && rule.closeParent) this.open = Math.max(0, this.open - 1);
|
|
17969
17969
|
else if (rule && rule.skip.nodeType) dom = rule.skip;
|
|
17970
17970
|
let sync, oldNeedsBlock = this.needsBlock;
|
|
17971
|
-
if (blockTags.hasOwnProperty(name)) {
|
|
17971
|
+
if (blockTags$1.hasOwnProperty(name)) {
|
|
17972
17972
|
if (top.content.length && top.content[0].isInline && this.open) {
|
|
17973
17973
|
this.open--;
|
|
17974
17974
|
top = this.top;
|
|
@@ -28368,6 +28368,7 @@ var defaultLabels = {
|
|
|
28368
28368
|
slashWarning: "Warning",
|
|
28369
28369
|
slashCaution: "Caution",
|
|
28370
28370
|
formattedEditor: "Formatted Markdown editor",
|
|
28371
|
+
richTextEditor: "Rich text editor",
|
|
28371
28372
|
plainTextEditor: "Plain text editor",
|
|
28372
28373
|
sourceEditor: "Markdown source editor",
|
|
28373
28374
|
placeholder: "Start typing…",
|
|
@@ -28408,6 +28409,33 @@ function editorLabels(ctx) {
|
|
|
28408
28409
|
}
|
|
28409
28410
|
//#endregion
|
|
28410
28411
|
//#region src/types.ts
|
|
28412
|
+
/** The closed list of what a conversion can lose. The API reference describes each one. */
|
|
28413
|
+
var formatLossCodes = [
|
|
28414
|
+
"heading",
|
|
28415
|
+
"code-block",
|
|
28416
|
+
"diagram",
|
|
28417
|
+
"quote",
|
|
28418
|
+
"callout",
|
|
28419
|
+
"task",
|
|
28420
|
+
"rule",
|
|
28421
|
+
"footnote",
|
|
28422
|
+
"comment",
|
|
28423
|
+
"highlight",
|
|
28424
|
+
"image",
|
|
28425
|
+
"file",
|
|
28426
|
+
"wiki-link",
|
|
28427
|
+
"literal",
|
|
28428
|
+
"link",
|
|
28429
|
+
"table-alignment",
|
|
28430
|
+
"list-start",
|
|
28431
|
+
"list-item-blocks",
|
|
28432
|
+
"list-depth",
|
|
28433
|
+
"link-underline",
|
|
28434
|
+
"invalid-character",
|
|
28435
|
+
"alignment",
|
|
28436
|
+
"mark-spaces",
|
|
28437
|
+
"empty-line"
|
|
28438
|
+
];
|
|
28411
28439
|
/** Every failure InkKit reports. Hosts branch on `code`; `message` is for logs. When the failure
|
|
28412
28440
|
* started elsewhere, such as in an adapter, the host clipboard or the renderer, `cause` holds the
|
|
28413
28441
|
* original error. */
|
|
@@ -28940,6 +28968,61 @@ function remarkLinkForms() {
|
|
|
28940
28968
|
//#endregion
|
|
28941
28969
|
//#region node_modules/.pnpm/prosemirror-schema-list@1.5.1/node_modules/prosemirror-schema-list/dist/index.js
|
|
28942
28970
|
/**
|
|
28971
|
+
Returns a command function that wraps the selection in a list with
|
|
28972
|
+
the given type an attributes. If `dispatch` is null, only return a
|
|
28973
|
+
value to indicate whether this is possible, but don't actually
|
|
28974
|
+
perform the change.
|
|
28975
|
+
*/
|
|
28976
|
+
function wrapInList(listType, attrs = null) {
|
|
28977
|
+
return function(state, dispatch) {
|
|
28978
|
+
let { $from, $to } = state.selection;
|
|
28979
|
+
let range = $from.blockRange($to);
|
|
28980
|
+
if (!range) return false;
|
|
28981
|
+
let tr = dispatch ? state.tr : null;
|
|
28982
|
+
if (!wrapRangeInList(tr, range, listType, attrs)) return false;
|
|
28983
|
+
if (dispatch) dispatch(tr.scrollIntoView());
|
|
28984
|
+
return true;
|
|
28985
|
+
};
|
|
28986
|
+
}
|
|
28987
|
+
/**
|
|
28988
|
+
Try to wrap the given node range in a list of the given type.
|
|
28989
|
+
Return `true` when this is possible, `false` otherwise. When `tr`
|
|
28990
|
+
is non-null, the wrapping is added to that transaction. When it is
|
|
28991
|
+
`null`, the function only queries whether the wrapping is
|
|
28992
|
+
possible.
|
|
28993
|
+
*/
|
|
28994
|
+
function wrapRangeInList(tr, range, listType, attrs = null) {
|
|
28995
|
+
let doJoin = false, outerRange = range, doc = range.$from.doc;
|
|
28996
|
+
if (range.depth >= 2 && range.$from.node(range.depth - 1).type.compatibleContent(listType) && range.startIndex == 0) {
|
|
28997
|
+
if (range.$from.index(range.depth - 1) == 0) return false;
|
|
28998
|
+
let $insert = doc.resolve(range.start - 2);
|
|
28999
|
+
outerRange = new NodeRange($insert, $insert, range.depth);
|
|
29000
|
+
if (range.endIndex < range.parent.childCount) range = new NodeRange(range.$from, doc.resolve(range.$to.end(range.depth)), range.depth);
|
|
29001
|
+
doJoin = true;
|
|
29002
|
+
}
|
|
29003
|
+
let wrap = findWrapping(outerRange, listType, attrs, range);
|
|
29004
|
+
if (!wrap) return false;
|
|
29005
|
+
if (tr) doWrapInList(tr, range, wrap, doJoin, listType);
|
|
29006
|
+
return true;
|
|
29007
|
+
}
|
|
29008
|
+
function doWrapInList(tr, range, wrappers, joinBefore, listType) {
|
|
29009
|
+
let content = Fragment.empty;
|
|
29010
|
+
for (let i = wrappers.length - 1; i >= 0; i--) content = Fragment.from(wrappers[i].type.create(wrappers[i].attrs, content));
|
|
29011
|
+
tr.step(new ReplaceAroundStep(range.start - (joinBefore ? 2 : 0), range.end, range.start, range.end, new Slice(content, 0, 0), wrappers.length, true));
|
|
29012
|
+
let found = 0;
|
|
29013
|
+
for (let i = 0; i < wrappers.length; i++) if (wrappers[i].type == listType) found = i + 1;
|
|
29014
|
+
let splitDepth = wrappers.length - found;
|
|
29015
|
+
let splitPos = range.start + wrappers.length - (joinBefore ? 2 : 0), parent = range.parent;
|
|
29016
|
+
for (let i = range.startIndex, e = range.endIndex, first = true; i < e; i++, first = false) {
|
|
29017
|
+
if (!first && canSplit(tr.doc, splitPos, splitDepth)) {
|
|
29018
|
+
tr.split(splitPos, splitDepth);
|
|
29019
|
+
splitPos += 2 * splitDepth;
|
|
29020
|
+
}
|
|
29021
|
+
splitPos += parent.child(i).nodeSize;
|
|
29022
|
+
}
|
|
29023
|
+
return tr;
|
|
29024
|
+
}
|
|
29025
|
+
/**
|
|
28943
29026
|
Build a command that splits a non-empty textblock at the top level
|
|
28944
29027
|
of a list item by also splitting that list item.
|
|
28945
29028
|
*/
|
|
@@ -40191,26 +40274,32 @@ var quotes$1 = {
|
|
|
40191
40274
|
"\"": ["“", "”"],
|
|
40192
40275
|
"'": ["‘", "’"]
|
|
40193
40276
|
};
|
|
40277
|
+
/** Input rules that are not Markdown syntax, which apply in every formatted document. */
|
|
40278
|
+
var formatNeutralRules = /* @__PURE__ */ new WeakSet();
|
|
40279
|
+
function formatNeutral(rule) {
|
|
40280
|
+
formatNeutralRules.add(rule);
|
|
40281
|
+
return rule;
|
|
40282
|
+
}
|
|
40194
40283
|
var smartPunctuation = [
|
|
40195
40284
|
smartPunctuationCtx,
|
|
40196
40285
|
inputTracking,
|
|
40197
|
-
$inputRule((ctx) => new InputRule(/["']$/, (state, match, start, end) => {
|
|
40286
|
+
$inputRule((ctx) => formatNeutral(new InputRule(/["']$/, (state, match, start, end) => {
|
|
40198
40287
|
if (!applies(ctx.get(smartPunctuationCtx.key), state, match, start)) return null;
|
|
40199
40288
|
const [opening, closing] = quotes$1[match[0]];
|
|
40200
40289
|
return state.tr.insertText(opensHere(state, start) ? opening : closing, start, end).setMeta(smartRule, "quote");
|
|
40201
|
-
}, { inCodeMark: false })),
|
|
40202
|
-
$inputRule((ctx) => new InputRule(/--$/, (state, match, start, end) => {
|
|
40290
|
+
}, { inCodeMark: false }))),
|
|
40291
|
+
$inputRule((ctx) => formatNeutral(new InputRule(/--$/, (state, match, start, end) => {
|
|
40203
40292
|
if (!applies(ctx.get(smartPunctuationCtx.key), state, match, start)) return null;
|
|
40204
40293
|
if (input.getState(state)?.hyphen !== state.selection.from) return null;
|
|
40205
40294
|
const before = textBefore(state, start);
|
|
40206
40295
|
if (!before.trim() || before.endsWith("-") || /^\s*\|/.test(before)) return null;
|
|
40207
40296
|
return state.tr.insertText(enDash, start, end).setMeta(smartRule, "en");
|
|
40208
|
-
}, { inCodeMark: false })),
|
|
40209
|
-
$inputRule((ctx) => new InputRule(/\u2013-$/, (state, match, start, end) => {
|
|
40297
|
+
}, { inCodeMark: false }))),
|
|
40298
|
+
$inputRule((ctx) => formatNeutral(new InputRule(/\u2013-$/, (state, match, start, end) => {
|
|
40210
40299
|
if (!applies(ctx.get(smartPunctuationCtx.key), state, match, start)) return null;
|
|
40211
40300
|
if (lastRule(state)?.transform.getMeta(smartRule) !== "en") return null;
|
|
40212
40301
|
return state.tr.insertText(emDash, start, end).setMeta(smartRule, "em");
|
|
40213
|
-
}, { inCodeMark: false }))
|
|
40302
|
+
}, { inCodeMark: false })))
|
|
40214
40303
|
].flat();
|
|
40215
40304
|
/**
|
|
40216
40305
|
* Undo straight after a smart quote or dash puts back the character that was typed, as Backspace
|
|
@@ -42702,6 +42791,16 @@ function insertBlocks(view, blocks, point) {
|
|
|
42702
42791
|
view.dispatch(tr.setSelection(TextSelection.create(tr.doc, end)).scrollIntoView());
|
|
42703
42792
|
}
|
|
42704
42793
|
//#endregion
|
|
42794
|
+
//#region src/format-input.ts
|
|
42795
|
+
var markdown = {
|
|
42796
|
+
markdownInput: () => true,
|
|
42797
|
+
richText: () => void 0
|
|
42798
|
+
};
|
|
42799
|
+
var formatInputCtx = $ctx(markdown, "inkkitFormatInput");
|
|
42800
|
+
function formatInput(ctx) {
|
|
42801
|
+
return ctx.isInjected(formatInputCtx.key) ? ctx.get(formatInputCtx.key) : markdown;
|
|
42802
|
+
}
|
|
42803
|
+
//#endregion
|
|
42705
42804
|
//#region src/literals.ts
|
|
42706
42805
|
var unsupported = /* @__PURE__ */ new Set([
|
|
42707
42806
|
"html",
|
|
@@ -43650,6 +43749,7 @@ function editedBlocks(before, after) {
|
|
|
43650
43749
|
var autolinkLiterals = $prose((ctx) => new Plugin({
|
|
43651
43750
|
key: new PluginKey("autolinkLiterals"),
|
|
43652
43751
|
appendTransaction(transactions, oldState, newState) {
|
|
43752
|
+
if (!formatInput(ctx).markdownInput()) return null;
|
|
43653
43753
|
if (!transactions.some((tr) => tr.docChanged)) return null;
|
|
43654
43754
|
const base = newState.tr;
|
|
43655
43755
|
const written = transactions.every(isWrittenEdit);
|
|
@@ -46013,8 +46113,8 @@ function shareFormatting(children) {
|
|
|
46013
46113
|
};
|
|
46014
46114
|
const previous = children[index - 1];
|
|
46015
46115
|
const next = children[index + 1];
|
|
46016
|
-
const before = previous && same(previous, formatting);
|
|
46017
|
-
const after = next && same(next, formatting);
|
|
46116
|
+
const before = Boolean(previous && same(previous, formatting));
|
|
46117
|
+
const after = Boolean(next && same(next, formatting));
|
|
46018
46118
|
if (!before && !after) continue;
|
|
46019
46119
|
const merged = {
|
|
46020
46120
|
...formatting,
|
|
@@ -46271,6 +46371,26 @@ var preserveSpacerParagraphs = paragraphSchema.extendSchema((base) => (ctx) => {
|
|
|
46271
46371
|
const schema = base(ctx);
|
|
46272
46372
|
return {
|
|
46273
46373
|
...schema,
|
|
46374
|
+
attrs: {
|
|
46375
|
+
...schema.attrs,
|
|
46376
|
+
align: { default: null }
|
|
46377
|
+
},
|
|
46378
|
+
parseDOM: [{ tag: "p" }],
|
|
46379
|
+
toDOM: (node) => {
|
|
46380
|
+
const [tag, attrs, hole] = schema.toDOM(node);
|
|
46381
|
+
return node.attrs.align ? [
|
|
46382
|
+
tag,
|
|
46383
|
+
{
|
|
46384
|
+
...attrs,
|
|
46385
|
+
style: `text-align: ${String(node.attrs.align)}`
|
|
46386
|
+
},
|
|
46387
|
+
hole
|
|
46388
|
+
] : [
|
|
46389
|
+
tag,
|
|
46390
|
+
attrs,
|
|
46391
|
+
hole
|
|
46392
|
+
];
|
|
46393
|
+
},
|
|
46274
46394
|
parseMarkdown: {
|
|
46275
46395
|
...schema.parseMarkdown,
|
|
46276
46396
|
runner(state, node, type) {
|
|
@@ -46595,7 +46715,7 @@ function serialize(ctx, doc = ctx.get(editorViewCtx).state.doc) {
|
|
|
46595
46715
|
}
|
|
46596
46716
|
//#endregion
|
|
46597
46717
|
//#region src/editor-selection.ts
|
|
46598
|
-
var markNames = {
|
|
46718
|
+
var markNames$1 = {
|
|
46599
46719
|
strong: "bold",
|
|
46600
46720
|
emphasis: "italic",
|
|
46601
46721
|
strike_through: "strikethrough",
|
|
@@ -46640,10 +46760,10 @@ function caretState(state) {
|
|
|
46640
46760
|
const active = /* @__PURE__ */ new Set();
|
|
46641
46761
|
const marks = empty ? state.storedMarks ?? $from.marks() : [];
|
|
46642
46762
|
for (const mark of marks) {
|
|
46643
|
-
const name = markNames[mark.type.name];
|
|
46763
|
+
const name = markNames$1[mark.type.name];
|
|
46644
46764
|
if (name) active.add(name);
|
|
46645
46765
|
}
|
|
46646
|
-
if (!empty) for (const [name, mark] of Object.entries(markNames)) {
|
|
46766
|
+
if (!empty) for (const [name, mark] of Object.entries(markNames$1)) {
|
|
46647
46767
|
const type = state.schema.marks[name];
|
|
46648
46768
|
if (type && state.doc.rangeHasMark($from.pos, $to.pos, type)) active.add(mark);
|
|
46649
46769
|
}
|
|
@@ -46685,9 +46805,10 @@ function blockAt(parent, $from) {
|
|
|
46685
46805
|
}
|
|
46686
46806
|
//#endregion
|
|
46687
46807
|
//#region src/editor-links.ts
|
|
46688
|
-
function checkDestination(ctx, destination, name, linkSchemes) {
|
|
46808
|
+
function checkDestination(ctx, destination, name, linkSchemes, markdown = true) {
|
|
46689
46809
|
if (destination.trim() === "") throw invalidArgument(`${name} must not be empty`);
|
|
46690
46810
|
if (!isSafeLinkTarget(destination, linkSchemes)) throw invalidArgument(`${name} must be relative, or an address with an allowed scheme (http, https, mailto, tel or one of linkSchemes), with no control characters`);
|
|
46811
|
+
if (!markdown) return;
|
|
46691
46812
|
const { schema } = ctx.get(editorViewCtx).state;
|
|
46692
46813
|
const written = serialize(ctx, schema.node("doc", null, [schema.node("paragraph", null, [schema.text("x", [schema.marks.link.create({ href: destination })])])]));
|
|
46693
46814
|
let read;
|
|
@@ -47427,6 +47548,22 @@ var EditorClipboard = class {
|
|
|
47427
47548
|
return;
|
|
47428
47549
|
}
|
|
47429
47550
|
const view = this.runtime.ctx().get(editorViewCtx);
|
|
47551
|
+
const rich = this.runtime.handler().richText;
|
|
47552
|
+
if (rich) {
|
|
47553
|
+
if (!event.clipboardData || view.state.selection.empty) return;
|
|
47554
|
+
event.preventDefault();
|
|
47555
|
+
event.stopPropagation();
|
|
47556
|
+
try {
|
|
47557
|
+
this.runtime.snapshot();
|
|
47558
|
+
const output = rich.copy(view.state, this.runtime.linkSchemes());
|
|
47559
|
+
event.clipboardData.setData("text/plain", output.text);
|
|
47560
|
+
event.clipboardData.setData("text/html", output.html);
|
|
47561
|
+
if (event.type === "cut" && this.runtime.writable()) view.dispatch(view.state.tr.deleteSelection().scrollIntoView());
|
|
47562
|
+
} catch (error) {
|
|
47563
|
+
this.runtime.report(error);
|
|
47564
|
+
}
|
|
47565
|
+
return;
|
|
47566
|
+
}
|
|
47430
47567
|
const fragment = commentSelectionContent(view.state.doc, view.state.selection.from, view.state.selection.to, view.state.selection.content().content);
|
|
47431
47568
|
const expanded = selectionContent(view.state.doc, fragment);
|
|
47432
47569
|
let hasImages = false;
|
|
@@ -48760,6 +48897,7 @@ function runOffsets(nodes, text, from, to) {
|
|
|
48760
48897
|
const offsets = [from];
|
|
48761
48898
|
const links = [];
|
|
48762
48899
|
const breaks = /* @__PURE__ */ new Set();
|
|
48900
|
+
const tokens = /* @__PURE__ */ new Map();
|
|
48763
48901
|
let at = from;
|
|
48764
48902
|
let afterBreak = false;
|
|
48765
48903
|
for (const node of nodes) {
|
|
@@ -48772,10 +48910,9 @@ function runOffsets(nodes, text, from, to) {
|
|
|
48772
48910
|
}
|
|
48773
48911
|
if (start !== at) return void 0;
|
|
48774
48912
|
const raw = text.slice(start, end);
|
|
48775
|
-
|
|
48776
|
-
|
|
48777
|
-
|
|
48778
|
-
if (raw !== label && !angle) return void 0;
|
|
48913
|
+
const label = node.type === "link" ? inlineText(node) : void 0;
|
|
48914
|
+
const angle = label !== void 0 && raw === `<${label}>`;
|
|
48915
|
+
if (label !== void 0 && (raw === label || angle)) {
|
|
48779
48916
|
links.push([decoded.length, decoded.length + label.length]);
|
|
48780
48917
|
decoded += label;
|
|
48781
48918
|
for (let index = 1; index <= label.length; index++) offsets.push(index === label.length ? end : start + index + Number(angle));
|
|
@@ -48788,7 +48925,12 @@ function runOffsets(nodes, text, from, to) {
|
|
|
48788
48925
|
breaks.add(decoded.length);
|
|
48789
48926
|
decoded += "\n";
|
|
48790
48927
|
offsets.push(end);
|
|
48791
|
-
} else
|
|
48928
|
+
} else {
|
|
48929
|
+
tokens.set(decoded.length, signature(node));
|
|
48930
|
+
links.push([decoded.length, decoded.length + 1]);
|
|
48931
|
+
decoded += "";
|
|
48932
|
+
offsets.push(end);
|
|
48933
|
+
}
|
|
48792
48934
|
afterBreak = node.type === "break";
|
|
48793
48935
|
at = end;
|
|
48794
48936
|
}
|
|
@@ -48796,7 +48938,8 @@ function runOffsets(nodes, text, from, to) {
|
|
|
48796
48938
|
decoded,
|
|
48797
48939
|
offsets,
|
|
48798
48940
|
links,
|
|
48799
|
-
breaks
|
|
48941
|
+
breaks,
|
|
48942
|
+
tokens
|
|
48800
48943
|
} : void 0;
|
|
48801
48944
|
}
|
|
48802
48945
|
function retainRun(before, after, source, canonical, [from, to], [newFrom, newTo]) {
|
|
@@ -48806,11 +48949,31 @@ function retainRun(before, after, source, canonical, [from, to], [newFrom, newTo
|
|
|
48806
48949
|
const a = old.decoded;
|
|
48807
48950
|
const b = now.decoded;
|
|
48808
48951
|
let prefix = 0;
|
|
48809
|
-
while (prefix < a.length && prefix < b.length && a[prefix] === b[prefix] && old.breaks.has(prefix) === now.breaks.has(prefix)) prefix++;
|
|
48952
|
+
while (prefix < a.length && prefix < b.length && a[prefix] === b[prefix] && old.tokens.get(prefix) === now.tokens.get(prefix) && old.breaks.has(prefix) === now.breaks.has(prefix)) prefix++;
|
|
48810
48953
|
let suffix = 0;
|
|
48811
|
-
while (suffix < a.length - prefix && suffix < b.length - prefix && a[a.length - 1 - suffix] === b[b.length - 1 - suffix] && old.breaks.has(a.length - 1 - suffix) === now.breaks.has(b.length - 1 - suffix)) suffix++;
|
|
48954
|
+
while (suffix < a.length - prefix && suffix < b.length - prefix && a[a.length - 1 - suffix] === b[b.length - 1 - suffix] && old.tokens.get(a.length - 1 - suffix) === now.tokens.get(b.length - 1 - suffix) && old.breaks.has(a.length - 1 - suffix) === now.breaks.has(b.length - 1 - suffix)) suffix++;
|
|
48955
|
+
const reference = /^&(?:#[xX][\da-fA-F]+|#\d+|[A-Za-z][A-Za-z\d]+);$/;
|
|
48956
|
+
const encodedBoundary = (oldIndex, newIndex) => reference.test(canonical.slice(now.offsets[newIndex], now.offsets[newIndex + 1])) && !reference.test(source.slice(old.offsets[oldIndex], old.offsets[oldIndex + 1]));
|
|
48812
48957
|
for (let widened = true; widened;) {
|
|
48813
48958
|
widened = false;
|
|
48959
|
+
if (prefix > 0 && encodedBoundary(prefix - 1, prefix - 1)) {
|
|
48960
|
+
prefix--;
|
|
48961
|
+
widened = true;
|
|
48962
|
+
}
|
|
48963
|
+
if (suffix > 0 && encodedBoundary(a.length - suffix, b.length - suffix)) {
|
|
48964
|
+
suffix--;
|
|
48965
|
+
widened = true;
|
|
48966
|
+
}
|
|
48967
|
+
for (const { offsets, decoded } of [old, now]) {
|
|
48968
|
+
while (prefix > 0 && offsets[prefix] === offsets[prefix + 1]) {
|
|
48969
|
+
prefix--;
|
|
48970
|
+
widened = true;
|
|
48971
|
+
}
|
|
48972
|
+
while (suffix > 0 && offsets[decoded.length - suffix] === offsets[decoded.length - suffix + 1]) {
|
|
48973
|
+
suffix--;
|
|
48974
|
+
widened = true;
|
|
48975
|
+
}
|
|
48976
|
+
}
|
|
48814
48977
|
for (const [links, length] of [[old.links, a.length], [now.links, b.length]]) for (const [start, end] of links) if (start <= length - suffix && end >= prefix) {
|
|
48815
48978
|
if (start < prefix) {
|
|
48816
48979
|
prefix = start;
|
|
@@ -48988,7 +49151,7 @@ function correspondence(before, after) {
|
|
|
48988
49151
|
if (work.budget < 0) throw tooManyBlocks();
|
|
48989
49152
|
const anchors = uniqueAnchors(before, after, b, bEnd, a, aEnd);
|
|
48990
49153
|
if (!anchors.length) {
|
|
48991
|
-
align(before, after, b, bEnd, a, aEnd, result, work);
|
|
49154
|
+
align$1(before, after, b, bEnd, a, aEnd, result, work);
|
|
48992
49155
|
continue;
|
|
48993
49156
|
}
|
|
48994
49157
|
for (const anchor of anchors) {
|
|
@@ -49053,7 +49216,7 @@ function uniqueAnchors(before, after, b, bEnd, a, aEnd) {
|
|
|
49053
49216
|
}
|
|
49054
49217
|
var INDEL = 2;
|
|
49055
49218
|
var REPLACE = 3;
|
|
49056
|
-
function align(before, after, b, bEnd, a, aEnd, result, work) {
|
|
49219
|
+
function align$1(before, after, b, bEnd, a, aEnd, result, work) {
|
|
49057
49220
|
const rows = bEnd - b + 1;
|
|
49058
49221
|
const columns = aEnd - a + 1;
|
|
49059
49222
|
const offset = Math.abs(rows - columns);
|
|
@@ -51482,7 +51645,7 @@ function cleanSemantics(container, localIDs) {
|
|
|
51482
51645
|
if (title) element.setAttribute("title", title);
|
|
51483
51646
|
if (id && localIDs.has(id)) element.setAttribute("id", id);
|
|
51484
51647
|
if (order && /^\d+$/.test(order)) element.setAttribute("start", order);
|
|
51485
|
-
if (/^(left|center|right)$/.test(alignment) && /^(TH|TD)$/.test(element.tagName)) element.setAttribute("style", `text-align: ${alignment}`);
|
|
51648
|
+
if (/^(left|center|right)$/.test(alignment) && /^(TH|TD)$/.test(element.tagName) || /^(center|right|justify)$/.test(alignment) && element.tagName === "P") element.setAttribute("style", `text-align: ${alignment}`);
|
|
51486
51649
|
}
|
|
51487
51650
|
const walker = document.createTreeWalker(container, NodeFilter.SHOW_COMMENT);
|
|
51488
51651
|
const comments = [];
|
|
@@ -51601,10 +51764,12 @@ var markdownFormat = {
|
|
|
51601
51764
|
promise: "exact",
|
|
51602
51765
|
literalOnly: false,
|
|
51603
51766
|
sourceMode: true,
|
|
51767
|
+
formattedLabel: "formattedEditor",
|
|
51604
51768
|
literalLabel: "sourceEditor",
|
|
51605
51769
|
headings: true,
|
|
51606
51770
|
nativeCopy: false,
|
|
51607
51771
|
keepsInsertedLineEndings: true,
|
|
51772
|
+
markdownInput: true,
|
|
51608
51773
|
load: (ctx, text) => Preservation.read(ctx, text),
|
|
51609
51774
|
parse: (ctx, text) => Preservation.read(ctx, text),
|
|
51610
51775
|
pendingExport: (ctx, text) => ctx.get(parserCtx)(text),
|
|
@@ -51642,6 +51807,1067 @@ var markdownFormat = {
|
|
|
51642
51807
|
};
|
|
51643
51808
|
}
|
|
51644
51809
|
};
|
|
51810
|
+
/** The largest first number of an ordered list that RTF writes and reads back unchanged, as
|
|
51811
|
+
* measured with the host's converter. */
|
|
51812
|
+
var maximumListStart = 2147483647;
|
|
51813
|
+
/** Characters a run never holds: they would change the structure, stand for an attachment or end
|
|
51814
|
+
* the text when RTF is written. */
|
|
51815
|
+
var forbidden = /[\u0000\n\r\u000b\u000c\u0085\u2028\u2029\ufffc]/;
|
|
51816
|
+
var loneSurrogate = /[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/;
|
|
51817
|
+
/** Why a run cannot hold `text`, or undefined. Loading and editing share the rule. */
|
|
51818
|
+
function textProblem(text) {
|
|
51819
|
+
const bad = forbidden.exec(text);
|
|
51820
|
+
if (bad) return `text cannot hold ${scalarName(bad[0])}`;
|
|
51821
|
+
if (loneSurrogate.test(text)) return "text cannot hold a lone surrogate";
|
|
51822
|
+
}
|
|
51823
|
+
/** Why `href` cannot be a link's address, or undefined. Any address that is not empty and holds
|
|
51824
|
+
* no control character is kept, as the host's converter keeps it; loading, editing, paste and the
|
|
51825
|
+
* converter share the rule. Edits and paste also apply Inkkit's link safety. */
|
|
51826
|
+
function linkProblem(href) {
|
|
51827
|
+
if (!href) return "a link needs an address";
|
|
51828
|
+
if (/[\u0000-\u001f\u007f-\u009f]/.test(href)) return "a link address cannot hold a control character";
|
|
51829
|
+
const problem = textProblem(href);
|
|
51830
|
+
return problem && `a link address ${problem.replace(/^text /, "")}`;
|
|
51831
|
+
}
|
|
51832
|
+
/** The canonical order of marks. */
|
|
51833
|
+
var markRank = {
|
|
51834
|
+
bold: 0,
|
|
51835
|
+
italic: 1,
|
|
51836
|
+
underline: 2,
|
|
51837
|
+
strikethrough: 3,
|
|
51838
|
+
superscript: 4,
|
|
51839
|
+
subscript: 5,
|
|
51840
|
+
code: 6,
|
|
51841
|
+
link: 7
|
|
51842
|
+
};
|
|
51843
|
+
function invalid(path, problem) {
|
|
51844
|
+
throw invalidArgument(`${path}: ${problem}`);
|
|
51845
|
+
}
|
|
51846
|
+
function record(value, path, keys) {
|
|
51847
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) invalid(path, "must be an object");
|
|
51848
|
+
for (const key of Object.keys(value)) if (!keys.includes(key)) invalid(`${path}.${key}`, "is not a known field");
|
|
51849
|
+
return value;
|
|
51850
|
+
}
|
|
51851
|
+
function list$1(value, path) {
|
|
51852
|
+
if (!Array.isArray(value)) invalid(path, "must be a list");
|
|
51853
|
+
return value;
|
|
51854
|
+
}
|
|
51855
|
+
function scalarName(text) {
|
|
51856
|
+
return `U+${text.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")}`;
|
|
51857
|
+
}
|
|
51858
|
+
/** Collects inline content in canonical form: adjacent runs with equal marks merge, and each run
|
|
51859
|
+
* is NFC after merging, because joining two NFC strings can leave a sequence that composes. */
|
|
51860
|
+
var InlineBuilder = class {
|
|
51861
|
+
content = [];
|
|
51862
|
+
marksKey;
|
|
51863
|
+
text(text, marks) {
|
|
51864
|
+
if (!text) return;
|
|
51865
|
+
const key = JSON.stringify(marks);
|
|
51866
|
+
const last = this.content.at(-1);
|
|
51867
|
+
if (last?.type === "text" && this.marksKey === key) {
|
|
51868
|
+
last.text += text;
|
|
51869
|
+
return;
|
|
51870
|
+
}
|
|
51871
|
+
this.content.push(marks.length ? {
|
|
51872
|
+
type: "text",
|
|
51873
|
+
text,
|
|
51874
|
+
marks: [...marks]
|
|
51875
|
+
} : {
|
|
51876
|
+
type: "text",
|
|
51877
|
+
text
|
|
51878
|
+
});
|
|
51879
|
+
this.marksKey = key;
|
|
51880
|
+
}
|
|
51881
|
+
break() {
|
|
51882
|
+
this.content.push({ type: "break" });
|
|
51883
|
+
this.marksKey = void 0;
|
|
51884
|
+
}
|
|
51885
|
+
finish() {
|
|
51886
|
+
for (const inline of this.content) if (inline.type === "text") inline.text = inline.text.normalize("NFC");
|
|
51887
|
+
return this.content;
|
|
51888
|
+
}
|
|
51889
|
+
};
|
|
51890
|
+
/** Marks in canonical order. A link's run never also has underline: a link's underline is how it
|
|
51891
|
+
* is drawn, so RTF readers report it on every link. */
|
|
51892
|
+
function canonicalMarks(marks) {
|
|
51893
|
+
const sorted = [...marks].sort((a, b) => markRank[a.type] - markRank[b.type]);
|
|
51894
|
+
return sorted.some((mark) => mark.type === "link") ? sorted.filter((mark) => mark.type !== "underline") : sorted;
|
|
51895
|
+
}
|
|
51896
|
+
function readMarks(value, path) {
|
|
51897
|
+
const sorted = [...list$1(value, path).map((entry, index) => {
|
|
51898
|
+
const at = `${path}[${index}]`;
|
|
51899
|
+
const mark = record(entry, at, ["type", "href"]);
|
|
51900
|
+
const type = mark.type;
|
|
51901
|
+
if (typeof type !== "string" || !(type in markRank)) invalid(`${at}.type`, `unknown mark type ${JSON.stringify(type)}`);
|
|
51902
|
+
if (type === "link") {
|
|
51903
|
+
if (typeof mark.href !== "string") invalid(`${at}.href`, "must be text");
|
|
51904
|
+
return {
|
|
51905
|
+
type: "link",
|
|
51906
|
+
href: mark.href
|
|
51907
|
+
};
|
|
51908
|
+
}
|
|
51909
|
+
if (!isOmitted(mark.href)) invalid(`${at}.href`, "only a link has an address");
|
|
51910
|
+
return { type };
|
|
51911
|
+
})].sort((a, b) => markRank[a.type] - markRank[b.type]);
|
|
51912
|
+
const types = /* @__PURE__ */ new Set();
|
|
51913
|
+
for (const mark of sorted) {
|
|
51914
|
+
if (types.has(mark.type)) invalid(path, `the ${mark.type} mark appears more than once`);
|
|
51915
|
+
types.add(mark.type);
|
|
51916
|
+
const problem = mark.type === "link" ? linkProblem(mark.href) : void 0;
|
|
51917
|
+
if (problem) invalid(path, problem);
|
|
51918
|
+
}
|
|
51919
|
+
if (types.has("superscript") && types.has("subscript")) invalid(path, "a run cannot be both superscript and subscript");
|
|
51920
|
+
return canonicalMarks(sorted);
|
|
51921
|
+
}
|
|
51922
|
+
function readInlines(value, path) {
|
|
51923
|
+
const builder = new InlineBuilder();
|
|
51924
|
+
list$1(value, path).forEach((entry, index) => {
|
|
51925
|
+
const at = `${path}[${index}]`;
|
|
51926
|
+
const inline = record(entry, at, [
|
|
51927
|
+
"type",
|
|
51928
|
+
"text",
|
|
51929
|
+
"marks"
|
|
51930
|
+
]);
|
|
51931
|
+
if (inline.type === "break") {
|
|
51932
|
+
if (!isOmitted(inline.text) || !isOmitted(inline.marks)) invalid(at, "a break holds no text or marks");
|
|
51933
|
+
builder.break();
|
|
51934
|
+
return;
|
|
51935
|
+
}
|
|
51936
|
+
if (inline.type !== "text") invalid(`${at}.type`, `unknown inline type ${JSON.stringify(inline.type)}`);
|
|
51937
|
+
const text = inline.text;
|
|
51938
|
+
if (typeof text !== "string") invalid(`${at}.text`, "must be text");
|
|
51939
|
+
if (!text) invalid(`${at}.text`, "a text run cannot be empty");
|
|
51940
|
+
const problem = textProblem(text);
|
|
51941
|
+
if (problem) invalid(`${at}.text`, problem);
|
|
51942
|
+
builder.text(text, isOmitted(inline.marks) ? [] : readMarks(inline.marks, `${at}.marks`));
|
|
51943
|
+
});
|
|
51944
|
+
return builder.finish();
|
|
51945
|
+
}
|
|
51946
|
+
function readBlock(value, path, listDepth) {
|
|
51947
|
+
const block = record(value, path, [
|
|
51948
|
+
"type",
|
|
51949
|
+
"align",
|
|
51950
|
+
"content",
|
|
51951
|
+
"ordered",
|
|
51952
|
+
"start",
|
|
51953
|
+
"items",
|
|
51954
|
+
"rows"
|
|
51955
|
+
]);
|
|
51956
|
+
const only = (...keys) => {
|
|
51957
|
+
for (const key of Object.keys(block)) if (key !== "type" && !keys.includes(key)) invalid(`${path}.${key}`, `a ${String(block.type)} has no ${key}`);
|
|
51958
|
+
};
|
|
51959
|
+
switch (block.type) {
|
|
51960
|
+
case "paragraph": {
|
|
51961
|
+
only("align", "content");
|
|
51962
|
+
const align = block.align;
|
|
51963
|
+
if (!isOmitted(align) && align !== "center" && align !== "right" && align !== "justify") invalid(`${path}.align`, "must be center, right or justify");
|
|
51964
|
+
const content = readInlines(block.content, `${path}.content`);
|
|
51965
|
+
return isOmitted(align) ? {
|
|
51966
|
+
type: "paragraph",
|
|
51967
|
+
content
|
|
51968
|
+
} : {
|
|
51969
|
+
type: "paragraph",
|
|
51970
|
+
align,
|
|
51971
|
+
content
|
|
51972
|
+
};
|
|
51973
|
+
}
|
|
51974
|
+
case "list":
|
|
51975
|
+
only("ordered", "start", "items");
|
|
51976
|
+
return readList(block, path, listDepth + 1);
|
|
51977
|
+
case "table": {
|
|
51978
|
+
only("rows");
|
|
51979
|
+
if (listDepth > 0) invalid(path, "a table cannot be inside a list item");
|
|
51980
|
+
const rows = list$1(block.rows, `${path}.rows`);
|
|
51981
|
+
if (!rows.length) invalid(`${path}.rows`, "a table needs at least one row");
|
|
51982
|
+
const width = list$1(rows[0], `${path}.rows[0]`).length;
|
|
51983
|
+
if (!width) invalid(`${path}.rows[0]`, "a table row needs at least one cell");
|
|
51984
|
+
return {
|
|
51985
|
+
type: "table",
|
|
51986
|
+
rows: rows.map((row, rowIndex) => {
|
|
51987
|
+
const at = `${path}.rows[${rowIndex}]`;
|
|
51988
|
+
const cells = list$1(row, at);
|
|
51989
|
+
if (cells.length !== width) invalid(at, `the row has ${cells.length} cells, but the first row has ${width}`);
|
|
51990
|
+
return cells.map((cell, cellIndex) => {
|
|
51991
|
+
const cellPath = `${at}[${cellIndex}]`;
|
|
51992
|
+
const content = record(cell, cellPath, ["content"]).content;
|
|
51993
|
+
return { content: readInlines(content, `${cellPath}.content`) };
|
|
51994
|
+
});
|
|
51995
|
+
})
|
|
51996
|
+
};
|
|
51997
|
+
}
|
|
51998
|
+
default: invalid(`${path}.type`, `unknown block type ${JSON.stringify(block.type)}`);
|
|
51999
|
+
}
|
|
52000
|
+
}
|
|
52001
|
+
function readList(block, path, depth) {
|
|
52002
|
+
if (depth > 9) invalid(path, `lists nest deeper than 9 levels`);
|
|
52003
|
+
if (typeof block.ordered !== "boolean") invalid(`${path}.ordered`, "must be true or false");
|
|
52004
|
+
let start;
|
|
52005
|
+
if (!isOmitted(block.start)) {
|
|
52006
|
+
if (!block.ordered) invalid(`${path}.start`, "only an ordered list has a start");
|
|
52007
|
+
if (!Number.isSafeInteger(block.start)) invalid(`${path}.start`, "must be a whole number");
|
|
52008
|
+
if (block.start < 1) invalid(`${path}.start`, "the start must be at least 1");
|
|
52009
|
+
if (block.start > 2147483647) invalid(`${path}.start`, `the start must be at most ${maximumListStart}`);
|
|
52010
|
+
if (block.start !== 1) start = block.start;
|
|
52011
|
+
}
|
|
52012
|
+
const entries = list$1(block.items, `${path}.items`);
|
|
52013
|
+
if (!entries.length) invalid(`${path}.items`, "a list needs at least one item");
|
|
52014
|
+
const items = entries.map((entry, itemIndex) => {
|
|
52015
|
+
const at = `${path}.items[${itemIndex}]`;
|
|
52016
|
+
const blocks = list$1(record(entry, at, ["blocks"]).blocks, `${at}.blocks`);
|
|
52017
|
+
const first = blocks[0];
|
|
52018
|
+
if (!first || typeof first !== "object" || first.type !== "paragraph") invalid(`${at}.blocks[0]`, "a list item must start with a paragraph");
|
|
52019
|
+
return { blocks: blocks.map((child, blockIndex) => {
|
|
52020
|
+
const childPath = `${at}.blocks[${blockIndex}]`;
|
|
52021
|
+
if (blockIndex > 0 && (!child || typeof child !== "object" || child.type !== "list")) invalid(childPath, "after its paragraph, a list item can hold only lists");
|
|
52022
|
+
return readBlock(child, childPath, depth);
|
|
52023
|
+
}) };
|
|
52024
|
+
});
|
|
52025
|
+
return start === void 0 ? {
|
|
52026
|
+
type: "list",
|
|
52027
|
+
ordered: block.ordered,
|
|
52028
|
+
items
|
|
52029
|
+
} : {
|
|
52030
|
+
type: "list",
|
|
52031
|
+
ordered: block.ordered,
|
|
52032
|
+
start,
|
|
52033
|
+
items
|
|
52034
|
+
};
|
|
52035
|
+
}
|
|
52036
|
+
/**
|
|
52037
|
+
* Checks `value` as a version 1 document and returns its canonical form: marks sorted, a link's
|
|
52038
|
+
* underline removed, adjacent equal runs merged, runs in NFC, and `start` of 1 left out. Throws
|
|
52039
|
+
* `invalid-argument` naming the first problem, under `path`.
|
|
52040
|
+
*/
|
|
52041
|
+
function readRichTextDocument(value, path) {
|
|
52042
|
+
const document = record(value, path, ["version", "blocks"]);
|
|
52043
|
+
if (document.version !== 1) invalid(`${path}.version`, `version ${JSON.stringify(document.version)} is not supported; the version must be 1`);
|
|
52044
|
+
return {
|
|
52045
|
+
version: 1,
|
|
52046
|
+
blocks: list$1(document.blocks, `${path}.blocks`).map((block, index) => readBlock(block, `${path}.blocks[${index}]`, 0))
|
|
52047
|
+
};
|
|
52048
|
+
}
|
|
52049
|
+
/** The text that stands for a canonical document inside the editor: its JSON. Two documents are
|
|
52050
|
+
* the same exactly when their texts are. */
|
|
52051
|
+
function richTextText(document) {
|
|
52052
|
+
return JSON.stringify(document);
|
|
52053
|
+
}
|
|
52054
|
+
/** The document a text from `richTextText` stands for, as a new object. */
|
|
52055
|
+
function richTextFromText(text) {
|
|
52056
|
+
return JSON.parse(text);
|
|
52057
|
+
}
|
|
52058
|
+
/** Text from outside a document, such as a paste or another format, as canonical runs: a line or
|
|
52059
|
+
* paragraph separator inside a paragraph becomes a line break, and characters RTF cannot hold as
|
|
52060
|
+
* text become U+FFFD, so that no character disappears. */
|
|
52061
|
+
function appendText(builder, text, marks) {
|
|
52062
|
+
text.replace(/[\u0000\ufffc]/g, "�").replace(/[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/g, "�").split(/\r\n|[\n\r\u000b\u000c\u0085\u2028\u2029]/).forEach((line, index) => {
|
|
52063
|
+
if (index) builder.break();
|
|
52064
|
+
builder.text(line, marks);
|
|
52065
|
+
});
|
|
52066
|
+
}
|
|
52067
|
+
/** Typed or pasted text as paragraphs: each paragraph separator ends one, including a final one,
|
|
52068
|
+
* so inserting `'a\n'` leaves the caret in a new paragraph. A line separator is a line break.
|
|
52069
|
+
* Unlike the host's `RichTextDocument(plainText:)`, which reads a file, nothing is trimmed. */
|
|
52070
|
+
function textBlocks(text) {
|
|
52071
|
+
if (text === "") return [];
|
|
52072
|
+
return text.split(/\r\n|[\n\r\u000c\u0085\u2029]/).map((line) => {
|
|
52073
|
+
const builder = new InlineBuilder();
|
|
52074
|
+
appendText(builder, line, []);
|
|
52075
|
+
return {
|
|
52076
|
+
type: "paragraph",
|
|
52077
|
+
content: builder.finish()
|
|
52078
|
+
};
|
|
52079
|
+
});
|
|
52080
|
+
}
|
|
52081
|
+
//#endregion
|
|
52082
|
+
//#region src/formats/rich-text-prose.ts
|
|
52083
|
+
/** Editor marks by rich text mark, in canonical order. */
|
|
52084
|
+
var markNames = {
|
|
52085
|
+
bold: "strong",
|
|
52086
|
+
italic: "emphasis",
|
|
52087
|
+
underline: "underline",
|
|
52088
|
+
strikethrough: "strike_through",
|
|
52089
|
+
superscript: "superscript",
|
|
52090
|
+
subscript: "subscript",
|
|
52091
|
+
code: "inlineCode",
|
|
52092
|
+
link: "link"
|
|
52093
|
+
};
|
|
52094
|
+
var richMarks$1 = Object.fromEntries(Object.entries(markNames).map(([rich, name]) => [name, rich]));
|
|
52095
|
+
/** The editor nodes an RTF document can hold. */
|
|
52096
|
+
var allowedNodes = /* @__PURE__ */ new Set([
|
|
52097
|
+
"doc",
|
|
52098
|
+
"paragraph",
|
|
52099
|
+
"text",
|
|
52100
|
+
"hardbreak",
|
|
52101
|
+
"bullet_list",
|
|
52102
|
+
"ordered_list",
|
|
52103
|
+
"list_item",
|
|
52104
|
+
"table",
|
|
52105
|
+
"table_header_row",
|
|
52106
|
+
"table_row",
|
|
52107
|
+
"table_header",
|
|
52108
|
+
"table_cell"
|
|
52109
|
+
]);
|
|
52110
|
+
function unrepresentable(what) {
|
|
52111
|
+
return new InkKitError("preservation", `The document holds ${what}, which an RTF document cannot hold`);
|
|
52112
|
+
}
|
|
52113
|
+
function proseMarks(schema, marks = []) {
|
|
52114
|
+
return marks.map((mark) => mark.type === "link" ? schema.marks.link.create({ href: mark.href }) : schema.marks[markNames[mark.type]].create());
|
|
52115
|
+
}
|
|
52116
|
+
function proseInlines(schema, content, around) {
|
|
52117
|
+
return content.map((inline) => {
|
|
52118
|
+
if (inline.type === "break") return schema.nodes.hardbreak.create({
|
|
52119
|
+
isInline: false,
|
|
52120
|
+
isHTML: false
|
|
52121
|
+
});
|
|
52122
|
+
let marks = [...around];
|
|
52123
|
+
for (const mark of proseMarks(schema, inline.marks)) marks = mark.addToSet(marks);
|
|
52124
|
+
return schema.text(inline.text, marks);
|
|
52125
|
+
});
|
|
52126
|
+
}
|
|
52127
|
+
function proseParagraph(schema, content, around, align = null) {
|
|
52128
|
+
return schema.nodes.paragraph.create({ align }, proseInlines(schema, content, around));
|
|
52129
|
+
}
|
|
52130
|
+
/** The editor block for a rich text block. Its text also takes `around`, the marks of text that a
|
|
52131
|
+
* paste replaces. */
|
|
52132
|
+
function proseBlock(schema, block, around = []) {
|
|
52133
|
+
switch (block.type) {
|
|
52134
|
+
case "paragraph": return proseParagraph(schema, block.content, around, block.align ?? null);
|
|
52135
|
+
case "list": {
|
|
52136
|
+
const start = block.start ?? 1;
|
|
52137
|
+
const items = block.items.map((item, index) => schema.nodes.list_item.create({
|
|
52138
|
+
label: block.ordered ? `${start + index}.` : "•",
|
|
52139
|
+
listType: block.ordered ? "ordered" : "bullet",
|
|
52140
|
+
spread: false,
|
|
52141
|
+
checked: null
|
|
52142
|
+
}, item.blocks.map((child) => proseBlock(schema, child, around))));
|
|
52143
|
+
return block.ordered ? schema.nodes.ordered_list.create({
|
|
52144
|
+
order: start,
|
|
52145
|
+
spread: false
|
|
52146
|
+
}, items) : schema.nodes.bullet_list.create({ spread: false }, items);
|
|
52147
|
+
}
|
|
52148
|
+
case "table": {
|
|
52149
|
+
const row = (cells, index) => {
|
|
52150
|
+
const header = index === 0;
|
|
52151
|
+
return schema.nodes[header ? "table_header_row" : "table_row"].create(null, cells.rows[index].map((cell) => schema.nodes[header ? "table_header" : "table_cell"].create({ alignment: null }, proseParagraph(schema, cell.content, around))));
|
|
52152
|
+
};
|
|
52153
|
+
return schema.nodes.table.create(null, block.rows.map((_, index) => row(block, index)));
|
|
52154
|
+
}
|
|
52155
|
+
}
|
|
52156
|
+
}
|
|
52157
|
+
/** The editor document for a canonical rich text document. */
|
|
52158
|
+
function richTextToProse(schema, document) {
|
|
52159
|
+
const blocks = document.blocks.map((block) => proseBlock(schema, block));
|
|
52160
|
+
if (!blocks.length) blocks.push(proseParagraph(schema, [], []));
|
|
52161
|
+
return schema.topNodeType.create(null, blocks);
|
|
52162
|
+
}
|
|
52163
|
+
function richMarksOf(marks) {
|
|
52164
|
+
const result = [];
|
|
52165
|
+
for (const mark of marks) {
|
|
52166
|
+
const type = richMarks$1[mark.type.name];
|
|
52167
|
+
if (!type) throw unrepresentable(`the ${mark.type.name} mark`);
|
|
52168
|
+
result.push(type === "link" ? {
|
|
52169
|
+
type: "link",
|
|
52170
|
+
href: String(mark.attrs.href ?? "")
|
|
52171
|
+
} : { type });
|
|
52172
|
+
}
|
|
52173
|
+
return canonicalMarks(result);
|
|
52174
|
+
}
|
|
52175
|
+
function richInlines(node) {
|
|
52176
|
+
const builder = new InlineBuilder();
|
|
52177
|
+
node.forEach((child) => {
|
|
52178
|
+
if (child.type.name === "hardbreak") builder.break();
|
|
52179
|
+
else if (child.isText) {
|
|
52180
|
+
const text = child.text;
|
|
52181
|
+
if (textProblem(text)) throw unrepresentable("a separator in its text");
|
|
52182
|
+
const marks = richMarksOf(child.marks);
|
|
52183
|
+
if (marks.some((mark) => mark.type === "link" && linkProblem(mark.href))) throw unrepresentable("a link without an address");
|
|
52184
|
+
builder.text(text, marks);
|
|
52185
|
+
} else throw unrepresentable(`a ${child.type.name}`);
|
|
52186
|
+
});
|
|
52187
|
+
return builder.finish();
|
|
52188
|
+
}
|
|
52189
|
+
function richParagraph(node, aligned) {
|
|
52190
|
+
if (node.type.name !== "paragraph") throw unrepresentable(`a ${node.type.name}`);
|
|
52191
|
+
const align = node.attrs.align;
|
|
52192
|
+
if (align != null && !aligned) throw unrepresentable("an aligned table cell");
|
|
52193
|
+
if (align != null && align !== "center" && align !== "right" && align !== "justify") throw unrepresentable(`${align} alignment`);
|
|
52194
|
+
const content = richInlines(node);
|
|
52195
|
+
return align == null ? {
|
|
52196
|
+
type: "paragraph",
|
|
52197
|
+
content
|
|
52198
|
+
} : {
|
|
52199
|
+
type: "paragraph",
|
|
52200
|
+
align,
|
|
52201
|
+
content
|
|
52202
|
+
};
|
|
52203
|
+
}
|
|
52204
|
+
function richBlock(node, listDepth) {
|
|
52205
|
+
switch (node.type.name) {
|
|
52206
|
+
case "paragraph": return richParagraph(node, true);
|
|
52207
|
+
case "bullet_list":
|
|
52208
|
+
case "ordered_list": {
|
|
52209
|
+
const depth = listDepth + 1;
|
|
52210
|
+
if (depth > 9) throw unrepresentable(`lists deeper than 9 levels`);
|
|
52211
|
+
const ordered = node.type.name === "ordered_list";
|
|
52212
|
+
const items = [];
|
|
52213
|
+
node.forEach((item) => {
|
|
52214
|
+
if (item.type.name !== "list_item") throw unrepresentable(`a ${item.type.name}`);
|
|
52215
|
+
if (item.attrs.checked != null) throw unrepresentable("a task");
|
|
52216
|
+
const blocks = [];
|
|
52217
|
+
item.forEach((child, _offset, index) => {
|
|
52218
|
+
if (index === 0) blocks.push(richParagraph(child, true));
|
|
52219
|
+
else if (/^(bullet|ordered)_list$/.test(child.type.name)) blocks.push(richBlock(child, depth));
|
|
52220
|
+
else throw unrepresentable(`a ${child.type.name} in a list item`);
|
|
52221
|
+
});
|
|
52222
|
+
items.push({ blocks });
|
|
52223
|
+
});
|
|
52224
|
+
const start = Number(node.attrs.order ?? 1);
|
|
52225
|
+
if (ordered && (!Number.isSafeInteger(start) || start < 1 || start > 2147483647)) throw unrepresentable(`a list that starts below 1 or above ${maximumListStart}`);
|
|
52226
|
+
return ordered && start !== 1 ? {
|
|
52227
|
+
type: "list",
|
|
52228
|
+
ordered,
|
|
52229
|
+
start,
|
|
52230
|
+
items
|
|
52231
|
+
} : {
|
|
52232
|
+
type: "list",
|
|
52233
|
+
ordered,
|
|
52234
|
+
items
|
|
52235
|
+
};
|
|
52236
|
+
}
|
|
52237
|
+
case "table": {
|
|
52238
|
+
if (listDepth > 0) throw unrepresentable("a table in a list");
|
|
52239
|
+
const rows = [];
|
|
52240
|
+
node.forEach((row) => {
|
|
52241
|
+
const cells = [];
|
|
52242
|
+
row.forEach((cell) => {
|
|
52243
|
+
if (cell.attrs.alignment != null || Number(cell.attrs.colspan ?? 1) !== 1 || Number(cell.attrs.rowspan ?? 1) !== 1) throw unrepresentable("an aligned or merged table cell");
|
|
52244
|
+
if (cell.childCount !== 1) throw unrepresentable("a cell of several blocks");
|
|
52245
|
+
const paragraph = richParagraph(cell.firstChild, false);
|
|
52246
|
+
cells.push({ content: paragraph.content });
|
|
52247
|
+
});
|
|
52248
|
+
rows.push(cells);
|
|
52249
|
+
});
|
|
52250
|
+
if (!rows.length || !rows[0].length || rows.some((row) => row.length !== rows[0].length)) throw unrepresentable("a table with uneven rows");
|
|
52251
|
+
return {
|
|
52252
|
+
type: "table",
|
|
52253
|
+
rows
|
|
52254
|
+
};
|
|
52255
|
+
}
|
|
52256
|
+
default: throw unrepresentable(`a ${node.type.name}`);
|
|
52257
|
+
}
|
|
52258
|
+
}
|
|
52259
|
+
/** The canonical rich text document for an editor document. Throws `preservation` when the
|
|
52260
|
+
* document holds something RTF cannot. The editor always holds a block, so its one empty
|
|
52261
|
+
* paragraph is an empty document when `emptyAsNone`, as it is when the document loaded empty. */
|
|
52262
|
+
function proseToRichText(doc, emptyAsNone = true) {
|
|
52263
|
+
const blocks = [];
|
|
52264
|
+
doc.forEach((node) => blocks.push(richBlock(node, 0)));
|
|
52265
|
+
if (emptyAsNone && blocks.length === 1 && blocks[0].type === "paragraph" && !blocks[0].align && !blocks[0].content.length) blocks.length = 0;
|
|
52266
|
+
return {
|
|
52267
|
+
version: 1,
|
|
52268
|
+
blocks
|
|
52269
|
+
};
|
|
52270
|
+
}
|
|
52271
|
+
/** Why `node` cannot be in an RTF document at `$depth` lists deep, or undefined. Checks the node
|
|
52272
|
+
* itself and its direct children, not its descendants. */
|
|
52273
|
+
function refusal(node, listDepth, inCell) {
|
|
52274
|
+
const name = node.type.name;
|
|
52275
|
+
if (!allowedNodes.has(name)) return name;
|
|
52276
|
+
for (const mark of node.marks) if (!richMarks$1[mark.type.name]) return mark.type.name;
|
|
52277
|
+
if (node.isText && textProblem(node.text)) return "text";
|
|
52278
|
+
const href = node.type.schema.marks.link?.isInSet(node.marks)?.attrs.href;
|
|
52279
|
+
if (href !== void 0 && linkProblem(String(href ?? ""))) return "link";
|
|
52280
|
+
if (name === "paragraph" && node.attrs.align != null && inCell) return "aligned cell";
|
|
52281
|
+
if (name === "list_item") {
|
|
52282
|
+
if (node.attrs.checked != null) return "task";
|
|
52283
|
+
for (let index = 1; index < node.childCount; index++) if (!/^(bullet|ordered)_list$/.test(node.child(index).type.name)) return "list item block";
|
|
52284
|
+
}
|
|
52285
|
+
if ((name === "bullet_list" || name === "ordered_list") && listDepth > 9) return "list depth";
|
|
52286
|
+
const order = Number(node.attrs.order);
|
|
52287
|
+
if (name === "ordered_list" && !(order >= 1 && order <= 2147483647)) return "list start";
|
|
52288
|
+
if (name === "table" && listDepth > 0) return "table in list";
|
|
52289
|
+
if ((name === "table_cell" || name === "table_header") && (node.attrs.alignment != null || Number(node.attrs.colspan ?? 1) !== 1 || Number(node.attrs.rowspan ?? 1) !== 1)) return "cell";
|
|
52290
|
+
}
|
|
52291
|
+
/** Whether the document a transaction reaches holds only what RTF can, read in the part it
|
|
52292
|
+
* changed and that part's ancestors. */
|
|
52293
|
+
function richTextAllows(tr, before) {
|
|
52294
|
+
if (!tr.docChanged) return true;
|
|
52295
|
+
const doc = tr.doc;
|
|
52296
|
+
const start = before.doc.content.findDiffStart(doc.content);
|
|
52297
|
+
if (start == null) return true;
|
|
52298
|
+
const end = before.doc.content.findDiffEnd(doc.content);
|
|
52299
|
+
const to = Math.max(start, end?.b ?? doc.content.size);
|
|
52300
|
+
let allowed = true;
|
|
52301
|
+
const $start = doc.resolve(start);
|
|
52302
|
+
let lists = 0;
|
|
52303
|
+
for (let depth = 1; depth <= $start.depth; depth++) {
|
|
52304
|
+
const node = $start.node(depth);
|
|
52305
|
+
if (/^(bullet|ordered)_list$/.test(node.type.name)) lists++;
|
|
52306
|
+
if (refusal(node, lists, false)) return false;
|
|
52307
|
+
}
|
|
52308
|
+
doc.nodesBetween(start, Math.min(to, doc.content.size), (node, pos) => {
|
|
52309
|
+
if (!allowed) return false;
|
|
52310
|
+
const $pos = doc.resolve(pos);
|
|
52311
|
+
let depth = /^(bullet|ordered)_list$/.test(node.type.name) ? 1 : 0;
|
|
52312
|
+
let inCell = false;
|
|
52313
|
+
for (let level = $pos.depth; level > 0; level--) {
|
|
52314
|
+
const name = $pos.node(level).type.name;
|
|
52315
|
+
if (/^(bullet|ordered)_list$/.test(name)) depth++;
|
|
52316
|
+
if (name === "table_cell" || name === "table_header") inCell = true;
|
|
52317
|
+
}
|
|
52318
|
+
if (refusal(node, depth, inCell)) allowed = false;
|
|
52319
|
+
return allowed;
|
|
52320
|
+
});
|
|
52321
|
+
return allowed;
|
|
52322
|
+
}
|
|
52323
|
+
/** The ranges of runs that have both a link and underline, which canonical form removes the
|
|
52324
|
+
* underline from, in the part of `doc` from `from` to `to`. */
|
|
52325
|
+
function linkedUnderlines(doc, from, to) {
|
|
52326
|
+
const ranges = [];
|
|
52327
|
+
const { link, underline } = doc.type.schema.marks;
|
|
52328
|
+
if (!link || !underline) return ranges;
|
|
52329
|
+
doc.nodesBetween(from, to, (node, pos) => {
|
|
52330
|
+
if (node.isText && link.isInSet(node.marks) && underline.isInSet(node.marks)) ranges.push({
|
|
52331
|
+
from: pos,
|
|
52332
|
+
to: pos + node.nodeSize
|
|
52333
|
+
});
|
|
52334
|
+
});
|
|
52335
|
+
return ranges;
|
|
52336
|
+
}
|
|
52337
|
+
/** Whether every character from `from` to `to`, or the caret's marks, is in a link. */
|
|
52338
|
+
function inLink(state) {
|
|
52339
|
+
const { link } = state.schema.marks;
|
|
52340
|
+
if (!link) return false;
|
|
52341
|
+
const { from, to, empty, $from } = state.selection;
|
|
52342
|
+
if (empty) return Boolean(link.isInSet(state.storedMarks ?? $from.marks()));
|
|
52343
|
+
let text = false;
|
|
52344
|
+
let all = true;
|
|
52345
|
+
state.doc.nodesBetween(from, to, (node) => {
|
|
52346
|
+
if (!node.isText) return true;
|
|
52347
|
+
text = true;
|
|
52348
|
+
if (!link.isInSet(node.marks)) all = false;
|
|
52349
|
+
return all;
|
|
52350
|
+
});
|
|
52351
|
+
return text && all;
|
|
52352
|
+
}
|
|
52353
|
+
//#endregion
|
|
52354
|
+
//#region src/formats/rich-text-import.ts
|
|
52355
|
+
var skipped = /* @__PURE__ */ new Set([
|
|
52356
|
+
"SCRIPT",
|
|
52357
|
+
"STYLE",
|
|
52358
|
+
"TEMPLATE",
|
|
52359
|
+
"NOSCRIPT",
|
|
52360
|
+
"IFRAME",
|
|
52361
|
+
"OBJECT",
|
|
52362
|
+
"EMBED",
|
|
52363
|
+
"HEAD",
|
|
52364
|
+
"TITLE",
|
|
52365
|
+
"META",
|
|
52366
|
+
"LINK",
|
|
52367
|
+
"SVG",
|
|
52368
|
+
"MATH",
|
|
52369
|
+
"IMG",
|
|
52370
|
+
"PICTURE",
|
|
52371
|
+
"VIDEO",
|
|
52372
|
+
"AUDIO",
|
|
52373
|
+
"CANVAS",
|
|
52374
|
+
"BUTTON",
|
|
52375
|
+
"INPUT",
|
|
52376
|
+
"SELECT",
|
|
52377
|
+
"TEXTAREA",
|
|
52378
|
+
"HR"
|
|
52379
|
+
]);
|
|
52380
|
+
var blockTags = /* @__PURE__ */ new Set([
|
|
52381
|
+
"ADDRESS",
|
|
52382
|
+
"ARTICLE",
|
|
52383
|
+
"ASIDE",
|
|
52384
|
+
"BLOCKQUOTE",
|
|
52385
|
+
"BODY",
|
|
52386
|
+
"CENTER",
|
|
52387
|
+
"DD",
|
|
52388
|
+
"DETAILS",
|
|
52389
|
+
"DIV",
|
|
52390
|
+
"DL",
|
|
52391
|
+
"DT",
|
|
52392
|
+
"FIELDSET",
|
|
52393
|
+
"FIGCAPTION",
|
|
52394
|
+
"FIGURE",
|
|
52395
|
+
"FOOTER",
|
|
52396
|
+
"FORM",
|
|
52397
|
+
"H1",
|
|
52398
|
+
"H2",
|
|
52399
|
+
"H3",
|
|
52400
|
+
"H4",
|
|
52401
|
+
"H5",
|
|
52402
|
+
"H6",
|
|
52403
|
+
"HEADER",
|
|
52404
|
+
"HTML",
|
|
52405
|
+
"LI",
|
|
52406
|
+
"MAIN",
|
|
52407
|
+
"NAV",
|
|
52408
|
+
"OL",
|
|
52409
|
+
"P",
|
|
52410
|
+
"PRE",
|
|
52411
|
+
"SECTION",
|
|
52412
|
+
"SUMMARY",
|
|
52413
|
+
"TABLE",
|
|
52414
|
+
"UL"
|
|
52415
|
+
]);
|
|
52416
|
+
function withMark(marks, mark) {
|
|
52417
|
+
if (marks.some((held) => held.type === mark.type)) return marks;
|
|
52418
|
+
const other = mark.type === "superscript" ? "subscript" : mark.type === "subscript" ? "superscript" : "";
|
|
52419
|
+
return canonicalMarks([...marks.filter((held) => held.type !== other), mark]);
|
|
52420
|
+
}
|
|
52421
|
+
function withoutMark(marks, type) {
|
|
52422
|
+
return marks.filter((mark) => mark.type !== type);
|
|
52423
|
+
}
|
|
52424
|
+
/** The marks an element adds to its text, from its tag and its inline style. */
|
|
52425
|
+
function elementMarks(element, marks, schemes) {
|
|
52426
|
+
const tag = element.tagName;
|
|
52427
|
+
if (tag === "B" || tag === "STRONG") marks = withMark(marks, { type: "bold" });
|
|
52428
|
+
if (tag === "I" || tag === "EM" || tag === "CITE" || tag === "DFN" || tag === "VAR") marks = withMark(marks, { type: "italic" });
|
|
52429
|
+
if (tag === "U" || tag === "INS") marks = withMark(marks, { type: "underline" });
|
|
52430
|
+
if (tag === "S" || tag === "DEL" || tag === "STRIKE") marks = withMark(marks, { type: "strikethrough" });
|
|
52431
|
+
if (tag === "SUP") marks = withMark(marks, { type: "superscript" });
|
|
52432
|
+
if (tag === "SUB") marks = withMark(marks, { type: "subscript" });
|
|
52433
|
+
if (tag === "CODE" || tag === "KBD" || tag === "SAMP" || tag === "TT") marks = withMark(marks, { type: "code" });
|
|
52434
|
+
if (tag === "A") {
|
|
52435
|
+
const href = element.getAttribute("href")?.trim();
|
|
52436
|
+
if (href && isSafeLinkTarget(href, schemes) && !linkProblem(href)) marks = withMark(withoutMark(marks, "link"), {
|
|
52437
|
+
type: "link",
|
|
52438
|
+
href
|
|
52439
|
+
});
|
|
52440
|
+
}
|
|
52441
|
+
const style = element instanceof HTMLElement ? element.style : void 0;
|
|
52442
|
+
if (style) {
|
|
52443
|
+
const weight = style.fontWeight;
|
|
52444
|
+
if (weight === "bold" || weight === "bolder" || Number(weight) >= 600) marks = withMark(marks, { type: "bold" });
|
|
52445
|
+
else if (weight === "normal" || weight && Number(weight) < 600) marks = withoutMark(marks, "bold");
|
|
52446
|
+
if (style.fontStyle === "italic" || style.fontStyle === "oblique") marks = withMark(marks, { type: "italic" });
|
|
52447
|
+
else if (style.fontStyle === "normal") marks = withoutMark(marks, "italic");
|
|
52448
|
+
const decoration = `${style.textDecoration} ${style.textDecorationLine}`;
|
|
52449
|
+
if (/underline/.test(decoration)) marks = withMark(marks, { type: "underline" });
|
|
52450
|
+
if (/line-through/.test(decoration)) marks = withMark(marks, { type: "strikethrough" });
|
|
52451
|
+
if (style.verticalAlign === "super") marks = withMark(marks, { type: "superscript" });
|
|
52452
|
+
if (style.verticalAlign === "sub") marks = withMark(marks, { type: "subscript" });
|
|
52453
|
+
}
|
|
52454
|
+
return canonicalMarks(marks);
|
|
52455
|
+
}
|
|
52456
|
+
function alignment(element) {
|
|
52457
|
+
const value = (element instanceof HTMLElement ? element.style.textAlign : "") || element.getAttribute("align") || (element.tagName === "CENTER" ? "center" : "");
|
|
52458
|
+
return value === "center" || value === "right" || value === "justify" ? value : void 0;
|
|
52459
|
+
}
|
|
52460
|
+
/** Reads blocks from a DOM, the way a person reads the page. */
|
|
52461
|
+
var Reader = class Reader {
|
|
52462
|
+
schemes;
|
|
52463
|
+
depth;
|
|
52464
|
+
blocks = [];
|
|
52465
|
+
inline;
|
|
52466
|
+
align;
|
|
52467
|
+
space;
|
|
52468
|
+
constructor(schemes, depth) {
|
|
52469
|
+
this.schemes = schemes;
|
|
52470
|
+
this.depth = depth;
|
|
52471
|
+
}
|
|
52472
|
+
/** Ends the open paragraph, if any. */
|
|
52473
|
+
flush() {
|
|
52474
|
+
if (!this.inline) return;
|
|
52475
|
+
const content = this.inline.finish();
|
|
52476
|
+
if (content.at(-1)?.type === "break") content.pop();
|
|
52477
|
+
this.blocks.push(this.align ? {
|
|
52478
|
+
type: "paragraph",
|
|
52479
|
+
align: this.align,
|
|
52480
|
+
content
|
|
52481
|
+
} : {
|
|
52482
|
+
type: "paragraph",
|
|
52483
|
+
content
|
|
52484
|
+
});
|
|
52485
|
+
this.inline = void 0;
|
|
52486
|
+
this.align = void 0;
|
|
52487
|
+
this.space = void 0;
|
|
52488
|
+
}
|
|
52489
|
+
open() {
|
|
52490
|
+
this.inline ??= new InlineBuilder();
|
|
52491
|
+
return this.inline;
|
|
52492
|
+
}
|
|
52493
|
+
text(value, marks, pre) {
|
|
52494
|
+
if (pre) {
|
|
52495
|
+
value.split(/\r\n|[\n\r]/).forEach((line, index) => {
|
|
52496
|
+
if (index) this.flush();
|
|
52497
|
+
appendText(this.open(), line, marks);
|
|
52498
|
+
});
|
|
52499
|
+
return;
|
|
52500
|
+
}
|
|
52501
|
+
const collapsed = value.replace(/[ \t\n\r\f]+/g, " ");
|
|
52502
|
+
if (!collapsed) return;
|
|
52503
|
+
if (collapsed === " ") {
|
|
52504
|
+
if (this.inline?.content.length) this.space ??= marks;
|
|
52505
|
+
return;
|
|
52506
|
+
}
|
|
52507
|
+
const builder = this.open();
|
|
52508
|
+
const empty = !builder.content.length;
|
|
52509
|
+
let text = collapsed;
|
|
52510
|
+
if (text.startsWith(" ")) {
|
|
52511
|
+
if (!empty) this.space ??= marks;
|
|
52512
|
+
text = text.slice(1);
|
|
52513
|
+
}
|
|
52514
|
+
if (!text) return;
|
|
52515
|
+
if (this.space) {
|
|
52516
|
+
builder.text(" ", this.space);
|
|
52517
|
+
this.space = void 0;
|
|
52518
|
+
}
|
|
52519
|
+
if (text.endsWith(" ")) {
|
|
52520
|
+
this.space = marks;
|
|
52521
|
+
text = text.slice(0, -1);
|
|
52522
|
+
}
|
|
52523
|
+
appendText(builder, text, marks);
|
|
52524
|
+
}
|
|
52525
|
+
lineBreak() {
|
|
52526
|
+
this.space = void 0;
|
|
52527
|
+
this.open().break();
|
|
52528
|
+
}
|
|
52529
|
+
/** Starts a paragraph that takes `element`'s alignment. */
|
|
52530
|
+
paragraph(element) {
|
|
52531
|
+
this.flush();
|
|
52532
|
+
this.align = alignment(element);
|
|
52533
|
+
}
|
|
52534
|
+
push(block) {
|
|
52535
|
+
this.flush();
|
|
52536
|
+
this.blocks.push(block);
|
|
52537
|
+
}
|
|
52538
|
+
read(node, marks, pre) {
|
|
52539
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
52540
|
+
this.text(node.textContent ?? "", marks, pre);
|
|
52541
|
+
return;
|
|
52542
|
+
}
|
|
52543
|
+
if (node.nodeType !== Node.ELEMENT_NODE) return;
|
|
52544
|
+
const element = node;
|
|
52545
|
+
const tag = element.tagName.toUpperCase();
|
|
52546
|
+
if (skipped.has(tag) || element instanceof HTMLElement && element.hidden) return;
|
|
52547
|
+
if (element instanceof HTMLElement && element.style.display === "none") return;
|
|
52548
|
+
if (tag === "BR") {
|
|
52549
|
+
this.lineBreak();
|
|
52550
|
+
return;
|
|
52551
|
+
}
|
|
52552
|
+
if (tag === "UL" || tag === "OL") {
|
|
52553
|
+
this.push(this.list(element, marks));
|
|
52554
|
+
return;
|
|
52555
|
+
}
|
|
52556
|
+
if (tag === "TABLE") {
|
|
52557
|
+
const table = this.depth === 0 && !spansRows(element);
|
|
52558
|
+
const rows = tableRows(element, marks, this.schemes, table);
|
|
52559
|
+
if (!rows.length) return;
|
|
52560
|
+
if (table) this.push({
|
|
52561
|
+
type: "table",
|
|
52562
|
+
rows
|
|
52563
|
+
});
|
|
52564
|
+
else for (const row of rows) {
|
|
52565
|
+
this.paragraph(element);
|
|
52566
|
+
this.open();
|
|
52567
|
+
row.forEach((cell, index) => {
|
|
52568
|
+
if (index) this.open().text(" ", []);
|
|
52569
|
+
for (const inline of cell.content) if (inline.type === "break") this.open().break();
|
|
52570
|
+
else this.open().text(inline.text, inline.marks ?? []);
|
|
52571
|
+
});
|
|
52572
|
+
this.flush();
|
|
52573
|
+
}
|
|
52574
|
+
return;
|
|
52575
|
+
}
|
|
52576
|
+
let inner = elementMarks(element, marks, this.schemes);
|
|
52577
|
+
if (/^H[1-6]$/.test(tag)) inner = withMark(inner, { type: "bold" });
|
|
52578
|
+
const block = blockTags.has(tag);
|
|
52579
|
+
if (block) this.paragraph(element);
|
|
52580
|
+
if (tag === "P" || /^H[1-6]$/.test(tag)) this.open();
|
|
52581
|
+
const preformatted = pre || tag === "PRE" || element instanceof HTMLElement && /^pre/.test(element.style.whiteSpace);
|
|
52582
|
+
if (tag === "PRE") inner = withMark(inner, { type: "code" });
|
|
52583
|
+
for (const child of element.childNodes) this.read(child, inner, preformatted);
|
|
52584
|
+
if (block) this.flush();
|
|
52585
|
+
}
|
|
52586
|
+
list(element, marks) {
|
|
52587
|
+
const ordered = element.tagName.toUpperCase() === "OL";
|
|
52588
|
+
const start = Number(element.getAttribute("start") ?? 1);
|
|
52589
|
+
const items = [];
|
|
52590
|
+
const inner = elementMarks(element, marks, this.schemes);
|
|
52591
|
+
for (const child of element.childNodes) {
|
|
52592
|
+
if (child.nodeType === Node.ELEMENT_NODE && child.tagName.toUpperCase() === "LI") {
|
|
52593
|
+
items.push({ blocks: this.item(child, inner) });
|
|
52594
|
+
continue;
|
|
52595
|
+
}
|
|
52596
|
+
const reader = new Reader(this.schemes, this.depth + 1);
|
|
52597
|
+
reader.read(child, inner, false);
|
|
52598
|
+
reader.flush();
|
|
52599
|
+
if (!reader.blocks.length) continue;
|
|
52600
|
+
const blocks = itemBlocks(reader.blocks, this.depth + 1);
|
|
52601
|
+
if (items.length && blocks[0].type === "paragraph" && !blocks[0].content.length) items.at(-1).blocks.push(...blocks.slice(1));
|
|
52602
|
+
else items.push({ blocks });
|
|
52603
|
+
}
|
|
52604
|
+
if (!items.length) items.push({ blocks: [{
|
|
52605
|
+
type: "paragraph",
|
|
52606
|
+
content: []
|
|
52607
|
+
}] });
|
|
52608
|
+
return ordered && Number.isSafeInteger(start) && start > 1 ? {
|
|
52609
|
+
type: "list",
|
|
52610
|
+
ordered,
|
|
52611
|
+
start: Math.min(start, maximumListStart),
|
|
52612
|
+
items
|
|
52613
|
+
} : {
|
|
52614
|
+
type: "list",
|
|
52615
|
+
ordered,
|
|
52616
|
+
items
|
|
52617
|
+
};
|
|
52618
|
+
}
|
|
52619
|
+
item(element, marks) {
|
|
52620
|
+
const reader = new Reader(this.schemes, this.depth + 1);
|
|
52621
|
+
const inner = elementMarks(element, marks, this.schemes);
|
|
52622
|
+
for (const child of element.childNodes) reader.read(child, inner, false);
|
|
52623
|
+
reader.flush();
|
|
52624
|
+
return itemBlocks(reader.blocks, this.depth + 1);
|
|
52625
|
+
}
|
|
52626
|
+
};
|
|
52627
|
+
/** A list item's blocks as RTF holds them: one paragraph, then lists. Further paragraphs join the
|
|
52628
|
+
* first as lines. Lists keep every level here; the paste lifts those deeper than RTF holds. */
|
|
52629
|
+
function itemBlocks(blocks, depth) {
|
|
52630
|
+
const builder = new InlineBuilder();
|
|
52631
|
+
let align;
|
|
52632
|
+
let lines = 0;
|
|
52633
|
+
const nested = [];
|
|
52634
|
+
const line = (content) => {
|
|
52635
|
+
if (lines++) builder.break();
|
|
52636
|
+
for (const inline of content) if (inline.type === "break") builder.break();
|
|
52637
|
+
else builder.text(inline.text, inline.marks ?? []);
|
|
52638
|
+
};
|
|
52639
|
+
const flatten = (block) => {
|
|
52640
|
+
if (block.type === "paragraph") line(block.content);
|
|
52641
|
+
else if (block.type === "list") for (const item of block.items) for (const child of item.blocks) flatten(child);
|
|
52642
|
+
else for (const row of block.rows) for (const cell of row) line(cell.content);
|
|
52643
|
+
};
|
|
52644
|
+
for (const block of blocks) if (block.type === "list") nested.push(block);
|
|
52645
|
+
else if (block.type === "paragraph" && !nested.length) {
|
|
52646
|
+
if (!lines) align = block.align;
|
|
52647
|
+
line(block.content);
|
|
52648
|
+
} else flatten(block);
|
|
52649
|
+
const content = builder.finish();
|
|
52650
|
+
return [align ? {
|
|
52651
|
+
type: "paragraph",
|
|
52652
|
+
align,
|
|
52653
|
+
content
|
|
52654
|
+
} : {
|
|
52655
|
+
type: "paragraph",
|
|
52656
|
+
content
|
|
52657
|
+
}, ...nested];
|
|
52658
|
+
}
|
|
52659
|
+
/** Whether a cell of `table` itself, not of a table inside it, spans more than one row. */
|
|
52660
|
+
function spansRows(table) {
|
|
52661
|
+
return [...table.querySelectorAll("td[rowspan], th[rowspan]")].some((cell) => cell.closest("table") === table && Number(cell.getAttribute("rowspan")) > 1);
|
|
52662
|
+
}
|
|
52663
|
+
function tableRows(table, marks, schemes, even) {
|
|
52664
|
+
const rows = [];
|
|
52665
|
+
for (const row of table.querySelectorAll("tr")) {
|
|
52666
|
+
if (row.closest("table") !== table) continue;
|
|
52667
|
+
const cells = [];
|
|
52668
|
+
for (const cell of row.children) {
|
|
52669
|
+
if (cell.tagName !== "TD" && cell.tagName !== "TH") continue;
|
|
52670
|
+
const reader = new Reader(schemes, 1);
|
|
52671
|
+
const inner = elementMarks(cell, marks, schemes);
|
|
52672
|
+
for (const child of cell.childNodes) reader.read(child, inner, false);
|
|
52673
|
+
reader.flush();
|
|
52674
|
+
const builder = new InlineBuilder();
|
|
52675
|
+
let lines = 0;
|
|
52676
|
+
const add = (block) => {
|
|
52677
|
+
if (block.type === "paragraph") {
|
|
52678
|
+
if (lines++) builder.break();
|
|
52679
|
+
for (const inline of block.content) if (inline.type === "break") builder.break();
|
|
52680
|
+
else builder.text(inline.text, inline.marks ?? []);
|
|
52681
|
+
} else if (block.type === "list") for (const item of block.items) item.blocks.forEach(add);
|
|
52682
|
+
else for (const nestedRow of block.rows) for (const nestedCell of nestedRow) add({
|
|
52683
|
+
type: "paragraph",
|
|
52684
|
+
content: nestedCell.content
|
|
52685
|
+
});
|
|
52686
|
+
};
|
|
52687
|
+
reader.blocks.forEach(add);
|
|
52688
|
+
const content = builder.finish();
|
|
52689
|
+
const span = Math.max(1, Math.min(100, Number(cell.getAttribute("colspan") ?? 1) || 1));
|
|
52690
|
+
cells.push({ content });
|
|
52691
|
+
for (let index = 1; index < span; index++) cells.push({ content: [] });
|
|
52692
|
+
}
|
|
52693
|
+
if (cells.length) rows.push(cells);
|
|
52694
|
+
}
|
|
52695
|
+
const width = even ? Math.max(0, ...rows.map((row) => row.length)) : 0;
|
|
52696
|
+
for (const row of rows) while (row.length < width) row.push({ content: [] });
|
|
52697
|
+
return rows;
|
|
52698
|
+
}
|
|
52699
|
+
/** Blocks for pasted HTML. */
|
|
52700
|
+
function htmlBlocks(html, schemes) {
|
|
52701
|
+
const parsed = new DOMParser().parseFromString(html, "text/html");
|
|
52702
|
+
const reader = new Reader(schemes, 0);
|
|
52703
|
+
reader.read(parsed.body, [], false);
|
|
52704
|
+
reader.flush();
|
|
52705
|
+
return reader.blocks;
|
|
52706
|
+
}
|
|
52707
|
+
/** Every block's text as lines of one paragraph, for a place that holds only one, such as a
|
|
52708
|
+
* table cell. */
|
|
52709
|
+
function asLines(blocks) {
|
|
52710
|
+
const builder = new InlineBuilder();
|
|
52711
|
+
let lines = 0;
|
|
52712
|
+
const add = (content) => {
|
|
52713
|
+
if (lines++) builder.break();
|
|
52714
|
+
for (const inline of content) if (inline.type === "break") builder.break();
|
|
52715
|
+
else builder.text(inline.text, inline.marks ?? []);
|
|
52716
|
+
};
|
|
52717
|
+
const visit = (block) => {
|
|
52718
|
+
if (block.type === "paragraph") add(block.content);
|
|
52719
|
+
else if (block.type === "list") block.items.forEach((item) => item.blocks.forEach(visit));
|
|
52720
|
+
else block.rows.forEach((row) => add(row.flatMap((cell, index) => index ? [{
|
|
52721
|
+
type: "text",
|
|
52722
|
+
text: " "
|
|
52723
|
+
}, ...cell.content] : cell.content)));
|
|
52724
|
+
};
|
|
52725
|
+
blocks.forEach(visit);
|
|
52726
|
+
return [{
|
|
52727
|
+
type: "paragraph",
|
|
52728
|
+
content: builder.finish()
|
|
52729
|
+
}];
|
|
52730
|
+
}
|
|
52731
|
+
/** `list`, at `depth` lists deep, with every level beyond the deepest RTF holds lifted to the
|
|
52732
|
+
* deepest: the items of a list that would be too deep become items of its parent list. */
|
|
52733
|
+
function liftDeepLevels(list, depth) {
|
|
52734
|
+
const flat = (deep) => deep.items.flatMap((item) => [{ blocks: [item.blocks[0]] }, ...item.blocks.slice(1).flatMap((nested) => flat(nested))]);
|
|
52735
|
+
return {
|
|
52736
|
+
...list,
|
|
52737
|
+
items: list.items.flatMap((item) => {
|
|
52738
|
+
const [paragraph, ...nested] = item.blocks;
|
|
52739
|
+
const kept = [];
|
|
52740
|
+
const lifted = [];
|
|
52741
|
+
for (const child of nested) if (depth + 1 <= 9) kept.push(liftDeepLevels(child, depth + 1));
|
|
52742
|
+
else lifted.push(...flat(child));
|
|
52743
|
+
return [{ blocks: [paragraph, ...kept] }, ...lifted];
|
|
52744
|
+
})
|
|
52745
|
+
};
|
|
52746
|
+
}
|
|
52747
|
+
/**
|
|
52748
|
+
* A paste of several blocks into a list item, which holds one paragraph: each paragraph becomes
|
|
52749
|
+
* an item beside the one at the caret, at its level. The first joins the item at the caret, the
|
|
52750
|
+
* text after the caret moves to the last, and a pasted list nests in the item before it.
|
|
52751
|
+
*/
|
|
52752
|
+
function listItemsSlice(schema, blocks, list, depth, marks) {
|
|
52753
|
+
const empty = {
|
|
52754
|
+
type: "paragraph",
|
|
52755
|
+
content: []
|
|
52756
|
+
};
|
|
52757
|
+
const items = [];
|
|
52758
|
+
for (const block of blocks) if (block.type === "paragraph") items.push({ blocks: [block] });
|
|
52759
|
+
else if (block.type === "list") {
|
|
52760
|
+
if (!items.length) items.push({ blocks: [empty] });
|
|
52761
|
+
items.at(-1).blocks.push(block);
|
|
52762
|
+
} else for (const line of asLines([block])) items.push({ blocks: [line] });
|
|
52763
|
+
if (!items.length || items.at(-1).blocks.length > 1) items.push({ blocks: [empty] });
|
|
52764
|
+
const ordered = list.type.name === "ordered_list";
|
|
52765
|
+
const nodes = liftDeepLevels({
|
|
52766
|
+
type: "list",
|
|
52767
|
+
ordered,
|
|
52768
|
+
items
|
|
52769
|
+
}, depth).items.map((item) => schema.nodes.list_item.create({
|
|
52770
|
+
label: ordered ? "1." : "•",
|
|
52771
|
+
listType: ordered ? "ordered" : "bullet",
|
|
52772
|
+
spread: false,
|
|
52773
|
+
checked: null
|
|
52774
|
+
}, item.blocks.map((block) => proseBlock(schema, block, marks))));
|
|
52775
|
+
return new Slice(Fragment.fromArray(nodes), 2, 2);
|
|
52776
|
+
}
|
|
52777
|
+
/**
|
|
52778
|
+
* The slice that `input` pastes into an RTF document at the selection. Explicit Markdown pastes as
|
|
52779
|
+
* its text, unparsed; HTML keeps what RTF can hold; plain text gives a paragraph for each line.
|
|
52780
|
+
* Plain text takes the marks of the text it replaces, as typing would.
|
|
52781
|
+
*/
|
|
52782
|
+
function richTextPaste(input, state, schemes) {
|
|
52783
|
+
const { schema, selection } = state;
|
|
52784
|
+
const { $from, $to } = selection;
|
|
52785
|
+
const html = input.markdown == null && input.html ? htmlBlocks(input.html, schemes) : void 0;
|
|
52786
|
+
const plain = html == null;
|
|
52787
|
+
let blocks = html ?? textBlocks(input.markdown ?? input.text);
|
|
52788
|
+
if (html && !blocks.some((block) => block.type !== "paragraph" || block.content.length)) blocks = textBlocks(input.text);
|
|
52789
|
+
let cell = false;
|
|
52790
|
+
let lists = 0;
|
|
52791
|
+
let list;
|
|
52792
|
+
for (let depth = $from.depth; depth > 0; depth--) {
|
|
52793
|
+
const node = $from.node(depth);
|
|
52794
|
+
const name = node.type.name;
|
|
52795
|
+
if (name === "table_cell" || name === "table_header") cell = true;
|
|
52796
|
+
if (name === "bullet_list" || name === "ordered_list") {
|
|
52797
|
+
lists++;
|
|
52798
|
+
list ??= node;
|
|
52799
|
+
}
|
|
52800
|
+
}
|
|
52801
|
+
if (cell) blocks = asLines(blocks);
|
|
52802
|
+
else blocks = blocks.map((block) => block.type === "list" ? liftDeepLevels(block, lists + 1) : block);
|
|
52803
|
+
const marks = plain ? state.storedMarks ?? ($from.marksAcross($to) ?? $from.marks()).filter((mark) => mark.type.name !== "link") : [];
|
|
52804
|
+
if (list && !cell && !(blocks.length === 1 && blocks[0].type === "paragraph")) return listItemsSlice(schema, blocks, list, lists, marks);
|
|
52805
|
+
const nodes = blocks.map((block) => proseBlock(schema, block, marks));
|
|
52806
|
+
if (!nodes.length) return Slice.empty;
|
|
52807
|
+
const openStart = nodes[0].type.name === "paragraph" ? 1 : 0;
|
|
52808
|
+
const openEnd = nodes.at(-1).type.name === "paragraph" ? 1 : 0;
|
|
52809
|
+
return new Slice(Fragment.fromArray(nodes), openStart, openEnd);
|
|
52810
|
+
}
|
|
52811
|
+
//#endregion
|
|
52812
|
+
//#region src/formats/rich-text.ts
|
|
52813
|
+
function source(ctx, text) {
|
|
52814
|
+
const { schema } = ctx.get(editorViewCtx).state;
|
|
52815
|
+
const document = richTextFromText(text);
|
|
52816
|
+
const emptyAsNone = document.blocks.length === 0;
|
|
52817
|
+
return {
|
|
52818
|
+
baseline: richTextToProse(schema, document),
|
|
52819
|
+
serialize: (doc) => richTextText(proseToRichText(doc, emptyAsNone))
|
|
52820
|
+
};
|
|
52821
|
+
}
|
|
52822
|
+
/** Rich text, which the host converts to and from RTF. The editor holds the canonical JSON as the
|
|
52823
|
+
* document's text. It reaches no Markdown parsing or preservation;
|
|
52824
|
+
* `test/formats/boundary.test.ts` checks its imports. */
|
|
52825
|
+
var richTextFormat = {
|
|
52826
|
+
format: "rtf",
|
|
52827
|
+
promise: "structure",
|
|
52828
|
+
literalOnly: false,
|
|
52829
|
+
sourceMode: false,
|
|
52830
|
+
formattedLabel: "richTextEditor",
|
|
52831
|
+
literalLabel: "sourceEditor",
|
|
52832
|
+
headings: false,
|
|
52833
|
+
nativeCopy: false,
|
|
52834
|
+
keepsInsertedLineEndings: false,
|
|
52835
|
+
markdownInput: false,
|
|
52836
|
+
load: source,
|
|
52837
|
+
parse: source,
|
|
52838
|
+
pendingExport: () => void 0,
|
|
52839
|
+
literalPaste: (input) => input.text,
|
|
52840
|
+
richText: {
|
|
52841
|
+
allows: richTextAllows,
|
|
52842
|
+
linkedUnderlines,
|
|
52843
|
+
inLink,
|
|
52844
|
+
paste: richTextPaste,
|
|
52845
|
+
copy(state, linkSchemes) {
|
|
52846
|
+
const content = state.selection.content().content;
|
|
52847
|
+
return clipboardContent(content, state.schema, linkSchemes);
|
|
52848
|
+
}
|
|
52849
|
+
},
|
|
52850
|
+
async print({ doc, signal, output }) {
|
|
52851
|
+
return printableMarkdown(doc.content, output.ctx().get(editorViewCtx).state.schema, void 0, output.context(), void 0, signal);
|
|
52852
|
+
},
|
|
52853
|
+
async clipboard({ all, version, output, assertCurrent }) {
|
|
52854
|
+
const { state } = output.ctx().get(editorViewCtx);
|
|
52855
|
+
const slice = all ? new Slice(state.doc.content, 0, 0) : state.selection.content();
|
|
52856
|
+
const content = slice.content;
|
|
52857
|
+
const markdown = output.richTextMarkdown(slice);
|
|
52858
|
+
const { html } = clipboardContent(content, state.schema, output.linkSchemes());
|
|
52859
|
+
const text = clipboardText(content);
|
|
52860
|
+
assertCurrent();
|
|
52861
|
+
return {
|
|
52862
|
+
...version,
|
|
52863
|
+
text,
|
|
52864
|
+
html,
|
|
52865
|
+
markdown,
|
|
52866
|
+
images: [],
|
|
52867
|
+
diagrams: []
|
|
52868
|
+
};
|
|
52869
|
+
}
|
|
52870
|
+
};
|
|
51645
52871
|
//#endregion
|
|
51646
52872
|
//#region src/formats/text.ts
|
|
51647
52873
|
/** A paragraph holding `text`, or an empty one. */
|
|
@@ -51656,10 +52882,12 @@ var textFormat = {
|
|
|
51656
52882
|
promise: "exact",
|
|
51657
52883
|
literalOnly: true,
|
|
51658
52884
|
sourceMode: false,
|
|
52885
|
+
formattedLabel: "formattedEditor",
|
|
51659
52886
|
literalLabel: "plainTextEditor",
|
|
51660
52887
|
headings: false,
|
|
51661
52888
|
nativeCopy: true,
|
|
51662
52889
|
keepsInsertedLineEndings: false,
|
|
52890
|
+
markdownInput: false,
|
|
51663
52891
|
load: (ctx) => paragraph(ctx, ""),
|
|
51664
52892
|
parse: paragraph,
|
|
51665
52893
|
pendingExport: () => void 0,
|
|
@@ -51681,11 +52909,18 @@ var textFormat = {
|
|
|
51681
52909
|
};
|
|
51682
52910
|
//#endregion
|
|
51683
52911
|
//#region src/formats/index.ts
|
|
51684
|
-
/** Every format Inkkit can open
|
|
51685
|
-
var documentFormats = [
|
|
52912
|
+
/** Every format Inkkit can open. */
|
|
52913
|
+
var documentFormats = [
|
|
52914
|
+
"md",
|
|
52915
|
+
"txt",
|
|
52916
|
+
"rtf"
|
|
52917
|
+
];
|
|
52918
|
+
/** The formats a host is offered when it chooses none. */
|
|
52919
|
+
var defaultFormats = ["md", "txt"];
|
|
51686
52920
|
var formatHandlers = {
|
|
51687
52921
|
md: markdownFormat,
|
|
51688
|
-
txt: textFormat
|
|
52922
|
+
txt: textFormat,
|
|
52923
|
+
rtf: richTextFormat
|
|
51689
52924
|
};
|
|
51690
52925
|
//#endregion
|
|
51691
52926
|
//#region src/editor-document.ts
|
|
@@ -51746,7 +52981,7 @@ var EditorDocument = class {
|
|
|
51746
52981
|
this.sourcePreservations.set(provenance, preservation);
|
|
51747
52982
|
}
|
|
51748
52983
|
}
|
|
51749
|
-
text = preservation.serialize(cleanSourceDoc(doc), this.carried);
|
|
52984
|
+
text = preservation.serialize(this.handler.richText ? doc : cleanSourceDoc(doc), this.carried);
|
|
51750
52985
|
} catch (error) {
|
|
51751
52986
|
throw coded(error, "preservation");
|
|
51752
52987
|
}
|
|
@@ -51793,7 +53028,7 @@ var EditorDocument = class {
|
|
|
51793
53028
|
*/
|
|
51794
53029
|
authoredSources(doc, positions) {
|
|
51795
53030
|
const none = /* @__PURE__ */ new Map();
|
|
51796
|
-
if (this.runtime.literal() || !positions.length) return none;
|
|
53031
|
+
if (this.runtime.literal() || this.handler.richText || !positions.length) return none;
|
|
51797
53032
|
try {
|
|
51798
53033
|
let marked = doc;
|
|
51799
53034
|
for (let index = positions.length - 1; index >= 0; index--) {
|
|
@@ -52305,7 +53540,7 @@ var rejoinLists = $prose(() => keymap$3({ Backspace: (state, dispatch) => {
|
|
|
52305
53540
|
const list = $from.node(-2);
|
|
52306
53541
|
const sublist = container.maybeChild(1);
|
|
52307
53542
|
const previous = list.maybeChild($from.index(-2) - 1)?.lastChild;
|
|
52308
|
-
if (index !== 0 || container.childCount !== 2 || !sublist || !isList(sublist) || previous?.type !== sublist.type) return false;
|
|
53543
|
+
if (index !== 0 || container.childCount !== 2 || !sublist || !isList$1(sublist) || previous?.type !== sublist.type) return false;
|
|
52309
53544
|
if (dispatch) {
|
|
52310
53545
|
const at = $from.before(-1);
|
|
52311
53546
|
const end = at - 2;
|
|
@@ -52317,7 +53552,7 @@ var rejoinLists = $prose(() => keymap$3({ Backspace: (state, dispatch) => {
|
|
|
52317
53552
|
}
|
|
52318
53553
|
const before = container.maybeChild(index - 1);
|
|
52319
53554
|
const after = container.maybeChild(index + 1);
|
|
52320
|
-
if (!before || !after || !isList(before) || after.type !== before.type) return false;
|
|
53555
|
+
if (!before || !after || !isList$1(before) || after.type !== before.type) return false;
|
|
52321
53556
|
if (dispatch) {
|
|
52322
53557
|
const at = $from.before();
|
|
52323
53558
|
const tr = state.tr.delete(at, $from.after());
|
|
@@ -52390,21 +53625,21 @@ var movedListItems = $prose(() => new Plugin({
|
|
|
52390
53625
|
}));
|
|
52391
53626
|
var textInputPolicy = $prose(() => textInputPlugin());
|
|
52392
53627
|
function outermostListDepth($pos) {
|
|
52393
|
-
for (let depth = 1; depth <= $pos.depth; depth++) if (isList($pos.node(depth))) return depth;
|
|
53628
|
+
for (let depth = 1; depth <= $pos.depth; depth++) if (isList$1($pos.node(depth))) return depth;
|
|
52394
53629
|
return null;
|
|
52395
53630
|
}
|
|
52396
53631
|
function innermostListDepth($pos) {
|
|
52397
|
-
for (let depth = $pos.depth; depth >= 1; depth--) if (isList($pos.node(depth))) return depth;
|
|
53632
|
+
for (let depth = $pos.depth; depth >= 1; depth--) if (isList$1($pos.node(depth))) return depth;
|
|
52398
53633
|
return null;
|
|
52399
53634
|
}
|
|
52400
|
-
function isList(node) {
|
|
53635
|
+
function isList$1(node) {
|
|
52401
53636
|
return node.type.name === "bullet_list" || node.type.name === "ordered_list";
|
|
52402
53637
|
}
|
|
52403
53638
|
function joinLists(tr, start, index) {
|
|
52404
53639
|
const $start = tr.doc.resolve(start);
|
|
52405
53640
|
const before = $start.parent.maybeChild(index - 1);
|
|
52406
53641
|
const after = $start.parent.maybeChild(index);
|
|
52407
|
-
if (before && after && isList(before) && after.type === before.type) tr.join($start.posAtIndex(index));
|
|
53642
|
+
if (before && after && isList$1(before) && after.type === before.type) tr.join($start.posAtIndex(index));
|
|
52408
53643
|
}
|
|
52409
53644
|
//#endregion
|
|
52410
53645
|
//#region src/editor-commands.ts
|
|
@@ -52412,7 +53647,67 @@ function headingLevel(command) {
|
|
|
52412
53647
|
const match = /^heading([1-6])$/.exec(command);
|
|
52413
53648
|
return match ? Number(match[1]) : void 0;
|
|
52414
53649
|
}
|
|
52415
|
-
|
|
53650
|
+
/** What an RTF document cannot hold: headings, code blocks, quotes, tasks and highlight. Every
|
|
53651
|
+
* other command is available as in Markdown, and alignment only here. */
|
|
53652
|
+
var unavailableInRichText = [
|
|
53653
|
+
"heading1",
|
|
53654
|
+
"heading2",
|
|
53655
|
+
"heading3",
|
|
53656
|
+
"heading4",
|
|
53657
|
+
"heading5",
|
|
53658
|
+
"heading6",
|
|
53659
|
+
"paragraph",
|
|
53660
|
+
"highlight",
|
|
53661
|
+
"codeBlock",
|
|
53662
|
+
"quote",
|
|
53663
|
+
"taskList",
|
|
53664
|
+
"toggleTask"
|
|
53665
|
+
];
|
|
53666
|
+
var alignments = {
|
|
53667
|
+
alignLeft: null,
|
|
53668
|
+
alignCenter: "center",
|
|
53669
|
+
alignRight: "right",
|
|
53670
|
+
alignJustify: "justify"
|
|
53671
|
+
};
|
|
53672
|
+
function isAlignCommand(command) {
|
|
53673
|
+
return command in alignments;
|
|
53674
|
+
}
|
|
53675
|
+
/** Calls `found` for each paragraph the selection touches that can take an alignment, which
|
|
53676
|
+
* those in table cells cannot, until it returns false. */
|
|
53677
|
+
function eachAlignable(state, found) {
|
|
53678
|
+
const { from, to } = state.selection;
|
|
53679
|
+
let going = true;
|
|
53680
|
+
state.doc.nodesBetween(from, to, (node, pos) => {
|
|
53681
|
+
if (!going || node.type.name === "table") return false;
|
|
53682
|
+
if (node.type.name === "paragraph") going = found(node, pos);
|
|
53683
|
+
return going && !node.isTextblock;
|
|
53684
|
+
});
|
|
53685
|
+
}
|
|
53686
|
+
function canAlign(state) {
|
|
53687
|
+
let any = false;
|
|
53688
|
+
eachAlignable(state, () => {
|
|
53689
|
+
any = true;
|
|
53690
|
+
return false;
|
|
53691
|
+
});
|
|
53692
|
+
return any;
|
|
53693
|
+
}
|
|
53694
|
+
/** The alignment of the paragraph that holds the start of the selection. */
|
|
53695
|
+
function caretAlignment(state) {
|
|
53696
|
+
const { parent } = state.selection.$from;
|
|
53697
|
+
return parent.type.name === "paragraph" && parent.attrs.align ? parent.attrs.align : "left";
|
|
53698
|
+
}
|
|
53699
|
+
function formatAvailability(editor, state, formatted, handler) {
|
|
53700
|
+
const available = markdownAvailability(editor, state, formatted);
|
|
53701
|
+
const rich = handler.richText;
|
|
53702
|
+
if (!rich) return available;
|
|
53703
|
+
for (const command of unavailableInRichText) available[command] = false;
|
|
53704
|
+
if (available.underline && rich.inLink(state)) available.underline = false;
|
|
53705
|
+
const align = formatted && canAlign(state);
|
|
53706
|
+
available.alignLeft = available.alignCenter = align;
|
|
53707
|
+
available.alignRight = available.alignJustify = align;
|
|
53708
|
+
return available;
|
|
53709
|
+
}
|
|
53710
|
+
function markdownAvailability(editor, state, formatted) {
|
|
52416
53711
|
const manager = editor.ctx.get(commandsCtx);
|
|
52417
53712
|
const check = (key, payload) => formatted && manager.get(key)(payload)(state);
|
|
52418
53713
|
const canBecome = (type, level) => formatted && textblockType(editor, type, level)(state);
|
|
@@ -52439,9 +53734,27 @@ function formatAvailability(editor, state, formatted) {
|
|
|
52439
53734
|
orderedList: formatted && (innermostListDepth(textBounds(state).$from) != null || check(wrapInOrderedListCommand.key)),
|
|
52440
53735
|
taskList: formatted && (innermostListDepth(textBounds(state).$from) != null || check(wrapInBulletListCommand.key)),
|
|
52441
53736
|
toggleTask: formatted && taskAtCaret(state) != null,
|
|
53737
|
+
alignLeft: false,
|
|
53738
|
+
alignCenter: false,
|
|
53739
|
+
alignRight: false,
|
|
53740
|
+
alignJustify: false,
|
|
52442
53741
|
link: check(toggleLinkCommand.key, {})
|
|
52443
53742
|
};
|
|
52444
53743
|
}
|
|
53744
|
+
/** Sets the alignment of the paragraphs the selection touches. */
|
|
53745
|
+
function align(editor, command) {
|
|
53746
|
+
const view = editor.ctx.get(editorViewCtx);
|
|
53747
|
+
const value = alignments[command];
|
|
53748
|
+
const tr = view.state.tr;
|
|
53749
|
+
eachAlignable(view.state, (node, pos) => {
|
|
53750
|
+
if (node.attrs.align !== value) tr.setNodeMarkup(pos, void 0, {
|
|
53751
|
+
...node.attrs,
|
|
53752
|
+
align: value
|
|
53753
|
+
});
|
|
53754
|
+
return true;
|
|
53755
|
+
});
|
|
53756
|
+
if (tr.docChanged) view.dispatch(tr);
|
|
53757
|
+
}
|
|
52445
53758
|
function canQuote(editor, state) {
|
|
52446
53759
|
const { $from, $to } = textBounds(state);
|
|
52447
53760
|
if (quoted($from, $to)) {
|
|
@@ -52496,12 +53809,32 @@ function toggleInlineCode(editor) {
|
|
|
52496
53809
|
if (!type.isInSet(selection.$from.marks())) view.dispatch(view.state.tr.addStoredMark(type.create()));
|
|
52497
53810
|
else withMarkRunSelected(editor, type, run);
|
|
52498
53811
|
}
|
|
52499
|
-
function list(editor, kind) {
|
|
53812
|
+
function list(editor, kind, itemPerBlock) {
|
|
52500
53813
|
const view = editor.ctx.get(editorViewCtx);
|
|
52501
53814
|
const { state } = view;
|
|
52502
53815
|
const { $from } = textBounds(state);
|
|
52503
53816
|
const wanted = kind === "bulletList" ? "bullet_list" : "ordered_list";
|
|
52504
53817
|
const depth = innermostListDepth($from);
|
|
53818
|
+
if (depth == null && itemPerBlock) {
|
|
53819
|
+
const ordered = wanted === "ordered_list";
|
|
53820
|
+
wrapInList(state.schema.nodes[wanted], ordered ? {
|
|
53821
|
+
order: 1,
|
|
53822
|
+
spread: false
|
|
53823
|
+
} : { spread: false })(state, (tr) => {
|
|
53824
|
+
tr.doc.nodesBetween(tr.mapping.map(state.selection.from), tr.mapping.map(state.selection.to), (node, pos) => {
|
|
53825
|
+
if (node.type.name !== "list_item") return true;
|
|
53826
|
+
tr.setNodeMarkup(pos, void 0, {
|
|
53827
|
+
...node.attrs,
|
|
53828
|
+
listType: ordered ? "ordered" : "bullet",
|
|
53829
|
+
label: ordered ? "1." : "•",
|
|
53830
|
+
spread: false
|
|
53831
|
+
});
|
|
53832
|
+
return false;
|
|
53833
|
+
});
|
|
53834
|
+
view.dispatch(tr);
|
|
53835
|
+
});
|
|
53836
|
+
return;
|
|
53837
|
+
}
|
|
52505
53838
|
if (depth == null) {
|
|
52506
53839
|
const command = kind === "bulletList" ? wrapInBulletListCommand : wrapInOrderedListCommand;
|
|
52507
53840
|
editor.action(callCommand(command.key));
|
|
@@ -52560,11 +53893,15 @@ function textblockType(editor, type, attr) {
|
|
|
52560
53893
|
if (type === "heading") return setTextblockType(ctx, headingSchema.type(ctx), { level: Number(attr) });
|
|
52561
53894
|
return setTextblockType(ctx, paragraphSchema.type(ctx));
|
|
52562
53895
|
}
|
|
52563
|
-
function applyFormat(editor, command, checked, link) {
|
|
53896
|
+
function applyFormat(editor, command, checked, link, itemPerBlock = false) {
|
|
52564
53897
|
const view = editor.ctx.get(editorViewCtx);
|
|
52565
53898
|
const run = (cmd, payload) => editor.action(callCommand(cmd, payload));
|
|
52566
53899
|
const state = caretState(view.state);
|
|
52567
53900
|
const become = (type, attr) => textblockType(editor, type, attr)(view.state, view.dispatch);
|
|
53901
|
+
if (isAlignCommand(command)) {
|
|
53902
|
+
align(editor, command);
|
|
53903
|
+
return;
|
|
53904
|
+
}
|
|
52568
53905
|
const level = headingLevel(command);
|
|
52569
53906
|
if (level != null) {
|
|
52570
53907
|
if (state.block.type === "heading" && state.block.level === level) become("paragraph");
|
|
@@ -52608,7 +53945,7 @@ function applyFormat(editor, command, checked, link) {
|
|
|
52608
53945
|
break;
|
|
52609
53946
|
case "bulletList":
|
|
52610
53947
|
case "orderedList":
|
|
52611
|
-
list(editor, command);
|
|
53948
|
+
list(editor, command, itemPerBlock);
|
|
52612
53949
|
break;
|
|
52613
53950
|
case "taskList":
|
|
52614
53951
|
toggleTaskList(editor.ctx);
|
|
@@ -55583,6 +56920,7 @@ async function createMilkdownEditor(root, options, runtime) {
|
|
|
55583
56920
|
ctx.set(rootCtx, root);
|
|
55584
56921
|
ctx.set(authoredSourcesCtx.key, (doc, positions) => runtime.authoredSources(doc, positions));
|
|
55585
56922
|
ctx.set(smartPunctuationCtx.key, () => runtime.smartPunctuation());
|
|
56923
|
+
ctx.set(formatInputCtx.key, runtime.format);
|
|
55586
56924
|
ctx.set(linkSchemesCtx.key, runtime.linkSchemes);
|
|
55587
56925
|
ctx.update(labelsCtx.key, () => ({
|
|
55588
56926
|
...defaultLabels,
|
|
@@ -55632,7 +56970,7 @@ async function createMilkdownEditor(root, options, runtime) {
|
|
|
55632
56970
|
return true;
|
|
55633
56971
|
}
|
|
55634
56972
|
}));
|
|
55635
|
-
}).use(labelsCtx).use(linkSchemesCtx).use(runtime.plugins.paste).use(visibleClipboard).use(runtime.plugins.suggestions).use(runtime.plugins.keymap).use(createDialect(Boolean(options.images || options.files), Boolean(options.wikiLinks), Boolean(options.files))).use(codeControlCtx).use(codeControlsPlugin(() => runtime.visibleRect())).use(tablePlugins).use(tableBlock).use(runtime.plugins.change).use(runtime.plugins.historyInput).use(history).use(clipboard).use(cursor).use(taskListPlugin((operation) => runtime.mutate(operation))).use(undoTypedRule).use(quoteBackspace).use(calloutEnter).use(literalEnter).use(literalReread).use(listItemBackspace).use(nestedItemEnter).use(rejoinLists).use(movedListItems).use(textInputPolicy).use(runtime.copy ? codeCopyPlugin((text) => runtime.copy?.(text)) : []).use(placeholderPlugin).use(options.rendering?.codeHighlighting === false ? [] : (await import("./highlight-
|
|
56973
|
+
}).use(labelsCtx).use(linkSchemesCtx).use(formatInputCtx).use(richTextGuard).use(runtime.plugins.paste).use(visibleClipboard).use(runtime.plugins.suggestions).use(runtime.plugins.keymap).use(createDialect(Boolean(options.images || options.files), Boolean(options.wikiLinks), Boolean(options.files))).use(codeControlCtx).use(codeControlsPlugin(() => runtime.visibleRect())).use(tablePlugins).use(tableBlock).use(runtime.plugins.change).use(runtime.plugins.historyInput).use(history).use(clipboard).use(cursor).use(taskListPlugin((operation) => runtime.mutate(operation))).use(undoTypedRule).use(quoteBackspace).use(calloutEnter).use(literalEnter).use(literalReread).use(listItemBackspace).use(nestedItemEnter).use(rejoinLists).use(movedListItems).use(textInputPolicy).use(runtime.copy ? codeCopyPlugin((text) => runtime.copy?.(text)) : []).use(placeholderPlugin).use(options.rendering?.codeHighlighting === false ? [] : (await import("./highlight-CqtvWKCp.js")).highlightPlugin).use(options.rendering?.diagramPreview === false || !diagramsAvailable() ? [] : mermaidPreview(runtime.diagrams, (error) => runtime.report(error))).use(selectionPlugin).use($prose(() => search())).use(runtime.plugins.policy).use(options.wikiLinks ? wikiLinkView(options.wikiLinks, {
|
|
55636
56974
|
capture: () => runtime.capture(),
|
|
55637
56975
|
active: () => runtime.active(),
|
|
55638
56976
|
labels: {
|
|
@@ -55648,8 +56986,46 @@ async function createMilkdownEditor(root, options, runtime) {
|
|
|
55648
56986
|
...options.labels
|
|
55649
56987
|
},
|
|
55650
56988
|
error: (error) => runtime.report(error)
|
|
55651
|
-
}, options.files, runtime.canMutate) : []).create()
|
|
56989
|
+
}, options.files, runtime.canMutate) : []).create().then((editor) => {
|
|
56990
|
+
markdownInputRules(editor.ctx);
|
|
56991
|
+
return editor;
|
|
56992
|
+
});
|
|
56993
|
+
}
|
|
56994
|
+
/** Markdown input rules, such as `# ` for a heading, do nothing in a format that is not Markdown.
|
|
56995
|
+
* Rules that are not Markdown syntax, such as smart punctuation, keep applying. */
|
|
56996
|
+
function markdownInputRules(ctx) {
|
|
56997
|
+
for (const rule of ctx.get(inputRulesCtx)) {
|
|
56998
|
+
if (formatNeutralRules.has(rule)) continue;
|
|
56999
|
+
const held = rule;
|
|
57000
|
+
const handler = held.handler;
|
|
57001
|
+
held.handler = (state, match, start, end) => formatInput(ctx).markdownInput() ? handler(state, match, start, end) : null;
|
|
57002
|
+
}
|
|
55652
57003
|
}
|
|
57004
|
+
/**
|
|
57005
|
+
* In a format that holds less than Markdown, such as RTF, refuses any transaction that would bring
|
|
57006
|
+
* in a node, mark or structure the format cannot hold, so a disallowed edit changes nothing. A
|
|
57007
|
+
* link's underline, which RTF does not keep, is removed in the same step.
|
|
57008
|
+
*/
|
|
57009
|
+
var richTextGuard = $prose((ctx) => new Plugin({
|
|
57010
|
+
key: new PluginKey("inkkitRichTextGuard"),
|
|
57011
|
+
filterTransaction(tr, state) {
|
|
57012
|
+
const rich = formatInput(ctx).richText();
|
|
57013
|
+
return !rich || rich.allows(tr, state);
|
|
57014
|
+
},
|
|
57015
|
+
appendTransaction(transactions, oldState, newState) {
|
|
57016
|
+
const rich = formatInput(ctx).richText();
|
|
57017
|
+
if (!rich || !transactions.some((tr) => tr.docChanged)) return null;
|
|
57018
|
+
const start = oldState.doc.content.findDiffStart(newState.doc.content);
|
|
57019
|
+
if (start == null) return null;
|
|
57020
|
+
const end = oldState.doc.content.findDiffEnd(newState.doc.content).b;
|
|
57021
|
+
const ranges = rich.linkedUnderlines(newState.doc, start, Math.max(start, end));
|
|
57022
|
+
if (!ranges.length) return null;
|
|
57023
|
+
const tr = newState.tr;
|
|
57024
|
+
for (const { from, to } of ranges) tr.removeMark(from, to, newState.schema.marks.underline);
|
|
57025
|
+
if (newState.storedMarks) tr.setStoredMarks(newState.storedMarks);
|
|
57026
|
+
return tr;
|
|
57027
|
+
}
|
|
57028
|
+
}));
|
|
55653
57029
|
function createDocumentState(ctx, doc) {
|
|
55654
57030
|
return EditorState.create(ctx.get(editorStateOptionsCtx)({
|
|
55655
57031
|
schema: ctx.get(schemaCtx),
|
|
@@ -55662,6 +57038,402 @@ function initialDocumentSelection(state) {
|
|
|
55662
57038
|
return setSearchState(state.tr.setSelection(end), new SearchQuery({ search: "" }));
|
|
55663
57039
|
}
|
|
55664
57040
|
//#endregion
|
|
57041
|
+
//#region src/formats/convert.ts
|
|
57042
|
+
var Losses = class {
|
|
57043
|
+
counts = /* @__PURE__ */ new Map();
|
|
57044
|
+
add(code) {
|
|
57045
|
+
this.counts.set(code, (this.counts.get(code) ?? 0) + 1);
|
|
57046
|
+
}
|
|
57047
|
+
list() {
|
|
57048
|
+
return Object.freeze(formatLossCodes.filter((code) => this.counts.has(code)).map((code) => Object.freeze({
|
|
57049
|
+
code,
|
|
57050
|
+
count: this.counts.get(code)
|
|
57051
|
+
})));
|
|
57052
|
+
}
|
|
57053
|
+
};
|
|
57054
|
+
var richMarks = {
|
|
57055
|
+
strong: "bold",
|
|
57056
|
+
emphasis: "italic",
|
|
57057
|
+
underline: "underline",
|
|
57058
|
+
strike_through: "strikethrough",
|
|
57059
|
+
superscript: "superscript",
|
|
57060
|
+
subscript: "subscript",
|
|
57061
|
+
inlineCode: "code",
|
|
57062
|
+
link: "link"
|
|
57063
|
+
};
|
|
57064
|
+
var isList = (node) => node.type.name === "bullet_list" || node.type.name === "ordered_list";
|
|
57065
|
+
/** Reads a formatted Markdown document as rich text, counting what RTF cannot hold. */
|
|
57066
|
+
var MarkdownReader = class {
|
|
57067
|
+
lost = new Losses();
|
|
57068
|
+
/** Appends text, counting characters RTF can't hold, which become U+FFFD. */
|
|
57069
|
+
append(builder, text, marks) {
|
|
57070
|
+
if (/[\u0000\ufffc]|[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/.test(text)) this.lost.add("invalid-character");
|
|
57071
|
+
appendText(builder, text, marks);
|
|
57072
|
+
}
|
|
57073
|
+
marks(marks, extra) {
|
|
57074
|
+
const result = [...extra];
|
|
57075
|
+
for (const mark of marks) {
|
|
57076
|
+
const type = richMarks[mark.type.name];
|
|
57077
|
+
if (!type) {
|
|
57078
|
+
if (mark.type.name === "highlight") this.lost.add("highlight");
|
|
57079
|
+
continue;
|
|
57080
|
+
}
|
|
57081
|
+
if (result.some((held) => held.type === type)) continue;
|
|
57082
|
+
if (type === "link") {
|
|
57083
|
+
const href = String(mark.attrs.href ?? "");
|
|
57084
|
+
if (linkProblem(href)) {
|
|
57085
|
+
this.lost.add("link");
|
|
57086
|
+
continue;
|
|
57087
|
+
}
|
|
57088
|
+
result.push({
|
|
57089
|
+
type: "link",
|
|
57090
|
+
href
|
|
57091
|
+
});
|
|
57092
|
+
} else result.push({ type });
|
|
57093
|
+
}
|
|
57094
|
+
if (result.some((mark) => mark.type === "link") && result.some((mark) => mark.type === "underline")) this.lost.add("link-underline");
|
|
57095
|
+
return canonicalMarks(result);
|
|
57096
|
+
}
|
|
57097
|
+
/** Appends a textblock's inline content. */
|
|
57098
|
+
inline(node, builder, extra = []) {
|
|
57099
|
+
node.forEach((child) => {
|
|
57100
|
+
const name = child.type.name;
|
|
57101
|
+
if (child.isText) this.append(builder, child.text, this.marks(child.marks, extra));
|
|
57102
|
+
else if (name === "hardbreak") builder.break();
|
|
57103
|
+
else if (name === "footnote_reference") {
|
|
57104
|
+
this.lost.add("footnote");
|
|
57105
|
+
this.append(builder, `[${String(child.attrs.label)}]`, this.marks(child.marks, extra));
|
|
57106
|
+
} else if (name === "inkkit_wiki_link") {
|
|
57107
|
+
this.lost.add("wiki-link");
|
|
57108
|
+
this.append(builder, String(child.attrs.label || child.attrs.target || child.attrs.raw || ""), this.marks(child.marks, extra));
|
|
57109
|
+
} else if (name === "image") this.lost.add(child.attrs.inkkitFileTarget || child.attrs.inkkitFileRaw ? "file" : "image");
|
|
57110
|
+
else if (name === "comment_inline") this.lost.add("comment");
|
|
57111
|
+
else if (name === "html") {
|
|
57112
|
+
this.lost.add("literal");
|
|
57113
|
+
this.append(builder, String(child.attrs.value ?? ""), this.marks(child.marks, extra));
|
|
57114
|
+
} else if (child.isInline) this.inline(child, builder, extra);
|
|
57115
|
+
});
|
|
57116
|
+
}
|
|
57117
|
+
paragraph(node, extra = []) {
|
|
57118
|
+
const builder = new InlineBuilder();
|
|
57119
|
+
this.inline(node, builder, extra);
|
|
57120
|
+
return {
|
|
57121
|
+
type: "paragraph",
|
|
57122
|
+
content: builder.finish()
|
|
57123
|
+
};
|
|
57124
|
+
}
|
|
57125
|
+
lines(text, marks) {
|
|
57126
|
+
return text.split(/\r\n|[\n\r]/).map((line) => {
|
|
57127
|
+
const builder = new InlineBuilder();
|
|
57128
|
+
this.append(builder, line, marks);
|
|
57129
|
+
return {
|
|
57130
|
+
type: "paragraph",
|
|
57131
|
+
content: builder.finish()
|
|
57132
|
+
};
|
|
57133
|
+
});
|
|
57134
|
+
}
|
|
57135
|
+
/** The blocks for `node`, `depth` lists deep. */
|
|
57136
|
+
blocks(node, depth) {
|
|
57137
|
+
switch (node.type.name) {
|
|
57138
|
+
case "paragraph": return [this.paragraph(node)];
|
|
57139
|
+
case "heading":
|
|
57140
|
+
this.lost.add("heading");
|
|
57141
|
+
return [this.paragraph(node, [{ type: "bold" }])];
|
|
57142
|
+
case "code_block":
|
|
57143
|
+
this.lost.add(String(node.attrs.language).toLowerCase() === "mermaid" ? "diagram" : "code-block");
|
|
57144
|
+
return this.lines(node.textContent, [{ type: "code" }]);
|
|
57145
|
+
case "literal_markdown":
|
|
57146
|
+
this.lost.add("literal");
|
|
57147
|
+
return this.lines(node.textContent, []);
|
|
57148
|
+
case "blockquote":
|
|
57149
|
+
case "inkkit_callout": {
|
|
57150
|
+
this.lost.add(node.type.name === "blockquote" ? "quote" : "callout");
|
|
57151
|
+
const blocks = [];
|
|
57152
|
+
const title = node.attrs.title;
|
|
57153
|
+
if (title) blocks.push(...this.lines(title, [{ type: "bold" }]));
|
|
57154
|
+
node.forEach((child) => blocks.push(...this.blocks(child, depth)));
|
|
57155
|
+
return blocks;
|
|
57156
|
+
}
|
|
57157
|
+
case "footnote_definition": {
|
|
57158
|
+
this.lost.add("footnote");
|
|
57159
|
+
const blocks = [];
|
|
57160
|
+
node.forEach((child) => blocks.push(...this.blocks(child, depth)));
|
|
57161
|
+
const label = `[${String(node.attrs.label)}] `;
|
|
57162
|
+
const first = blocks[0];
|
|
57163
|
+
const builder = new InlineBuilder();
|
|
57164
|
+
builder.text(first?.type === "paragraph" ? label : label.trim(), []);
|
|
57165
|
+
if (first?.type === "paragraph") {
|
|
57166
|
+
for (const inline of first.content) if (inline.type === "break") builder.break();
|
|
57167
|
+
else builder.text(inline.text, inline.marks ?? []);
|
|
57168
|
+
blocks[0] = {
|
|
57169
|
+
...first,
|
|
57170
|
+
content: builder.finish()
|
|
57171
|
+
};
|
|
57172
|
+
} else blocks.unshift({
|
|
57173
|
+
type: "paragraph",
|
|
57174
|
+
content: builder.finish()
|
|
57175
|
+
});
|
|
57176
|
+
return blocks;
|
|
57177
|
+
}
|
|
57178
|
+
case "bullet_list":
|
|
57179
|
+
case "ordered_list": return [this.list(node, depth + 1)];
|
|
57180
|
+
case "table": return depth > 0 ? this.tableLines(node) : [this.table(node)];
|
|
57181
|
+
case "hr":
|
|
57182
|
+
this.lost.add("rule");
|
|
57183
|
+
return [];
|
|
57184
|
+
case "comment_block":
|
|
57185
|
+
this.lost.add("comment");
|
|
57186
|
+
return [];
|
|
57187
|
+
case "image_block":
|
|
57188
|
+
this.lost.add(node.attrs.inkkitFileTarget || node.attrs.inkkitFileRaw ? "file" : "image");
|
|
57189
|
+
return [];
|
|
57190
|
+
case "reference_definition": return [];
|
|
57191
|
+
default: {
|
|
57192
|
+
if (node.isTextblock) return [this.paragraph(node)];
|
|
57193
|
+
const blocks = [];
|
|
57194
|
+
node.forEach((child) => blocks.push(...this.blocks(child, depth)));
|
|
57195
|
+
return blocks;
|
|
57196
|
+
}
|
|
57197
|
+
}
|
|
57198
|
+
}
|
|
57199
|
+
table(node) {
|
|
57200
|
+
let aligned = false;
|
|
57201
|
+
const rows = [];
|
|
57202
|
+
node.forEach((row) => {
|
|
57203
|
+
const cells = [];
|
|
57204
|
+
row.forEach((cell) => {
|
|
57205
|
+
if (cell.attrs.alignment != null) aligned = true;
|
|
57206
|
+
const builder = new InlineBuilder();
|
|
57207
|
+
cell.forEach((child, _offset, index) => {
|
|
57208
|
+
if (index) builder.break();
|
|
57209
|
+
this.inline(child, builder);
|
|
57210
|
+
});
|
|
57211
|
+
cells.push({ content: builder.finish() });
|
|
57212
|
+
});
|
|
57213
|
+
rows.push(cells);
|
|
57214
|
+
});
|
|
57215
|
+
if (aligned) this.lost.add("table-alignment");
|
|
57216
|
+
return {
|
|
57217
|
+
type: "table",
|
|
57218
|
+
rows
|
|
57219
|
+
};
|
|
57220
|
+
}
|
|
57221
|
+
/** A table inside a list item, as lines of tab-separated cells. */
|
|
57222
|
+
tableLines(node) {
|
|
57223
|
+
return this.table(node).rows.map((row) => {
|
|
57224
|
+
const builder = new InlineBuilder();
|
|
57225
|
+
row.forEach((cell, index) => {
|
|
57226
|
+
if (index) builder.text(" ", []);
|
|
57227
|
+
for (const inline of cell.content) if (inline.type === "break") builder.break();
|
|
57228
|
+
else builder.text(inline.text, inline.marks ?? []);
|
|
57229
|
+
});
|
|
57230
|
+
return {
|
|
57231
|
+
type: "paragraph",
|
|
57232
|
+
content: builder.finish()
|
|
57233
|
+
};
|
|
57234
|
+
});
|
|
57235
|
+
}
|
|
57236
|
+
list(node, depth) {
|
|
57237
|
+
const ordered = node.type.name === "ordered_list";
|
|
57238
|
+
let start = Number(node.attrs.order ?? 1);
|
|
57239
|
+
if (ordered && !(Number.isSafeInteger(start) && start >= 1)) {
|
|
57240
|
+
this.lost.add("list-start");
|
|
57241
|
+
start = 1;
|
|
57242
|
+
} else if (ordered && start > 2147483647) {
|
|
57243
|
+
this.lost.add("list-start");
|
|
57244
|
+
start = maximumListStart;
|
|
57245
|
+
}
|
|
57246
|
+
const items = [];
|
|
57247
|
+
node.forEach((item) => {
|
|
57248
|
+
if (item.attrs.checked != null) this.lost.add("task");
|
|
57249
|
+
const builder = new InlineBuilder();
|
|
57250
|
+
let lines = 0;
|
|
57251
|
+
const nested = [];
|
|
57252
|
+
let flattened = false;
|
|
57253
|
+
const line = (content) => {
|
|
57254
|
+
if (lines++) builder.break();
|
|
57255
|
+
for (const inline of content) if (inline.type === "break") builder.break();
|
|
57256
|
+
else builder.text(inline.text, inline.marks ?? []);
|
|
57257
|
+
};
|
|
57258
|
+
const flatten = (block) => {
|
|
57259
|
+
if (block.type === "paragraph") line(block.content);
|
|
57260
|
+
else if (block.type === "list") for (const listItem of block.items) listItem.blocks.forEach(flatten);
|
|
57261
|
+
else for (const row of block.rows) for (const cell of row) line(cell.content);
|
|
57262
|
+
};
|
|
57263
|
+
item.forEach((child, _offset, index) => {
|
|
57264
|
+
if (isList(child)) {
|
|
57265
|
+
if (depth < 9) nested.push(...this.blocks(child, depth));
|
|
57266
|
+
else {
|
|
57267
|
+
this.lost.add("list-depth");
|
|
57268
|
+
this.blocks(child, depth).forEach(flatten);
|
|
57269
|
+
}
|
|
57270
|
+
return;
|
|
57271
|
+
}
|
|
57272
|
+
for (const block of this.blocks(child, depth)) {
|
|
57273
|
+
if (index === 0 && !lines && block.type === "paragraph") {
|
|
57274
|
+
line(block.content);
|
|
57275
|
+
continue;
|
|
57276
|
+
}
|
|
57277
|
+
flattened = true;
|
|
57278
|
+
flatten(block);
|
|
57279
|
+
}
|
|
57280
|
+
});
|
|
57281
|
+
if (flattened) this.lost.add("list-item-blocks");
|
|
57282
|
+
items.push({ blocks: [{
|
|
57283
|
+
type: "paragraph",
|
|
57284
|
+
content: builder.finish()
|
|
57285
|
+
}, ...nested] });
|
|
57286
|
+
});
|
|
57287
|
+
return ordered && start !== 1 ? {
|
|
57288
|
+
type: "list",
|
|
57289
|
+
ordered,
|
|
57290
|
+
start,
|
|
57291
|
+
items
|
|
57292
|
+
} : {
|
|
57293
|
+
type: "list",
|
|
57294
|
+
ordered,
|
|
57295
|
+
items
|
|
57296
|
+
};
|
|
57297
|
+
}
|
|
57298
|
+
};
|
|
57299
|
+
/** A formatted Markdown document as rich text, with what RTF could not hold. */
|
|
57300
|
+
function markdownToRichText(doc) {
|
|
57301
|
+
const reader = new MarkdownReader();
|
|
57302
|
+
const blocks = [];
|
|
57303
|
+
doc.forEach((node) => blocks.push(...reader.blocks(node, 0)));
|
|
57304
|
+
if (blocks.length === 1 && blocks[0].type === "paragraph" && !blocks[0].content.length) blocks.length = 0;
|
|
57305
|
+
return {
|
|
57306
|
+
document: {
|
|
57307
|
+
version: 1,
|
|
57308
|
+
blocks
|
|
57309
|
+
},
|
|
57310
|
+
lost: reader.lost.list()
|
|
57311
|
+
};
|
|
57312
|
+
}
|
|
57313
|
+
/** A rich text document as Markdown, with what Markdown could not hold: alignment. Underline,
|
|
57314
|
+
* superscript and subscript are written as `<u>`, `<sup>` and `<sub>`. */
|
|
57315
|
+
function richTextToMarkdown(ctx, document) {
|
|
57316
|
+
const lost = new Losses();
|
|
57317
|
+
const { schema } = ctx.get(editorViewCtx).state;
|
|
57318
|
+
const ready = markdownReady(document, lost);
|
|
57319
|
+
const text = serialize(ctx, htmlBreaksInCells(richTextToProse(schema, ready)));
|
|
57320
|
+
let reread;
|
|
57321
|
+
try {
|
|
57322
|
+
reread = markdownToRichText(ctx.get(parserCtx)(text)).document;
|
|
57323
|
+
} catch {
|
|
57324
|
+
reread = void 0;
|
|
57325
|
+
}
|
|
57326
|
+
if (!reread || !sameDocument(reread, ready)) throw new InkKitError("preservation", "The document could not be written as Markdown that reads back the same");
|
|
57327
|
+
return {
|
|
57328
|
+
text,
|
|
57329
|
+
lost: lost.list()
|
|
57330
|
+
};
|
|
57331
|
+
}
|
|
57332
|
+
/** Marks that show on a space: moving a space out of one changes what the reader sees. */
|
|
57333
|
+
var visibleOnSpace = /* @__PURE__ */ new Set([
|
|
57334
|
+
"underline",
|
|
57335
|
+
"strikethrough",
|
|
57336
|
+
"link"
|
|
57337
|
+
]);
|
|
57338
|
+
/**
|
|
57339
|
+
* `content` as Markdown can write it. An emphasis, strikethrough, link or HTML mark can't start or
|
|
57340
|
+
* end with a space, so leading and trailing spaces move outside the run, and a run of only spaces
|
|
57341
|
+
* loses its marks. A code span keeps its spaces. Counts `mark-spaces` where a visible mark moved.
|
|
57342
|
+
*/
|
|
57343
|
+
function markdownInlines(content, lost) {
|
|
57344
|
+
const builder = new InlineBuilder();
|
|
57345
|
+
for (const inline of content) {
|
|
57346
|
+
if (inline.type === "break") {
|
|
57347
|
+
builder.break();
|
|
57348
|
+
continue;
|
|
57349
|
+
}
|
|
57350
|
+
let marks = inline.marks ?? [];
|
|
57351
|
+
if (/^ +$/.test(inline.text) && marks.some((mark) => mark.type === "code")) {
|
|
57352
|
+
const kept = marks.filter((mark) => mark.type === "code");
|
|
57353
|
+
if (marks.some((mark) => mark.type !== "code" && visibleOnSpace.has(mark.type))) lost.add("mark-spaces");
|
|
57354
|
+
marks = kept;
|
|
57355
|
+
}
|
|
57356
|
+
const [, lead, core, trail] = /^( *)([^]*?)( *)$/.exec(inline.text);
|
|
57357
|
+
if (!marks.length || marks.some((mark) => mark.type === "code") || !lead && !trail) {
|
|
57358
|
+
builder.text(inline.text, marks);
|
|
57359
|
+
continue;
|
|
57360
|
+
}
|
|
57361
|
+
if (marks.some((mark) => visibleOnSpace.has(mark.type))) lost.add("mark-spaces");
|
|
57362
|
+
builder.text(lead, []);
|
|
57363
|
+
builder.text(core, marks);
|
|
57364
|
+
builder.text(trail, []);
|
|
57365
|
+
}
|
|
57366
|
+
return builder.finish();
|
|
57367
|
+
}
|
|
57368
|
+
/**
|
|
57369
|
+
* `document` as Markdown can hold it, with what that drops counted in `lost`: alignment, a mark's
|
|
57370
|
+
* edge spaces, a paragraph's only line break, which Markdown reads back as an empty paragraph, and
|
|
57371
|
+
* empty paragraphs at the end, which Markdown doesn't keep.
|
|
57372
|
+
*/
|
|
57373
|
+
function markdownReady(document, lost) {
|
|
57374
|
+
const block = (item) => {
|
|
57375
|
+
if (item.type === "paragraph") {
|
|
57376
|
+
if (item.align) lost.add("alignment");
|
|
57377
|
+
let content = markdownInlines(item.content, lost);
|
|
57378
|
+
if (content.length === 1 && content[0].type === "break") {
|
|
57379
|
+
lost.add("empty-line");
|
|
57380
|
+
content = [];
|
|
57381
|
+
}
|
|
57382
|
+
return {
|
|
57383
|
+
type: "paragraph",
|
|
57384
|
+
content
|
|
57385
|
+
};
|
|
57386
|
+
}
|
|
57387
|
+
if (item.type === "list") return {
|
|
57388
|
+
...item,
|
|
57389
|
+
items: item.items.map((listItem) => ({ blocks: listItem.blocks.map(block) }))
|
|
57390
|
+
};
|
|
57391
|
+
return {
|
|
57392
|
+
type: "table",
|
|
57393
|
+
rows: item.rows.map((row) => row.map((cell) => ({ content: markdownInlines(cell.content, lost) })))
|
|
57394
|
+
};
|
|
57395
|
+
};
|
|
57396
|
+
const blocks = document.blocks.map(block);
|
|
57397
|
+
while (blocks.length > 1 && blocks.at(-1).type === "paragraph" && !blocks.at(-1).content.length) {
|
|
57398
|
+
blocks.pop();
|
|
57399
|
+
lost.add("empty-line");
|
|
57400
|
+
}
|
|
57401
|
+
return {
|
|
57402
|
+
version: 1,
|
|
57403
|
+
blocks
|
|
57404
|
+
};
|
|
57405
|
+
}
|
|
57406
|
+
/** Whether two documents are the same, counting one empty paragraph as an empty document. */
|
|
57407
|
+
function sameDocument(a, b) {
|
|
57408
|
+
const text = (document) => JSON.stringify(document.blocks.length === 1 && document.blocks[0].type === "paragraph" && !document.blocks[0].content.length ? [] : document.blocks);
|
|
57409
|
+
return text(a) === text(b);
|
|
57410
|
+
}
|
|
57411
|
+
/** Markdown for content copied from a rich text document. Unlike a conversion it reports no
|
|
57412
|
+
* losses and isn't read back: a copy may lose alignment. */
|
|
57413
|
+
function richContentMarkdown(ctx, content) {
|
|
57414
|
+
const { schema } = ctx.get(editorViewCtx).state;
|
|
57415
|
+
const empty = schema.topNodeType.createAndFill();
|
|
57416
|
+
let doc = new Transform(empty).replace(0, empty.content.size, content).doc;
|
|
57417
|
+
try {
|
|
57418
|
+
doc = richTextToProse(schema, markdownReady(proseToRichText(doc, false), new Losses()));
|
|
57419
|
+
} catch {}
|
|
57420
|
+
return serialize(ctx, htmlBreaksInCells(doc));
|
|
57421
|
+
}
|
|
57422
|
+
/** `node` with each line break in a table cell written as `<br />`, since a cell is one line of
|
|
57423
|
+
* Markdown. */
|
|
57424
|
+
function htmlBreaksInCells(node, inCell = false) {
|
|
57425
|
+
if (node.type.name === "hardbreak") return inCell ? node.type.create({
|
|
57426
|
+
...node.attrs,
|
|
57427
|
+
isHTML: true
|
|
57428
|
+
}, null, node.marks) : node;
|
|
57429
|
+
if (node.isLeaf) return node;
|
|
57430
|
+
const role = node.type.spec.tableRole;
|
|
57431
|
+
const cell = inCell || role === "cell" || role === "header_cell";
|
|
57432
|
+
const children = [];
|
|
57433
|
+
node.forEach((child) => children.push(htmlBreaksInCells(child, cell)));
|
|
57434
|
+
return node.copy(Fragment.fromArray(children));
|
|
57435
|
+
}
|
|
57436
|
+
//#endregion
|
|
55665
57437
|
//#region src/paste-html.ts
|
|
55666
57438
|
var rich = /* @__PURE__ */ new Set([
|
|
55667
57439
|
"P",
|
|
@@ -56772,6 +58544,14 @@ var PasteController = class {
|
|
|
56772
58544
|
transformPasted: (slice, view, plain) => this.claimed || plain || view.dragging ? slice : importableSlice(this.options.ctx(), slice),
|
|
56773
58545
|
handlePaste: (view, event) => {
|
|
56774
58546
|
this.claimed = false;
|
|
58547
|
+
if (this.options.richText?.() && event.clipboardData && view.editable) {
|
|
58548
|
+
const data = event.clipboardData;
|
|
58549
|
+
this.run({
|
|
58550
|
+
text: data.getData("text/plain"),
|
|
58551
|
+
html: data.getData("text/html") || void 0
|
|
58552
|
+
}).catch((error) => options.onError?.(error));
|
|
58553
|
+
return true;
|
|
58554
|
+
}
|
|
56775
58555
|
const claim = claimsPaste(view, event);
|
|
56776
58556
|
if (claim === false) return false;
|
|
56777
58557
|
const data = event.clipboardData;
|
|
@@ -56876,6 +58656,17 @@ var PasteController = class {
|
|
|
56876
58656
|
},
|
|
56877
58657
|
controller: new AbortController()
|
|
56878
58658
|
};
|
|
58659
|
+
const rich = this.options.richText?.();
|
|
58660
|
+
if (rich) {
|
|
58661
|
+
const anchor = operation;
|
|
58662
|
+
const state = view.state;
|
|
58663
|
+
const slice = input.plainText ? rich.paste({ text: input.text }, state, this.options.linkSchemes ?? /* @__PURE__ */ new Set()) : rich.paste(input, state, this.options.linkSchemes ?? /* @__PURE__ */ new Set());
|
|
58664
|
+
const tr = state.tr.replaceRange(anchor.from, anchor.to, slice);
|
|
58665
|
+
const end = tr.doc.resolve(tr.mapping.map(anchor.to, 1));
|
|
58666
|
+
this.dispatch(view, tr.setSelection(Selection.near(end, -1)).scrollIntoView());
|
|
58667
|
+
if (slice.size && view.state.doc === state.doc) throw new InkKitError("preservation", "The paste was not applied because the RTF document cannot hold it there");
|
|
58668
|
+
return;
|
|
58669
|
+
}
|
|
56879
58670
|
const inCode = () => view.state.doc.resolve(this.anchor(operation).from).parent.type.spec.code;
|
|
56880
58671
|
this.active = operation;
|
|
56881
58672
|
this.options.onPendingChanged?.();
|
|
@@ -57502,6 +59293,10 @@ var formatCommands = [
|
|
|
57502
59293
|
"orderedList",
|
|
57503
59294
|
"taskList",
|
|
57504
59295
|
"toggleTask",
|
|
59296
|
+
"alignLeft",
|
|
59297
|
+
"alignCenter",
|
|
59298
|
+
"alignRight",
|
|
59299
|
+
"alignJustify",
|
|
57505
59300
|
"link"
|
|
57506
59301
|
];
|
|
57507
59302
|
/** Checks a format call's options against its command. */
|
|
@@ -57685,16 +59480,49 @@ function hostEvents(value) {
|
|
|
57685
59480
|
};
|
|
57686
59481
|
} });
|
|
57687
59482
|
}
|
|
57688
|
-
/** Checks a load's input. A format the host did not offer is refused like an unknown one
|
|
59483
|
+
/** Checks a load's input. A format the host did not offer is refused like an unknown one, and an
|
|
59484
|
+
* RTF document is checked and put in canonical form. */
|
|
57689
59485
|
function documentInput(value, offered) {
|
|
57690
59486
|
const input = expectRecord(value, "input");
|
|
59487
|
+
const documentId = expectString(input.documentId, "input.documentId");
|
|
59488
|
+
const generation = expectGeneration(input.generation, "input.generation");
|
|
59489
|
+
const format = expectOneOf(input.format, offered, "input.format");
|
|
59490
|
+
if (format === "rtf") {
|
|
59491
|
+
if (!isOmitted(input.text)) throw invalidArgument("input.text must be left out for an RTF document");
|
|
59492
|
+
return {
|
|
59493
|
+
documentId,
|
|
59494
|
+
generation,
|
|
59495
|
+
format,
|
|
59496
|
+
text: richTextText(readRichTextDocument(input.document, "input.document"))
|
|
59497
|
+
};
|
|
59498
|
+
}
|
|
59499
|
+
if (!isOmitted(input.document)) throw invalidArgument(`input.document must be left out for ${format}`);
|
|
57691
59500
|
return {
|
|
57692
|
-
documentId
|
|
57693
|
-
generation
|
|
57694
|
-
format
|
|
59501
|
+
documentId,
|
|
59502
|
+
generation,
|
|
59503
|
+
format,
|
|
57695
59504
|
text: expectString(input.text, "input.text")
|
|
57696
59505
|
};
|
|
57697
59506
|
}
|
|
59507
|
+
/** The public snapshot of the editor's text for a document. */
|
|
59508
|
+
function publicSnapshot(saved) {
|
|
59509
|
+
const { documentId, generation, revision, format, text, dirty } = saved;
|
|
59510
|
+
return format === "rtf" ? {
|
|
59511
|
+
documentId,
|
|
59512
|
+
generation,
|
|
59513
|
+
revision,
|
|
59514
|
+
format,
|
|
59515
|
+
document: richTextFromText(text),
|
|
59516
|
+
dirty
|
|
59517
|
+
} : {
|
|
59518
|
+
documentId,
|
|
59519
|
+
generation,
|
|
59520
|
+
revision,
|
|
59521
|
+
format,
|
|
59522
|
+
text,
|
|
59523
|
+
dirty
|
|
59524
|
+
};
|
|
59525
|
+
}
|
|
57698
59526
|
/** A range's shape. Whether its offsets fit the snapshot is `invalid-range`, checked later. */
|
|
57699
59527
|
function textRange(value, name) {
|
|
57700
59528
|
const range = expectRecord(value, name);
|
|
@@ -57818,6 +59646,7 @@ var InkKitEditor = class InkKitEditor {
|
|
|
57818
59646
|
output = new EditorOutput({
|
|
57819
59647
|
ctx: () => this.editor.ctx,
|
|
57820
59648
|
exportDocument: (text) => this.document.exportDocument(text),
|
|
59649
|
+
richTextMarkdown: (content) => richContentMarkdown(this.editor.ctx, content),
|
|
57821
59650
|
diagrams: () => this.diagrams,
|
|
57822
59651
|
root: () => this.root,
|
|
57823
59652
|
handler: () => this.document.handler,
|
|
@@ -57837,7 +59666,7 @@ var InkKitEditor = class InkKitEditor {
|
|
|
57837
59666
|
element: () => this.source.element,
|
|
57838
59667
|
replaceSelection: (text) => this.source.replaceLiteralSelection(text),
|
|
57839
59668
|
writable: () => this.writable,
|
|
57840
|
-
snapshot: () => this.
|
|
59669
|
+
snapshot: () => this.savedText(),
|
|
57841
59670
|
prepareSelection: () => this.clipboardSnapshot("selection"),
|
|
57842
59671
|
capture: () => this.session.capture(),
|
|
57843
59672
|
assertCut: (captured) => this.assertCut(captured),
|
|
@@ -57917,7 +59746,7 @@ var InkKitEditor = class InkKitEditor {
|
|
|
57917
59746
|
};
|
|
57918
59747
|
} });
|
|
57919
59748
|
this.linkSchemes = new Set(options.linkSchemes);
|
|
57920
|
-
this.offered = options.formats ??
|
|
59749
|
+
this.offered = options.formats ?? defaultFormats;
|
|
57921
59750
|
this.formatList = Object.freeze(this.offered.map((format) => Object.freeze({
|
|
57922
59751
|
format,
|
|
57923
59752
|
promise: formatHandlers[format].promise
|
|
@@ -57934,6 +59763,9 @@ var InkKitEditor = class InkKitEditor {
|
|
|
57934
59763
|
}
|
|
57935
59764
|
/** What `changed` reports for the document's new text. */
|
|
57936
59765
|
changeSnapshot(text) {
|
|
59766
|
+
return publicSnapshot(this.saved(text));
|
|
59767
|
+
}
|
|
59768
|
+
saved(text) {
|
|
57937
59769
|
return {
|
|
57938
59770
|
...this.session.version(),
|
|
57939
59771
|
format: this.document.format,
|
|
@@ -57941,6 +59773,11 @@ var InkKitEditor = class InkKitEditor {
|
|
|
57941
59773
|
dirty: text !== this.document.original
|
|
57942
59774
|
};
|
|
57943
59775
|
}
|
|
59776
|
+
/** The current text, as `snapshot` checks and reads it. */
|
|
59777
|
+
savedText(expectedGeneration) {
|
|
59778
|
+
this.assertCurrent(expectedGeneration);
|
|
59779
|
+
return this.saved(this.document.currentText());
|
|
59780
|
+
}
|
|
57944
59781
|
/** Throws unless the document `captured` describes is still current, alive, not composing and
|
|
57945
59782
|
* has no import pending. */
|
|
57946
59783
|
assertCaptured(captured, aborted = false) {
|
|
@@ -58146,7 +59983,7 @@ var InkKitEditor = class InkKitEditor {
|
|
|
58146
59983
|
this.publishCommandState();
|
|
58147
59984
|
}
|
|
58148
59985
|
applyInputPolicy() {
|
|
58149
|
-
applyInputPolicy(this.editor.ctx.get(editorViewCtx), this.source.element, this.inputPreferences, this.options.labels?.
|
|
59986
|
+
applyInputPolicy(this.editor.ctx.get(editorViewCtx), this.source.element, this.inputPreferences, this.options.labels?.[this.document.handler.formattedLabel] ?? defaultLabels[this.document.handler.formattedLabel], {
|
|
58150
59987
|
writable: () => this.writable,
|
|
58151
59988
|
literal: () => this.literalSurface
|
|
58152
59989
|
});
|
|
@@ -58163,8 +60000,16 @@ var InkKitEditor = class InkKitEditor {
|
|
|
58163
60000
|
const { state } = this.editor.ctx.get(editorViewCtx);
|
|
58164
60001
|
const mutable = this.writable && !this.composing && !this.pasteController.pending;
|
|
58165
60002
|
const formatted = mutable && !this.literalSurface;
|
|
58166
|
-
const
|
|
60003
|
+
const rich = this.document.handler.richText != null;
|
|
60004
|
+
const format = formatAvailability(this.editor, state, formatted, this.document.handler);
|
|
58167
60005
|
const tables = formatted ? tableAvailability(this.editor.ctx) : Object.fromEntries(tableCommands.map((name) => [name, false]));
|
|
60006
|
+
if (rich) tables.alignLeft = tables.alignCenter = tables.alignRight = false;
|
|
60007
|
+
const caret = this.literalSurface ? {
|
|
60008
|
+
marks: [],
|
|
60009
|
+
block: { type: "paragraph" },
|
|
60010
|
+
quoted: false
|
|
60011
|
+
} : caretState(state);
|
|
60012
|
+
if (rich) caret.align = caretAlignment(state);
|
|
58168
60013
|
return {
|
|
58169
60014
|
...this.session.version(),
|
|
58170
60015
|
format: this.document.format,
|
|
@@ -58172,11 +60017,7 @@ var InkKitEditor = class InkKitEditor {
|
|
|
58172
60017
|
editable: this.writable,
|
|
58173
60018
|
composing: this.composing,
|
|
58174
60019
|
pending: this.pasteController.pending,
|
|
58175
|
-
caret
|
|
58176
|
-
marks: [],
|
|
58177
|
-
block: { type: "paragraph" },
|
|
58178
|
-
quoted: false
|
|
58179
|
-
} : caretState(state),
|
|
60020
|
+
caret,
|
|
58180
60021
|
table: this.literalSurface ? void 0 : tableContext(state),
|
|
58181
60022
|
commands: {
|
|
58182
60023
|
undo: mutable && this.history.canUndo(state),
|
|
@@ -58184,12 +60025,12 @@ var InkKitEditor = class InkKitEditor {
|
|
|
58184
60025
|
insertText: mutable,
|
|
58185
60026
|
paste: mutable,
|
|
58186
60027
|
replace: mutable,
|
|
58187
|
-
replaceSource: mutable,
|
|
58188
|
-
insertImages: formatted && this.canInsertImages,
|
|
58189
|
-
appendImages: formatted && this.canInsertImages,
|
|
60028
|
+
replaceSource: mutable && !rich,
|
|
60029
|
+
insertImages: formatted && !rich && this.canInsertImages,
|
|
60030
|
+
appendImages: formatted && !rich && this.canInsertImages,
|
|
58190
60031
|
insertPaths: mutable,
|
|
58191
|
-
insertFootnote: formatted,
|
|
58192
|
-
editReferenceDefinition: formatted && referenceDefinitions(state.doc).size > 0,
|
|
60032
|
+
insertFootnote: formatted && !rich,
|
|
60033
|
+
editReferenceDefinition: formatted && !rich && referenceDefinitions(state.doc).size > 0,
|
|
58193
60034
|
format,
|
|
58194
60035
|
table: tables,
|
|
58195
60036
|
moveBlockUp: formatted && canMoveBlock(state, "up"),
|
|
@@ -58197,9 +60038,10 @@ var InkKitEditor = class InkKitEditor {
|
|
|
58197
60038
|
}
|
|
58198
60039
|
};
|
|
58199
60040
|
}
|
|
58200
|
-
/** An image adapter shows and exports an inserted reference, and so does a file adapter.
|
|
60041
|
+
/** An image adapter shows and exports an inserted reference, and so does a file adapter. An RTF
|
|
60042
|
+
* document holds no images. */
|
|
58201
60043
|
get canInsertImages() {
|
|
58202
|
-
return !!this.options.images || !!this.options.files;
|
|
60044
|
+
return (!!this.options.images || !!this.options.files) && !this.document.handler.richText;
|
|
58203
60045
|
}
|
|
58204
60046
|
historyInputPlugin = createHistoryInputPlugin({
|
|
58205
60047
|
writable: () => this.writable,
|
|
@@ -58238,7 +60080,7 @@ var InkKitEditor = class InkKitEditor {
|
|
|
58238
60080
|
this.history.settleSource();
|
|
58239
60081
|
this.history.modeChanged();
|
|
58240
60082
|
},
|
|
58241
|
-
text: () => this.
|
|
60083
|
+
text: () => this.savedText().text,
|
|
58242
60084
|
changed: () => {
|
|
58243
60085
|
this.mode = mode;
|
|
58244
60086
|
this.session.modeChanged();
|
|
@@ -58270,6 +60112,7 @@ var InkKitEditor = class InkKitEditor {
|
|
|
58270
60112
|
this.assertAlive();
|
|
58271
60113
|
expectString(text, "text");
|
|
58272
60114
|
this.assertMutation(expectedGeneration);
|
|
60115
|
+
if (this.document.handler.richText) throw invalidArgument("An RTF document has no source to replace");
|
|
58273
60116
|
const changed = this.history.applySource(text, true);
|
|
58274
60117
|
if (changed) this.clipboard.clear();
|
|
58275
60118
|
return changed;
|
|
@@ -58284,22 +60127,52 @@ var InkKitEditor = class InkKitEditor {
|
|
|
58284
60127
|
this.assertMutation(expectedGeneration);
|
|
58285
60128
|
return this.history.redo();
|
|
58286
60129
|
}
|
|
58287
|
-
/** The complete current
|
|
58288
|
-
*
|
|
60130
|
+
/** The complete current document, which is what a host saves. It throws rather than return a
|
|
60131
|
+
* partial document. */
|
|
58289
60132
|
snapshot(expectedGeneration) {
|
|
58290
|
-
this.
|
|
58291
|
-
|
|
58292
|
-
|
|
58293
|
-
|
|
58294
|
-
|
|
58295
|
-
|
|
58296
|
-
|
|
60133
|
+
return publicSnapshot(this.savedText(expectedGeneration));
|
|
60134
|
+
}
|
|
60135
|
+
/**
|
|
60136
|
+
* The loaded document converted to `format`, which must be offered, with what the conversion
|
|
60137
|
+
* lost. It changes nothing: load the result as a new generation. Markdown converts to RTF and
|
|
60138
|
+
* RTF to Markdown; the same format returns a copy. Throws `invalid-argument` for any other pair.
|
|
60139
|
+
*/
|
|
60140
|
+
convertDocument(format, expectedGeneration) {
|
|
60141
|
+
this.assertAlive();
|
|
60142
|
+
expectOneOf(format, this.offered, "format");
|
|
60143
|
+
const saved = this.savedText(expectedGeneration);
|
|
60144
|
+
const from = saved.format;
|
|
60145
|
+
if (from === format) return from === "rtf" ? {
|
|
60146
|
+
format: "rtf",
|
|
60147
|
+
document: richTextFromText(saved.text),
|
|
60148
|
+
lost: Object.freeze([])
|
|
60149
|
+
} : {
|
|
60150
|
+
format: from,
|
|
60151
|
+
text: saved.text,
|
|
60152
|
+
lost: Object.freeze([])
|
|
58297
60153
|
};
|
|
60154
|
+
if (from === "md" && format === "rtf") {
|
|
60155
|
+
const { document, lost } = markdownToRichText(this.document.exportDocument(saved.text));
|
|
60156
|
+
return {
|
|
60157
|
+
format,
|
|
60158
|
+
document,
|
|
60159
|
+
lost
|
|
60160
|
+
};
|
|
60161
|
+
}
|
|
60162
|
+
if (from === "rtf" && format === "md") {
|
|
60163
|
+
const { text, lost } = richTextToMarkdown(this.editor.ctx, richTextFromText(saved.text));
|
|
60164
|
+
return {
|
|
60165
|
+
format,
|
|
60166
|
+
text,
|
|
60167
|
+
lost
|
|
60168
|
+
};
|
|
60169
|
+
}
|
|
60170
|
+
throw invalidArgument(`Inkkit does not convert ${from} to ${format}; ${from === "txt" || format === "txt" ? "the host converts plain text" : "convert through another format"}`);
|
|
58298
60171
|
}
|
|
58299
60172
|
/** The whole document as standalone HTML for printing. Rejects rather than return partial
|
|
58300
60173
|
* output. */
|
|
58301
60174
|
async printableSnapshot(expectedGeneration) {
|
|
58302
|
-
const snapshot = this.
|
|
60175
|
+
const snapshot = this.savedText(expectedGeneration);
|
|
58303
60176
|
const captured = this.session.capture();
|
|
58304
60177
|
return this.output.printable(snapshot, captured);
|
|
58305
60178
|
}
|
|
@@ -58308,7 +60181,7 @@ var InkKitEditor = class InkKitEditor {
|
|
|
58308
60181
|
async clipboardSnapshot(scope, expectedGeneration) {
|
|
58309
60182
|
this.assertAlive();
|
|
58310
60183
|
const all = (isOmitted(scope) ? "document" : expectOneOf(scope, clipboardScopes, "scope")) === "document";
|
|
58311
|
-
const snapshot = this.
|
|
60184
|
+
const snapshot = this.savedText(expectedGeneration);
|
|
58312
60185
|
const captured = this.session.capture();
|
|
58313
60186
|
return this.output.clipboard(snapshot, captured, all);
|
|
58314
60187
|
}
|
|
@@ -58342,12 +60215,25 @@ var InkKitEditor = class InkKitEditor {
|
|
|
58342
60215
|
expectString(text, "text");
|
|
58343
60216
|
this.assertMutation(expectedGeneration);
|
|
58344
60217
|
const before = this.session.capture();
|
|
60218
|
+
const doc = this.editor.ctx.get(editorViewCtx).state.doc;
|
|
58345
60219
|
this.insertPlainText(text);
|
|
60220
|
+
if (text && this.refusedRichEdit(doc)) throw new InkKitError("preservation", "The text was not pasted because the RTF document cannot hold it there");
|
|
58346
60221
|
return !before.isCurrent();
|
|
58347
60222
|
}
|
|
60223
|
+
/** Whether an RTF edit that should have changed `before` was refused. */
|
|
60224
|
+
refusedRichEdit(before) {
|
|
60225
|
+
return this.document.handler.richText != null && !this.literalSurface && this.editor.ctx.get(editorViewCtx).state.doc === before;
|
|
60226
|
+
}
|
|
58348
60227
|
insertPlainText(text) {
|
|
58349
60228
|
if (this.literalSurface) this.source.replaceLiteralSelection(text, true, this.document.handler.keepsInsertedLineEndings);
|
|
58350
|
-
else
|
|
60229
|
+
else if (this.document.handler.richText) {
|
|
60230
|
+
const view = this.editor.ctx.get(editorViewCtx);
|
|
60231
|
+
const slice = this.document.handler.richText.paste({ text }, view.state, this.linkSchemes);
|
|
60232
|
+
const lines = /[\r\n\u000c\u0085\u2028\u2029\u000b]/.test(text);
|
|
60233
|
+
const tr = view.state.tr.replaceSelection(slice).scrollIntoView();
|
|
60234
|
+
view.dispatch(lines ? closeHistory(tr) : tr);
|
|
60235
|
+
if (lines) view.dispatch(closeHistory(view.state.tr).setMeta("addToHistory", false));
|
|
60236
|
+
} else insertPlainText(this.editor.ctx.get(editorViewCtx), text);
|
|
58351
60237
|
}
|
|
58352
60238
|
/** Runs a table command and returns whether it applied. False in source and TXT modes, outside a
|
|
58353
60239
|
* table for commands that need one, and at a boundary a move can't cross. */
|
|
@@ -58421,15 +60307,13 @@ var InkKitEditor = class InkKitEditor {
|
|
|
58421
60307
|
}
|
|
58422
60308
|
} catch (error) {
|
|
58423
60309
|
view.updateState(view.state);
|
|
58424
|
-
this.refuse(new InkKitError("preservation", `The edit was not applied because its Markdown could not be preserved: ${error instanceof Error ? error.message : String(error)}`, { cause: error }));
|
|
60310
|
+
this.refuse(new InkKitError("preservation", this.document.handler.richText ? `The edit was not applied because the document could not be saved as RTF: ${error instanceof Error ? error.message : String(error)}` : `The edit was not applied because its Markdown could not be preserved: ${error instanceof Error ? error.message : String(error)}`, { cause: error }));
|
|
58425
60311
|
return;
|
|
58426
60312
|
}
|
|
58427
60313
|
}
|
|
58428
60314
|
this.history.didApply(view.state, next);
|
|
58429
60315
|
view.updateState(next);
|
|
58430
60316
|
}
|
|
58431
|
-
/** Builds the editor in `root`. It has no document until `loadDocument`. Rejects with
|
|
58432
|
-
* `invalid-argument` for a bad root, handler or option. */
|
|
58433
60317
|
static async mount(root, events, options) {
|
|
58434
60318
|
if (!(root instanceof HTMLElement)) throw invalidArgument("root must be an HTML element");
|
|
58435
60319
|
events = hostEvents(events);
|
|
@@ -58448,7 +60332,9 @@ var InkKitEditor = class InkKitEditor {
|
|
|
58448
60332
|
commit: (apply) => instance.facadeCall(apply),
|
|
58449
60333
|
literalText: () => instance.literalSurface,
|
|
58450
60334
|
editable: () => instance.writable && !instance.literalSurface,
|
|
58451
|
-
onPendingChanged: () => instance.publishCommandState()
|
|
60335
|
+
onPendingChanged: () => instance.publishCommandState(),
|
|
60336
|
+
richText: () => instance.document.handler.richText,
|
|
60337
|
+
linkSchemes: instance.linkSchemes
|
|
58452
60338
|
});
|
|
58453
60339
|
instance.suggestions = createEditorSuggestions({
|
|
58454
60340
|
root,
|
|
@@ -58476,6 +60362,10 @@ var InkKitEditor = class InkKitEditor {
|
|
|
58476
60362
|
historyInput: instance.historyInputPlugin,
|
|
58477
60363
|
policy: instance.policyPlugin
|
|
58478
60364
|
},
|
|
60365
|
+
format: {
|
|
60366
|
+
markdownInput: () => instance.document.handler.markdownInput,
|
|
60367
|
+
richText: () => instance.document.handler.richText
|
|
60368
|
+
},
|
|
58479
60369
|
authoredSources: (doc, positions) => instance.document.authoredSources(doc, positions),
|
|
58480
60370
|
smartPunctuation: () => instance.inputPreferences.smartPunctuation === true,
|
|
58481
60371
|
linkSchemes: instance.linkSchemes,
|
|
@@ -58537,9 +60427,9 @@ var InkKitEditor = class InkKitEditor {
|
|
|
58537
60427
|
}
|
|
58538
60428
|
/** Replaces the document. The generation must be greater than the loaded one. A failed load
|
|
58539
60429
|
* changes nothing. */
|
|
58540
|
-
loadDocument(
|
|
60430
|
+
loadDocument(document) {
|
|
58541
60431
|
this.assertAlive();
|
|
58542
|
-
input = documentInput(
|
|
60432
|
+
const input = documentInput(document, this.offered);
|
|
58543
60433
|
this.session.checkLoad(input.generation, "input.generation");
|
|
58544
60434
|
const handler = formatHandlers[input.format];
|
|
58545
60435
|
const markdown = handler.literalOnly ? "" : input.text;
|
|
@@ -58547,7 +60437,7 @@ var InkKitEditor = class InkKitEditor {
|
|
|
58547
60437
|
try {
|
|
58548
60438
|
source = handler.load(this.editor.ctx, markdown);
|
|
58549
60439
|
} catch (error) {
|
|
58550
|
-
throw
|
|
60440
|
+
throw coded(error, "preservation", "The document was not loaded because its Markdown could not be shown without losing part of it");
|
|
58551
60441
|
}
|
|
58552
60442
|
const container = this.geometry.container;
|
|
58553
60443
|
const top = container.scrollTop, left = container.scrollLeft;
|
|
@@ -58564,7 +60454,7 @@ var InkKitEditor = class InkKitEditor {
|
|
|
58564
60454
|
this.source.reset();
|
|
58565
60455
|
this.search.reset();
|
|
58566
60456
|
this.document.handler = handler;
|
|
58567
|
-
this.source.setSourceValue(input.text);
|
|
60457
|
+
this.source.setSourceValue(handler.richText ? "" : input.text);
|
|
58568
60458
|
this.source.element.setSelectionRange(this.source.element.value.length, this.source.element.value.length);
|
|
58569
60459
|
this.load(markdown, source);
|
|
58570
60460
|
this.document.original = input.text;
|
|
@@ -58583,9 +60473,9 @@ var InkKitEditor = class InkKitEditor {
|
|
|
58583
60473
|
/** Replaces the document with a newer version of it, keeping the selection, focus, scroll
|
|
58584
60474
|
* position and Markdown editing mode. With `expectedGeneration`, the loaded document must be
|
|
58585
60475
|
* that generation. */
|
|
58586
|
-
reloadDocument(
|
|
60476
|
+
reloadDocument(document, expectedGeneration) {
|
|
58587
60477
|
this.assertAlive();
|
|
58588
|
-
input = documentInput(
|
|
60478
|
+
const input = documentInput(document, this.offered);
|
|
58589
60479
|
if (!isOmitted(expectedGeneration)) this.assertReady(expectedGeneration);
|
|
58590
60480
|
this.session.checkLoad(input.generation, "input.generation");
|
|
58591
60481
|
const view = this.editor.ctx.get(editorViewCtx);
|
|
@@ -58595,7 +60485,7 @@ var InkKitEditor = class InkKitEditor {
|
|
|
58595
60485
|
const saved = captureReloadSurface(view, this.source.element, this.geometry.container, wasLiteral);
|
|
58596
60486
|
this.reportingSuppressed = true;
|
|
58597
60487
|
try {
|
|
58598
|
-
this.loadDocument(
|
|
60488
|
+
this.loadDocument(document);
|
|
58599
60489
|
if (previous.sourceMode && this.document.handler.sourceMode && previousMode === "source") this.setEditingMode("source", input.generation);
|
|
58600
60490
|
restoreReloadSurface(view, this.source.element, () => this.literalSurface, saved);
|
|
58601
60491
|
} finally {
|
|
@@ -58730,7 +60620,8 @@ var InkKitEditor = class InkKitEditor {
|
|
|
58730
60620
|
expectString(search, "search");
|
|
58731
60621
|
expectString(replacement, "replacement");
|
|
58732
60622
|
this.assertMutation(expectedGeneration);
|
|
58733
|
-
|
|
60623
|
+
const doc = this.editor.ctx.get(editorViewCtx).state.doc;
|
|
60624
|
+
return this.search.replace(search, replacement, false) > 0 && (replacement === search || !this.refusedRichEdit(doc));
|
|
58734
60625
|
}
|
|
58735
60626
|
/** Replaces every non-overlapping match once and returns how many. */
|
|
58736
60627
|
replaceAll(search, replacement, expectedGeneration) {
|
|
@@ -58738,7 +60629,9 @@ var InkKitEditor = class InkKitEditor {
|
|
|
58738
60629
|
expectString(search, "search");
|
|
58739
60630
|
expectString(replacement, "replacement");
|
|
58740
60631
|
this.assertMutation(expectedGeneration);
|
|
58741
|
-
|
|
60632
|
+
const doc = this.editor.ctx.get(editorViewCtx).state.doc;
|
|
60633
|
+
const count = this.search.replace(search, replacement, true);
|
|
60634
|
+
return count && replacement !== search && this.refusedRichEdit(doc) ? 0 : count;
|
|
58742
60635
|
}
|
|
58743
60636
|
outlineContext() {
|
|
58744
60637
|
const { documentId, generation, revision, key } = this.session.capture();
|
|
@@ -58781,11 +60674,10 @@ var InkKitEditor = class InkKitEditor {
|
|
|
58781
60674
|
this.source.replaceLiteralSelection(text, false);
|
|
58782
60675
|
return true;
|
|
58783
60676
|
}
|
|
58784
|
-
|
|
58785
|
-
|
|
58786
|
-
|
|
58787
|
-
|
|
58788
|
-
return insertNativeText(this.editor.ctx.get(editorViewCtx), text);
|
|
60677
|
+
const doc = this.editor.ctx.get(editorViewCtx).state.doc;
|
|
60678
|
+
if (/[\r\n]/.test(text)) this.insertPlainText(text);
|
|
60679
|
+
else if (!insertNativeText(this.editor.ctx.get(editorViewCtx), text)) return false;
|
|
60680
|
+
return !(text && this.refusedRichEdit(doc));
|
|
58789
60681
|
}
|
|
58790
60682
|
/**
|
|
58791
60683
|
* A key the host's native input received, run through InkKit's key handling. Returns whether
|
|
@@ -58964,12 +60856,14 @@ var InkKitEditor = class InkKitEditor {
|
|
|
58964
60856
|
format(command, options, expectedGeneration) {
|
|
58965
60857
|
this.assertAlive();
|
|
58966
60858
|
const checked = formatOptions(command, options);
|
|
60859
|
+
if (checked?.destination !== void 0 && this.document.handler.richText) checked.destination = checked.destination.trim();
|
|
58967
60860
|
if (checked?.destination !== void 0) this.checkDestination(checked.destination, "options.destination");
|
|
58968
60861
|
this.assertMutation(expectedGeneration);
|
|
58969
60862
|
if (this.literalSurface) return false;
|
|
58970
60863
|
const view = this.editor.ctx.get(editorViewCtx);
|
|
58971
60864
|
const before = view.state;
|
|
58972
|
-
|
|
60865
|
+
if ((this.document.handler.richText || command.startsWith("align")) && !this.commandState().commands.format[command]) return false;
|
|
60866
|
+
applyFormat(this.editor, command, checked, (destination) => this.link(destination), this.document.handler.richText != null);
|
|
58973
60867
|
this.focus();
|
|
58974
60868
|
const after = view.state;
|
|
58975
60869
|
const marks = (state) => state.storedMarks ?? state.selection.$from.marks();
|
|
@@ -58977,11 +60871,14 @@ var InkKitEditor = class InkKitEditor {
|
|
|
58977
60871
|
}
|
|
58978
60872
|
/**
|
|
58979
60873
|
* Refuses a link destination that is empty, that opens anything but a web, mail or phone address
|
|
58980
|
-
* or a scheme the host listed, or that the
|
|
58981
|
-
* same.
|
|
60874
|
+
* or a scheme the host listed, or, in Markdown, that the writer cannot save so that it reads back
|
|
60875
|
+
* the same. In RTF, it also refuses what the rich text link rule refuses.
|
|
58982
60876
|
*/
|
|
58983
60877
|
checkDestination(destination, name) {
|
|
58984
|
-
|
|
60878
|
+
const rich = this.document.handler.richText != null;
|
|
60879
|
+
checkDestination(this.editor.ctx, destination, name, this.linkSchemes, !rich);
|
|
60880
|
+
const problem = rich ? linkProblem(destination) : void 0;
|
|
60881
|
+
if (problem) throw invalidArgument(`${name}: ${problem}`);
|
|
58985
60882
|
}
|
|
58986
60883
|
/**
|
|
58987
60884
|
* With a destination, sets the link: on the whole link that holds the caret or the selection,
|
|
@@ -59000,7 +60897,7 @@ var InkKitEditor = class InkKitEditor {
|
|
|
59000
60897
|
this.checkDestination(expectString(destination, "destination"), "destination");
|
|
59001
60898
|
optionalString(title, "title");
|
|
59002
60899
|
this.assertMutation(expectedGeneration);
|
|
59003
|
-
if (this.literalSurface) return false;
|
|
60900
|
+
if (this.literalSurface || this.document.handler.richText) return false;
|
|
59004
60901
|
return editReferenceDefinition(this.editor.ctx.get(editorViewCtx), label, destination, title);
|
|
59005
60902
|
}
|
|
59006
60903
|
/** Inserts a footnote reference, and a definition when the label has none, and returns whether
|
|
@@ -59010,8 +60907,8 @@ var InkKitEditor = class InkKitEditor {
|
|
|
59010
60907
|
const given = optionalString(label, "label");
|
|
59011
60908
|
if (given !== void 0 && !footnoteLabel(given)) throw invalidArgument("A footnote label must be 1 to 999 characters without whitespace, brackets or backslashes");
|
|
59012
60909
|
this.assertMutation(expectedGeneration);
|
|
59013
|
-
if (this.literalSurface) return false;
|
|
59014
|
-
insertFootnote(this.editor.ctx.get(editorViewCtx), label, () => this.
|
|
60910
|
+
if (this.literalSurface || this.document.handler.richText) return false;
|
|
60911
|
+
insertFootnote(this.editor.ctx.get(editorViewCtx), label, () => this.savedText().text);
|
|
59015
60912
|
this.focus();
|
|
59016
60913
|
return true;
|
|
59017
60914
|
}
|
|
@@ -59031,6 +60928,7 @@ var InkKitEditor = class InkKitEditor {
|
|
|
59031
60928
|
"loadDocument",
|
|
59032
60929
|
"reloadDocument",
|
|
59033
60930
|
"snapshot",
|
|
60931
|
+
"convertDocument",
|
|
59034
60932
|
"printableSnapshot",
|
|
59035
60933
|
"clipboardSnapshot",
|
|
59036
60934
|
"paste",
|
|
@@ -59094,13 +60992,13 @@ var InkKitEditor = class InkKitEditor {
|
|
|
59094
60992
|
});
|
|
59095
60993
|
}
|
|
59096
60994
|
const mount = InkKitEditor.mount;
|
|
59097
|
-
InkKitEditor.mount = async (...args) => {
|
|
60995
|
+
InkKitEditor.mount = (async (...args) => {
|
|
59098
60996
|
try {
|
|
59099
60997
|
return await mount(...args);
|
|
59100
60998
|
} catch (error) {
|
|
59101
60999
|
throw coded(error);
|
|
59102
61000
|
}
|
|
59103
|
-
};
|
|
61001
|
+
});
|
|
59104
61002
|
}
|
|
59105
61003
|
};
|
|
59106
61004
|
//#endregion
|