@domternal/core 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -0
- package/dist/index.cjs +276 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +156 -8
- package/dist/index.d.ts +156 -8
- package/dist/index.js +276 -49
- package/dist/index.js.map +1 -1
- package/package.json +12 -14
package/README.md
CHANGED
|
@@ -78,6 +78,47 @@ const editor = new Editor({
|
|
|
78
78
|
});
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
+
## Presets
|
|
82
|
+
|
|
83
|
+
`preset: 'notion'` switches the editor to the Notion-style experience in one option. It paints
|
|
84
|
+
the `dm-notion-mode` class on the `.dm-editor` host for you, and preset-aware code follows it:
|
|
85
|
+
the bubble menu serves the Notion text context, and images offer align controls instead of
|
|
86
|
+
float. `'classic'` is the default.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const editor = new Editor({
|
|
90
|
+
element: document.getElementById('editor')!,
|
|
91
|
+
extensions: [StarterKit],
|
|
92
|
+
preset: 'notion',
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`editor.preset` reports the resolved value, and still reports `'notion'` for a host that carries
|
|
97
|
+
the theme class alone, so setups that predate the option are unaffected.
|
|
98
|
+
|
|
99
|
+
## Printing
|
|
100
|
+
|
|
101
|
+
`Print` is not part of `StarterKit`, so add it explicitly. `printDocument()` isolates the
|
|
102
|
+
document from the host page before the browser's dialog opens, leaving your application's
|
|
103
|
+
sidebar, header, and everything else beside the document off the page, and it emits
|
|
104
|
+
`beforePrint` and `afterPrint` so you can set `document.title` or add page rules first.
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { Editor, StarterKit, Print } from '@domternal/core';
|
|
108
|
+
|
|
109
|
+
const editor = new Editor({
|
|
110
|
+
element: document.getElementById('editor')!,
|
|
111
|
+
extensions: [StarterKit, Print],
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
editor.commands.printDocument();
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The hiding itself is CSS, and it lives in
|
|
118
|
+
[`@domternal/theme`](https://www.npmjs.com/package/@domternal/theme), whose paper layer also
|
|
119
|
+
applies to the reader's own Ctrl/Cmd+P with no code involved. Set `isolateNativePrint: true` to
|
|
120
|
+
give that shortcut the same isolation as the command.
|
|
121
|
+
|
|
81
122
|
## SSR
|
|
82
123
|
|
|
83
124
|
The `generateHTML`, `generateJSON`, and `generateText` helpers render content
|
package/dist/index.cjs
CHANGED
|
@@ -1140,7 +1140,8 @@ function markInputRule(options) {
|
|
|
1140
1140
|
return null;
|
|
1141
1141
|
}
|
|
1142
1142
|
const { tr } = state;
|
|
1143
|
-
tr.
|
|
1143
|
+
const marks = tr.storedMarks ?? tr.doc.resolve(start).marksAcross(tr.doc.resolve(end));
|
|
1144
|
+
tr.replaceWith(start, end, state.schema.text(textContent, marks));
|
|
1144
1145
|
tr.addMark(start, start + textContent.length, type.create(attributes ?? void 0));
|
|
1145
1146
|
tr.removeStoredMark(type);
|
|
1146
1147
|
return tr;
|
|
@@ -1232,7 +1233,10 @@ function textInputRule(options) {
|
|
|
1232
1233
|
const { find: find2, replace, undoable } = options;
|
|
1233
1234
|
return new inputrules.InputRule(
|
|
1234
1235
|
find2,
|
|
1235
|
-
|
|
1236
|
+
// insertText inherits the replaced range's marks (stored marks first,
|
|
1237
|
+
// then marksAcross), so a replacement inside marked text keeps the
|
|
1238
|
+
// marks instead of punching an unmarked hole into them.
|
|
1239
|
+
(state, _match, start, end) => state.tr.insertText(replace, start, end),
|
|
1236
1240
|
undoable !== void 0 ? { undoable } : {}
|
|
1237
1241
|
);
|
|
1238
1242
|
}
|
|
@@ -1811,6 +1815,8 @@ var Mark = class _Mark extends Extension {
|
|
|
1811
1815
|
if (this.config.group !== void 0) spec.group = this.config.group;
|
|
1812
1816
|
if (this.config.spanning !== void 0)
|
|
1813
1817
|
spec.spanning = this.config.spanning;
|
|
1818
|
+
if (this.config.keepOnDuplicate !== void 0)
|
|
1819
|
+
spec["keepOnDuplicate"] = this.config.keepOnDuplicate;
|
|
1814
1820
|
const attributeSpecs = callOrReturn(this.config.addAttributes, this);
|
|
1815
1821
|
if (attributeSpecs) {
|
|
1816
1822
|
spec.attrs = buildProseMirrorAttrs(attributeSpecs);
|
|
@@ -1848,9 +1854,9 @@ var Mark = class _Mark extends Extension {
|
|
|
1848
1854
|
const renderFn = this.config.renderHTML;
|
|
1849
1855
|
const attrSpecs = attributeSpecs;
|
|
1850
1856
|
const markInstance = this;
|
|
1851
|
-
spec.toDOM = (
|
|
1852
|
-
const htmlAttrs = attrSpecs ? buildHTMLAttributes(
|
|
1853
|
-
return renderFn.call(markInstance, { mark, HTMLAttributes: htmlAttrs });
|
|
1857
|
+
spec.toDOM = (mark2, _inline) => {
|
|
1858
|
+
const htmlAttrs = attrSpecs ? buildHTMLAttributes(mark2.attrs, attrSpecs) : {};
|
|
1859
|
+
return renderFn.call(markInstance, { mark: mark2, HTMLAttributes: htmlAttrs });
|
|
1854
1860
|
};
|
|
1855
1861
|
}
|
|
1856
1862
|
return spec;
|
|
@@ -1943,8 +1949,8 @@ var setMark = (markName, attributes) => ({ state, tr, dispatch }) => {
|
|
|
1943
1949
|
const from = firstRange.$from.pos;
|
|
1944
1950
|
const existingMark = tr.storedMarks?.find((m) => m.type === markType) ?? state.storedMarks?.find((m) => m.type === markType) ?? tr.doc.resolve(from).marks().find((m) => m.type === markType) ?? null;
|
|
1945
1951
|
const mergedAttrs = existingMark ? { ...existingMark.attrs, ...attributes } : attributes;
|
|
1946
|
-
const
|
|
1947
|
-
tr.addStoredMark(
|
|
1952
|
+
const mark2 = markType.create(mergedAttrs);
|
|
1953
|
+
tr.addStoredMark(mark2);
|
|
1948
1954
|
dispatch(tr);
|
|
1949
1955
|
return true;
|
|
1950
1956
|
}
|
|
@@ -2571,12 +2577,12 @@ var updateAttributes = (typeOrName, attributes) => ({ state, tr, dispatch }) =>
|
|
|
2571
2577
|
const markType = state.schema.marks[typeOrName];
|
|
2572
2578
|
tr.doc.nodesBetween(from, to, (node, pos) => {
|
|
2573
2579
|
if (!node.isInline) return;
|
|
2574
|
-
const
|
|
2575
|
-
if (
|
|
2580
|
+
const mark2 = markType.isInSet(node.marks);
|
|
2581
|
+
if (mark2) {
|
|
2576
2582
|
markChanges.push({
|
|
2577
2583
|
pos,
|
|
2578
2584
|
nodeSize: node.nodeSize,
|
|
2579
|
-
attrs: { ...
|
|
2585
|
+
attrs: { ...mark2.attrs, ...attributes }
|
|
2580
2586
|
});
|
|
2581
2587
|
}
|
|
2582
2588
|
});
|
|
@@ -2622,12 +2628,12 @@ var resetAttributes = (typeOrName, attributeName) => ({ state, tr, dispatch }) =
|
|
|
2622
2628
|
const defaultValue = markType.spec.attrs?.[attributeName]?.default;
|
|
2623
2629
|
tr.doc.nodesBetween(from, to, (node, pos) => {
|
|
2624
2630
|
if (!node.isInline) return;
|
|
2625
|
-
const
|
|
2626
|
-
if (
|
|
2631
|
+
const mark2 = markType.isInSet(node.marks);
|
|
2632
|
+
if (mark2) {
|
|
2627
2633
|
markChanges.push({
|
|
2628
2634
|
pos,
|
|
2629
2635
|
nodeSize: node.nodeSize,
|
|
2630
|
-
attrs: { ...
|
|
2636
|
+
attrs: { ...mark2.attrs, [attributeName]: defaultValue }
|
|
2631
2637
|
});
|
|
2632
2638
|
}
|
|
2633
2639
|
});
|
|
@@ -3510,6 +3516,12 @@ var Editor = class _Editor extends EventEmitter {
|
|
|
3510
3516
|
* True while EditorView's constructor runs; see buildViewDispatch.
|
|
3511
3517
|
*/
|
|
3512
3518
|
_isViewConstructing = false;
|
|
3519
|
+
/**
|
|
3520
|
+
* The `.dm-editor` host this editor painted `dm-notion-mode` onto because
|
|
3521
|
+
* of `preset: 'notion'`. Tracked so destroy() removes only a class the
|
|
3522
|
+
* editor itself added, never one the consumer wrote.
|
|
3523
|
+
*/
|
|
3524
|
+
_presetClassHost = null;
|
|
3513
3525
|
/**
|
|
3514
3526
|
* Creates a new Editor instance
|
|
3515
3527
|
*
|
|
@@ -3554,6 +3566,43 @@ var Editor = class _Editor extends EventEmitter {
|
|
|
3554
3566
|
get isEditable() {
|
|
3555
3567
|
return this.view ? this.view.editable : this.options.editable ?? true;
|
|
3556
3568
|
}
|
|
3569
|
+
/**
|
|
3570
|
+
* The resolved editing-experience preset.
|
|
3571
|
+
*
|
|
3572
|
+
* The `preset` option wins when provided (so an explicit 'classic' can
|
|
3573
|
+
* opt out of everything). Otherwise a `dm-notion-mode` class on or above
|
|
3574
|
+
* the view counts as 'notion': consumers that predate the option declare
|
|
3575
|
+
* Notion mode with the theme class alone, and behavior must follow what
|
|
3576
|
+
* the user actually sees. Resolved on every read, not cached, so a class
|
|
3577
|
+
* toggled at runtime is picked up.
|
|
3578
|
+
*/
|
|
3579
|
+
get preset() {
|
|
3580
|
+
if (this.options.preset) {
|
|
3581
|
+
return this.options.preset;
|
|
3582
|
+
}
|
|
3583
|
+
if (this.view?.dom.closest(".dm-notion-mode")) {
|
|
3584
|
+
return "notion";
|
|
3585
|
+
}
|
|
3586
|
+
return "classic";
|
|
3587
|
+
}
|
|
3588
|
+
/**
|
|
3589
|
+
* Paints `dm-notion-mode` on the `.dm-editor` host when the editor was
|
|
3590
|
+
* created with `preset: 'notion'`. Runs during creation, and framework
|
|
3591
|
+
* wrappers call it again after adopting the view's DOM: they construct
|
|
3592
|
+
* the editor in a detached element, so the creation-time run cannot see
|
|
3593
|
+
* the host yet. Idempotent; a no-op for any other preset. Only a class
|
|
3594
|
+
* added here is removed again on destroy.
|
|
3595
|
+
*/
|
|
3596
|
+
adoptPresetClass() {
|
|
3597
|
+
if (this.options.preset !== "notion" || this._presetClassHost) {
|
|
3598
|
+
return;
|
|
3599
|
+
}
|
|
3600
|
+
const host = this.view.dom.closest(".dm-editor");
|
|
3601
|
+
if (host && !host.classList.contains("dm-notion-mode")) {
|
|
3602
|
+
host.classList.add("dm-notion-mode");
|
|
3603
|
+
this._presetClassHost = host;
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3557
3606
|
/**
|
|
3558
3607
|
* Checks if the editor content is empty
|
|
3559
3608
|
*/
|
|
@@ -3639,11 +3688,11 @@ var Editor = class _Editor extends EventEmitter {
|
|
|
3639
3688
|
if (markType) {
|
|
3640
3689
|
if (selection.empty) {
|
|
3641
3690
|
const storedMarks = state.storedMarks ?? $from.marks();
|
|
3642
|
-
const hasMark = storedMarks.some((
|
|
3691
|
+
const hasMark = storedMarks.some((mark2) => mark2.type === markType);
|
|
3643
3692
|
if (!hasMark) return false;
|
|
3644
3693
|
if (attrs) {
|
|
3645
|
-
const
|
|
3646
|
-
return
|
|
3694
|
+
const mark2 = storedMarks.find((m) => m.type === markType);
|
|
3695
|
+
return mark2 ? this.matchAttributes(mark2.attrs, attrs) : false;
|
|
3647
3696
|
}
|
|
3648
3697
|
return true;
|
|
3649
3698
|
}
|
|
@@ -3718,8 +3767,8 @@ var Editor = class _Editor extends EventEmitter {
|
|
|
3718
3767
|
const markType = schema.marks[name];
|
|
3719
3768
|
if (markType) {
|
|
3720
3769
|
const marks = state.storedMarks ?? $from.marks();
|
|
3721
|
-
const
|
|
3722
|
-
return
|
|
3770
|
+
const mark2 = marks.find((m) => m.type === markType);
|
|
3771
|
+
return mark2 ? { ...mark2.attrs } : {};
|
|
3723
3772
|
}
|
|
3724
3773
|
const nodeType = schema.nodes[name];
|
|
3725
3774
|
if (nodeType) {
|
|
@@ -3900,6 +3949,10 @@ var Editor = class _Editor extends EventEmitter {
|
|
|
3900
3949
|
}
|
|
3901
3950
|
this.emit("destroy");
|
|
3902
3951
|
this.options.onDestroy?.();
|
|
3952
|
+
if (this._presetClassHost) {
|
|
3953
|
+
this._presetClassHost.classList.remove("dm-notion-mode");
|
|
3954
|
+
this._presetClassHost = null;
|
|
3955
|
+
}
|
|
3903
3956
|
this.view.destroy();
|
|
3904
3957
|
this._extensionManager.destroy();
|
|
3905
3958
|
this.removeAllListeners();
|
|
@@ -4002,6 +4055,7 @@ var Editor = class _Editor extends EventEmitter {
|
|
|
4002
4055
|
}
|
|
4003
4056
|
});
|
|
4004
4057
|
this._isViewConstructing = false;
|
|
4058
|
+
this.adoptPresetClass();
|
|
4005
4059
|
this.emit("mount", { editor: this, view: this.view });
|
|
4006
4060
|
this.options.onMount?.({ editor: this, view: this.view });
|
|
4007
4061
|
this.commandManager = new CommandManager(this);
|
|
@@ -4238,7 +4292,6 @@ function refocusEditorAfterCommand(view) {
|
|
|
4238
4292
|
}
|
|
4239
4293
|
|
|
4240
4294
|
// src/utils/defaultBubbleContexts.ts
|
|
4241
|
-
var NOTION_MODE_CLASS = "dm-notion-mode";
|
|
4242
4295
|
var NOTION_TEXT_CONTEXT = Object.freeze([
|
|
4243
4296
|
// `ai` leads (Notion's "Ask AI"); skipped with its leading separator when
|
|
4244
4297
|
// the pro extension is absent, exactly like `mathInline`.
|
|
@@ -4266,8 +4319,7 @@ var STANDARD_TEXT_CONTEXT = Object.freeze([
|
|
|
4266
4319
|
"link"
|
|
4267
4320
|
]);
|
|
4268
4321
|
function defaultBubbleContexts(editor) {
|
|
4269
|
-
const
|
|
4270
|
-
const text = inNotionMode ? NOTION_TEXT_CONTEXT : STANDARD_TEXT_CONTEXT;
|
|
4322
|
+
const text = editor.preset === "notion" ? NOTION_TEXT_CONTEXT : STANDARD_TEXT_CONTEXT;
|
|
4271
4323
|
return { text: [...text] };
|
|
4272
4324
|
}
|
|
4273
4325
|
|
|
@@ -5026,7 +5078,8 @@ function groupFloatingMenuItems(items) {
|
|
|
5026
5078
|
if (!list) {
|
|
5027
5079
|
list = [];
|
|
5028
5080
|
map.set(name, list);
|
|
5029
|
-
order.
|
|
5081
|
+
if (name === "") order.unshift(name);
|
|
5082
|
+
else order.push(name);
|
|
5030
5083
|
}
|
|
5031
5084
|
list.push(item);
|
|
5032
5085
|
}
|
|
@@ -5560,7 +5613,8 @@ var defaultIcons = {
|
|
|
5560
5613
|
plus: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M224,128a8,8,0,0,1-8,8H136v80a8,8,0,0,1-16,0V136H40a8,8,0,0,1,0-16h80V40a8,8,0,0,1,16,0v80h80A8,8,0,0,1,224,128Z"/></svg>',
|
|
5561
5614
|
dotsSixVertical: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M108,60A16,16,0,1,1,92,44,16,16,0,0,1,108,60Zm56,16A16,16,0,1,0,148,60,16,16,0,0,0,164,76ZM92,112a16,16,0,1,0,16,16A16,16,0,0,0,92,112Zm72,0a16,16,0,1,0,16,16A16,16,0,0,0,164,112ZM92,180a16,16,0,1,0,16,16A16,16,0,0,0,92,180Zm72,0a16,16,0,1,0,16,16A16,16,0,0,0,164,180Z"/></svg>',
|
|
5562
5615
|
dotsThree: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M140,128a12,12,0,1,1-12-12A12,12,0,0,1,140,128ZM64,116a12,12,0,1,0,12,12A12,12,0,0,0,64,116Zm128,0a12,12,0,1,0,12,12A12,12,0,0,0,192,116Z"/></svg>',
|
|
5563
|
-
copy: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z"/></svg>'
|
|
5616
|
+
copy: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z"/></svg>',
|
|
5617
|
+
printer: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M214.67,72H200V40a8,8,0,0,0-8-8H64a8,8,0,0,0-8,8V72H41.33C27.36,72,16,82.77,16,96v80a8,8,0,0,0,8,8H56v32a8,8,0,0,0,8,8H192a8,8,0,0,0,8-8V184h32a8,8,0,0,0,8-8V96C240,82.77,228.64,72,214.67,72ZM72,48H184V72H72ZM184,208H72V160H184Zm40-40H200V152a8,8,0,0,0-8-8H64a8,8,0,0,0-8,8v16H32V96c0-4.34,4.28-8,9.33-8H214.67c5.05,0,9.33,3.66,9.33,8Zm-24-52a12,12,0,1,1-12-12A12,12,0,0,1,200,116Z"/></svg>'
|
|
5564
5618
|
};
|
|
5565
5619
|
|
|
5566
5620
|
// src/nodes/Document.ts
|
|
@@ -7092,6 +7146,7 @@ var TaskList = Node2.create({
|
|
|
7092
7146
|
// src/marks/Bold.ts
|
|
7093
7147
|
var Bold = Mark.create({
|
|
7094
7148
|
name: "bold",
|
|
7149
|
+
group: "formatting",
|
|
7095
7150
|
addOptions() {
|
|
7096
7151
|
return {
|
|
7097
7152
|
HTMLAttributes: {}
|
|
@@ -7169,6 +7224,7 @@ var Bold = Mark.create({
|
|
|
7169
7224
|
// src/marks/Italic.ts
|
|
7170
7225
|
var Italic = Mark.create({
|
|
7171
7226
|
name: "italic",
|
|
7227
|
+
group: "formatting",
|
|
7172
7228
|
addOptions() {
|
|
7173
7229
|
return {
|
|
7174
7230
|
HTMLAttributes: {}
|
|
@@ -7248,6 +7304,7 @@ var Italic = Mark.create({
|
|
|
7248
7304
|
// src/marks/Underline.ts
|
|
7249
7305
|
var Underline = Mark.create({
|
|
7250
7306
|
name: "underline",
|
|
7307
|
+
group: "formatting",
|
|
7251
7308
|
addOptions() {
|
|
7252
7309
|
return {
|
|
7253
7310
|
HTMLAttributes: {}
|
|
@@ -7300,6 +7357,7 @@ var Underline = Mark.create({
|
|
|
7300
7357
|
// src/marks/Strike.ts
|
|
7301
7358
|
var Strike = Mark.create({
|
|
7302
7359
|
name: "strike",
|
|
7360
|
+
group: "formatting",
|
|
7303
7361
|
addOptions() {
|
|
7304
7362
|
return {
|
|
7305
7363
|
HTMLAttributes: {}
|
|
@@ -7371,9 +7429,14 @@ var Code = Mark.create({
|
|
|
7371
7429
|
HTMLAttributes: {}
|
|
7372
7430
|
};
|
|
7373
7431
|
},
|
|
7374
|
-
// Code
|
|
7375
|
-
//
|
|
7376
|
-
|
|
7432
|
+
// Code cannot be combined with other FORMATTING marks (bold, italic,
|
|
7433
|
+
// color...), but semantic marks outside the group (link, a comment
|
|
7434
|
+
// thread anchor) survive: code excludes the 'formatting' group instead
|
|
7435
|
+
// of '_' (everything). Third-party formatting marks opt into the same
|
|
7436
|
+
// exclusion by declaring group: 'formatting'. Code is in the group
|
|
7437
|
+
// itself, which also keeps it self-exclusive.
|
|
7438
|
+
group: "formatting",
|
|
7439
|
+
excludes: "formatting",
|
|
7377
7440
|
// Code should not span across multiple nodes
|
|
7378
7441
|
spanning: false,
|
|
7379
7442
|
parseHTML() {
|
|
@@ -7861,6 +7924,7 @@ var Link = Mark.create({
|
|
|
7861
7924
|
// src/marks/Subscript.ts
|
|
7862
7925
|
var Subscript = Mark.create({
|
|
7863
7926
|
name: "subscript",
|
|
7927
|
+
group: "formatting",
|
|
7864
7928
|
// Mutual exclusion handled in toggle commands (not schema)
|
|
7865
7929
|
// so can() dry-run works correctly for toolbar disabled state
|
|
7866
7930
|
excludes: "",
|
|
@@ -7922,6 +7986,7 @@ var Subscript = Mark.create({
|
|
|
7922
7986
|
// src/marks/Superscript.ts
|
|
7923
7987
|
var Superscript = Mark.create({
|
|
7924
7988
|
name: "superscript",
|
|
7989
|
+
group: "formatting",
|
|
7925
7990
|
// Mutual exclusion handled in toggle commands (not schema)
|
|
7926
7991
|
// so can() dry-run works correctly for toolbar disabled state
|
|
7927
7992
|
excludes: "",
|
|
@@ -7983,6 +8048,7 @@ var Superscript = Mark.create({
|
|
|
7983
8048
|
// src/marks/TextStyle.ts
|
|
7984
8049
|
var TextStyle = Mark.create({
|
|
7985
8050
|
name: "textStyle",
|
|
8051
|
+
group: "formatting",
|
|
7986
8052
|
// Lower priority so it renders after other marks
|
|
7987
8053
|
priority: 101,
|
|
7988
8054
|
addOptions() {
|
|
@@ -8039,7 +8105,7 @@ var TextStyle = Mark.create({
|
|
|
8039
8105
|
let hasEmptyTextStyle = false;
|
|
8040
8106
|
tr.doc.nodesBetween(from, to, (node) => {
|
|
8041
8107
|
const textStyleMark = node.marks.find(
|
|
8042
|
-
(
|
|
8108
|
+
(mark2) => mark2.type.name === this.name
|
|
8043
8109
|
);
|
|
8044
8110
|
if (textStyleMark) {
|
|
8045
8111
|
const hasNonNullAttr = Object.values(textStyleMark.attrs).some(
|
|
@@ -8053,9 +8119,9 @@ var TextStyle = Mark.create({
|
|
|
8053
8119
|
if (!hasEmptyTextStyle) return false;
|
|
8054
8120
|
if (dispatch) {
|
|
8055
8121
|
tr.doc.nodesBetween(from, to, (node, pos) => {
|
|
8056
|
-
const
|
|
8057
|
-
if (
|
|
8058
|
-
const hasActiveAttr = Object.values(
|
|
8122
|
+
const mark2 = node.marks.find((m) => m.type === markType);
|
|
8123
|
+
if (mark2) {
|
|
8124
|
+
const hasActiveAttr = Object.values(mark2.attrs).some(
|
|
8059
8125
|
(v) => v !== null && v !== void 0
|
|
8060
8126
|
);
|
|
8061
8127
|
if (!hasActiveAttr) {
|
|
@@ -8560,11 +8626,7 @@ var Typography = Extension.create({
|
|
|
8560
8626
|
rules.push(
|
|
8561
8627
|
new inputrules.InputRule(/"([^"]+)"$/, (state, match, start, end) => {
|
|
8562
8628
|
const text = match[1] ?? "";
|
|
8563
|
-
return state.tr.
|
|
8564
|
-
start,
|
|
8565
|
-
end,
|
|
8566
|
-
state.schema.text(openDoubleQuote + text + closeDoubleQuote)
|
|
8567
|
-
);
|
|
8629
|
+
return state.tr.insertText(openDoubleQuote + text + closeDoubleQuote, start, end);
|
|
8568
8630
|
})
|
|
8569
8631
|
);
|
|
8570
8632
|
rules.push(
|
|
@@ -8572,12 +8634,10 @@ var Typography = Extension.create({
|
|
|
8572
8634
|
const text = match[1] ?? "";
|
|
8573
8635
|
const prefix = match[0].charAt(0);
|
|
8574
8636
|
const hasPrefix = prefix !== "'";
|
|
8575
|
-
return state.tr.
|
|
8637
|
+
return state.tr.insertText(
|
|
8638
|
+
hasPrefix ? prefix + openSingleQuote + text + closeSingleQuote : openSingleQuote + text + closeSingleQuote,
|
|
8576
8639
|
start,
|
|
8577
|
-
end
|
|
8578
|
-
state.schema.text(
|
|
8579
|
-
hasPrefix ? prefix + openSingleQuote + text + closeSingleQuote : openSingleQuote + text + closeSingleQuote
|
|
8580
|
-
)
|
|
8640
|
+
end
|
|
8581
8641
|
);
|
|
8582
8642
|
})
|
|
8583
8643
|
);
|
|
@@ -8842,6 +8902,9 @@ function generateUUID() {
|
|
|
8842
8902
|
});
|
|
8843
8903
|
}
|
|
8844
8904
|
var uniqueIDPluginKey = new state.PluginKey("uniqueID");
|
|
8905
|
+
function isWithin(from, to, ranges) {
|
|
8906
|
+
return ranges.some((range) => from >= range.from && to <= range.to);
|
|
8907
|
+
}
|
|
8845
8908
|
var UniqueID = Extension.create({
|
|
8846
8909
|
name: "uniqueID",
|
|
8847
8910
|
addOptions() {
|
|
@@ -8891,12 +8954,27 @@ var UniqueID = Extension.create({
|
|
|
8891
8954
|
addProseMirrorPlugins() {
|
|
8892
8955
|
const { types, attributeName, generateID, filterDuplicates } = this.options;
|
|
8893
8956
|
const editor = this.editor;
|
|
8957
|
+
const replacedRanges = (view) => {
|
|
8958
|
+
if (view?.dragging) return [];
|
|
8959
|
+
const ranges = view?.state?.selection?.ranges;
|
|
8960
|
+
if (ranges === void 0) return [];
|
|
8961
|
+
const replaced = [];
|
|
8962
|
+
for (const range of ranges) {
|
|
8963
|
+
if (range.$to.pos > range.$from.pos) {
|
|
8964
|
+
replaced.push({ from: range.$from.pos, to: range.$to.pos });
|
|
8965
|
+
}
|
|
8966
|
+
}
|
|
8967
|
+
return replaced;
|
|
8968
|
+
};
|
|
8894
8969
|
const transformPastedSlice = (slice, view) => {
|
|
8895
8970
|
if (view?.dragging?.move === true) return slice;
|
|
8896
8971
|
const existingIDs = /* @__PURE__ */ new Set();
|
|
8897
|
-
|
|
8972
|
+
const replaced = replacedRanges(view);
|
|
8973
|
+
editor?.state.doc.descendants((node, pos) => {
|
|
8974
|
+
if (isWithin(pos, pos + node.nodeSize, replaced)) return false;
|
|
8898
8975
|
const id = node.attrs[attributeName];
|
|
8899
8976
|
if (id) existingIDs.add(id);
|
|
8977
|
+
return true;
|
|
8900
8978
|
});
|
|
8901
8979
|
const transformNode = (node) => {
|
|
8902
8980
|
if (!types.includes(node.type.name)) {
|
|
@@ -9746,13 +9824,13 @@ var Highlight = Extension.create({
|
|
|
9746
9824
|
let hasHighlight = false;
|
|
9747
9825
|
if (empty) {
|
|
9748
9826
|
const marks = state.storedMarks ?? state.doc.resolve(from).marks();
|
|
9749
|
-
const
|
|
9750
|
-
hasHighlight = !!
|
|
9827
|
+
const mark2 = markType.isInSet(marks);
|
|
9828
|
+
hasHighlight = !!mark2?.attrs["backgroundColor"] || !!mark2?.attrs["backgroundColorToken"];
|
|
9751
9829
|
} else {
|
|
9752
9830
|
state.doc.nodesBetween(from, to, (node) => {
|
|
9753
9831
|
if (hasHighlight) return false;
|
|
9754
|
-
const
|
|
9755
|
-
if (
|
|
9832
|
+
const mark2 = markType.isInSet(node.marks);
|
|
9833
|
+
if (mark2?.attrs["backgroundColor"] || mark2?.attrs["backgroundColorToken"]) {
|
|
9756
9834
|
hasHighlight = true;
|
|
9757
9835
|
return false;
|
|
9758
9836
|
}
|
|
@@ -9799,7 +9877,8 @@ var Highlight = Extension.create({
|
|
|
9799
9877
|
const content = match[1];
|
|
9800
9878
|
if (!content) return null;
|
|
9801
9879
|
const { tr } = state;
|
|
9802
|
-
tr.
|
|
9880
|
+
const marks = tr.storedMarks ?? tr.doc.resolve(start).marksAcross(tr.doc.resolve(end));
|
|
9881
|
+
tr.replaceWith(start, end, state.schema.text(content, marks));
|
|
9803
9882
|
tr.addMark(
|
|
9804
9883
|
start,
|
|
9805
9884
|
start + content.length,
|
|
@@ -10095,6 +10174,153 @@ var ClearFormatting = Extension.create({
|
|
|
10095
10174
|
];
|
|
10096
10175
|
}
|
|
10097
10176
|
});
|
|
10177
|
+
|
|
10178
|
+
// src/extensions/Print.ts
|
|
10179
|
+
var ROOT_CLASS = "dm-print-root";
|
|
10180
|
+
var ANCESTOR_CLASS = "dm-print-ancestor";
|
|
10181
|
+
var PRINTING_CLASS = "dm-printing";
|
|
10182
|
+
var marked = /* @__PURE__ */ new Set();
|
|
10183
|
+
var printing = false;
|
|
10184
|
+
var Print = Extension.create({
|
|
10185
|
+
name: "print",
|
|
10186
|
+
addOptions() {
|
|
10187
|
+
return {
|
|
10188
|
+
toolbar: true,
|
|
10189
|
+
root: null,
|
|
10190
|
+
isolateNativePrint: false
|
|
10191
|
+
};
|
|
10192
|
+
},
|
|
10193
|
+
addStorage() {
|
|
10194
|
+
return { cleanup: null };
|
|
10195
|
+
},
|
|
10196
|
+
addCommands() {
|
|
10197
|
+
return {
|
|
10198
|
+
printDocument: () => ({ dispatch }) => {
|
|
10199
|
+
if (!dispatch) return true;
|
|
10200
|
+
const editor = this.editor;
|
|
10201
|
+
if (!editor || typeof window === "undefined") return false;
|
|
10202
|
+
const root = resolveRoot(editor, this.options.root);
|
|
10203
|
+
if (!root) return false;
|
|
10204
|
+
mark(root);
|
|
10205
|
+
try {
|
|
10206
|
+
emit(editor, "beforePrint", { root });
|
|
10207
|
+
window.print();
|
|
10208
|
+
} finally {
|
|
10209
|
+
unmark();
|
|
10210
|
+
emit(editor, "afterPrint", void 0);
|
|
10211
|
+
}
|
|
10212
|
+
return true;
|
|
10213
|
+
}
|
|
10214
|
+
};
|
|
10215
|
+
},
|
|
10216
|
+
addToolbarItems() {
|
|
10217
|
+
if (!this.options.toolbar) return [];
|
|
10218
|
+
return [
|
|
10219
|
+
{
|
|
10220
|
+
type: "button",
|
|
10221
|
+
name: "print",
|
|
10222
|
+
command: "printDocument",
|
|
10223
|
+
icon: "printer",
|
|
10224
|
+
label: "Print",
|
|
10225
|
+
shortcut: "Mod-P",
|
|
10226
|
+
group: "document",
|
|
10227
|
+
priority: 100,
|
|
10228
|
+
// Reading a document out to paper is not editing it, so the button
|
|
10229
|
+
// stays live in a read-only editor.
|
|
10230
|
+
allowReadOnly: true
|
|
10231
|
+
}
|
|
10232
|
+
];
|
|
10233
|
+
},
|
|
10234
|
+
addKeyboardShortcuts() {
|
|
10235
|
+
return {
|
|
10236
|
+
// Only bound while the caret is in the editor, which is exactly when
|
|
10237
|
+
// the reader means "print this document" rather than "print this
|
|
10238
|
+
// page". Everywhere else the browser's own Ctrl/Cmd+P is untouched.
|
|
10239
|
+
"Mod-p": () => this.editor?.commands.printDocument() ?? false
|
|
10240
|
+
};
|
|
10241
|
+
},
|
|
10242
|
+
onCreate() {
|
|
10243
|
+
if (!this.options.isolateNativePrint) return;
|
|
10244
|
+
if (typeof window === "undefined") return;
|
|
10245
|
+
const editor = this.editor;
|
|
10246
|
+
if (!editor) return;
|
|
10247
|
+
const resolve = this.options.root;
|
|
10248
|
+
const before = () => {
|
|
10249
|
+
if (printing) return;
|
|
10250
|
+
const root = resolveRoot(editor, resolve);
|
|
10251
|
+
if (!root) return;
|
|
10252
|
+
mark(root);
|
|
10253
|
+
emit(editor, "beforePrint", { root });
|
|
10254
|
+
};
|
|
10255
|
+
const after = () => {
|
|
10256
|
+
if (!printing) return;
|
|
10257
|
+
unmark();
|
|
10258
|
+
emit(editor, "afterPrint", void 0);
|
|
10259
|
+
};
|
|
10260
|
+
window.addEventListener("beforeprint", before);
|
|
10261
|
+
window.addEventListener("afterprint", after);
|
|
10262
|
+
let detachMedia = null;
|
|
10263
|
+
const onMediaChange = (event) => {
|
|
10264
|
+
if (event.matches) before();
|
|
10265
|
+
else after();
|
|
10266
|
+
};
|
|
10267
|
+
if (typeof window.matchMedia === "function") {
|
|
10268
|
+
const media = window.matchMedia("print");
|
|
10269
|
+
if (typeof media.addEventListener === "function") {
|
|
10270
|
+
media.addEventListener("change", onMediaChange);
|
|
10271
|
+
detachMedia = () => {
|
|
10272
|
+
media.removeEventListener("change", onMediaChange);
|
|
10273
|
+
};
|
|
10274
|
+
}
|
|
10275
|
+
}
|
|
10276
|
+
this.storage.cleanup = () => {
|
|
10277
|
+
window.removeEventListener("beforeprint", before);
|
|
10278
|
+
window.removeEventListener("afterprint", after);
|
|
10279
|
+
detachMedia?.();
|
|
10280
|
+
};
|
|
10281
|
+
},
|
|
10282
|
+
onDestroy() {
|
|
10283
|
+
this.storage.cleanup?.();
|
|
10284
|
+
this.storage.cleanup = null;
|
|
10285
|
+
unmark();
|
|
10286
|
+
}
|
|
10287
|
+
});
|
|
10288
|
+
function resolveRoot(editor, resolve) {
|
|
10289
|
+
if (resolve) return resolve(editor);
|
|
10290
|
+
const dom = editor.view.dom;
|
|
10291
|
+
return dom.closest(".dm-editor") ?? dom;
|
|
10292
|
+
}
|
|
10293
|
+
function mark(root) {
|
|
10294
|
+
root.classList.add(ROOT_CLASS);
|
|
10295
|
+
marked.add(root);
|
|
10296
|
+
let node = parentOf(root);
|
|
10297
|
+
while (node) {
|
|
10298
|
+
node.classList.add(ANCESTOR_CLASS);
|
|
10299
|
+
marked.add(node);
|
|
10300
|
+
node = parentOf(node);
|
|
10301
|
+
}
|
|
10302
|
+
document.body.classList.add(PRINTING_CLASS);
|
|
10303
|
+
printing = true;
|
|
10304
|
+
}
|
|
10305
|
+
function parentOf(node) {
|
|
10306
|
+
if (node.parentElement) return node.parentElement;
|
|
10307
|
+
if (typeof ShadowRoot === "undefined") return null;
|
|
10308
|
+
const root = node.getRootNode();
|
|
10309
|
+
return root instanceof ShadowRoot ? root.host : null;
|
|
10310
|
+
}
|
|
10311
|
+
function unmark() {
|
|
10312
|
+
printing = false;
|
|
10313
|
+
if (typeof document === "undefined") return;
|
|
10314
|
+
document.body.classList.remove(PRINTING_CLASS);
|
|
10315
|
+
for (const el of marked) {
|
|
10316
|
+
el.classList.remove(ROOT_CLASS, ANCESTOR_CLASS);
|
|
10317
|
+
}
|
|
10318
|
+
marked.clear();
|
|
10319
|
+
}
|
|
10320
|
+
function emit(editor, name, payload) {
|
|
10321
|
+
const bus = editor;
|
|
10322
|
+
bus.emit?.(name, payload);
|
|
10323
|
+
}
|
|
10098
10324
|
var linkPopoverPluginKey = new state.PluginKey("linkPopover");
|
|
10099
10325
|
function linkPopoverPlugin({ editor, markType, protocols }) {
|
|
10100
10326
|
const el = document.createElement("div");
|
|
@@ -10432,9 +10658,10 @@ function createBubbleMenuPlugin(options) {
|
|
|
10432
10658
|
const onDocumentMousedown = (e) => {
|
|
10433
10659
|
const target = e.target;
|
|
10434
10660
|
if (!target) return;
|
|
10661
|
+
if (!target.isConnected) return;
|
|
10435
10662
|
if (element.contains(target)) return;
|
|
10436
10663
|
if (editor.view.dom.contains(target)) return;
|
|
10437
|
-
if (target instanceof
|
|
10664
|
+
if (target instanceof Element && target.closest("[data-dm-editor-ui]")) return;
|
|
10438
10665
|
hideMenu();
|
|
10439
10666
|
suppressed = true;
|
|
10440
10667
|
};
|
|
@@ -10668,7 +10895,7 @@ var StarterKit = Extension.create({
|
|
|
10668
10895
|
});
|
|
10669
10896
|
|
|
10670
10897
|
// src/index.ts
|
|
10671
|
-
var VERSION = "0.
|
|
10898
|
+
var VERSION = "0.15.0";
|
|
10672
10899
|
|
|
10673
10900
|
Object.defineProperty(exports, "PluginKey", {
|
|
10674
10901
|
enumerable: true,
|
|
@@ -10726,6 +10953,7 @@ exports.NotionColorPicker = NotionColorPicker;
|
|
|
10726
10953
|
exports.OrderedList = OrderedList;
|
|
10727
10954
|
exports.Paragraph = Paragraph;
|
|
10728
10955
|
exports.Placeholder = Placeholder;
|
|
10956
|
+
exports.Print = Print;
|
|
10729
10957
|
exports.Selection = Selection5;
|
|
10730
10958
|
exports.SelectionDecoration = SelectionDecoration;
|
|
10731
10959
|
exports.StarterKit = StarterKit;
|