edytor 0.0.7 → 0.0.9
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 +225 -5
- package/dist/block/block.svelte.d.ts +123 -0
- package/dist/block/block.svelte.js +430 -0
- package/dist/block/block.utils.d.ts +82 -0
- package/dist/block/block.utils.js +499 -0
- package/dist/block/content.svelte.d.ts +14 -0
- package/dist/block/content.svelte.js +40 -0
- package/dist/block/inlineBlock.svelte.d.ts +30 -0
- package/dist/block/inlineBlock.svelte.js +53 -0
- package/dist/components/Block.svelte +27 -0
- package/dist/components/Block.svelte.d.ts +7 -0
- package/dist/components/Content.svelte +44 -0
- package/dist/components/Content.svelte.d.ts +7 -0
- package/dist/components/Edytor.svelte +165 -0
- package/dist/components/Edytor.svelte.d.ts +33 -0
- package/dist/components/InlineBlock.svelte +17 -0
- package/dist/components/InlineBlock.svelte.d.ts +7 -0
- package/dist/components/Mark.svelte +38 -0
- package/dist/components/Mark.svelte.d.ts +11 -0
- package/dist/components/ReadonlyEditor.svelte +46 -0
- package/dist/components/ReadonlyEditor.svelte.d.ts +4 -0
- package/dist/components/Text.svelte +37 -0
- package/dist/components/Text.svelte.d.ts +7 -0
- package/dist/components/readonlyElements.svelte.d.ts +82 -0
- package/dist/components/readonlyElements.svelte.js +144 -0
- package/dist/constants.d.ts +4 -0
- package/dist/constants.js +4 -0
- package/dist/dnd.svelte.d.ts +17 -0
- package/dist/dnd.svelte.js +125 -0
- package/dist/edytor.svelte.d.ts +94 -0
- package/dist/edytor.svelte.js +262 -0
- package/dist/edytor.utils.d.ts +3 -0
- package/dist/edytor.utils.js +78 -0
- package/dist/events/onBeforeInput.d.ts +2 -2
- package/dist/events/onBeforeInput.js +338 -0
- package/dist/events/onKeyDown.d.ts +2 -69
- package/dist/events/onKeyDown.js +18 -0
- package/dist/hotkeys.d.ts +27 -0
- package/dist/hotkeys.js +282 -0
- package/dist/index.d.ts +7 -5
- package/dist/index.js +7 -8
- package/dist/localProvider.d.ts +82 -0
- package/dist/localProvider.js +267 -0
- package/dist/plugins/arrowMove/arrowMove.d.ts +2 -0
- package/dist/plugins/arrowMove/arrowMove.js +66 -0
- package/dist/plugins/code/CodePlugin.svelte +218 -0
- package/dist/plugins/code/CodePlugin.svelte.d.ts +23 -0
- package/dist/plugins/code/prism.css +141 -0
- package/dist/plugins/image/ImagePlugin.svelte +22 -0
- package/dist/plugins/image/ImagePlugin.svelte.d.ts +20 -0
- package/dist/plugins/index.d.ts +3 -0
- package/dist/plugins/index.js +3 -0
- package/dist/plugins/mention/MentionPlugin.svelte +40 -0
- package/dist/plugins/mention/MentionPlugin.svelte.d.ts +20 -0
- package/dist/plugins/richtext/RichTextPlugin.svelte +241 -0
- package/dist/plugins/richtext/RichTextPlugin.svelte.d.ts +26 -0
- package/dist/plugins.d.ts +202 -0
- package/dist/plugins.js +19 -0
- package/dist/selection/selection.svelte.d.ts +68 -0
- package/dist/selection/selection.svelte.js +391 -0
- package/dist/selection/selection.utils.d.ts +14 -0
- package/dist/selection/selection.utils.js +153 -0
- package/dist/text/deltas.d.ts +11 -0
- package/dist/text/deltas.js +109 -0
- package/dist/text/text.svelte.d.ts +70 -0
- package/dist/text/text.svelte.js +119 -0
- package/dist/text/text.utils.d.ts +40 -0
- package/dist/text/text.utils.js +174 -0
- package/dist/utils/json.d.ts +32 -0
- package/dist/utils/json.js +2 -0
- package/dist/utils/jsx.d.ts +25 -0
- package/dist/utils/jsx.js +66 -0
- package/dist/utils/serialize.d.ts +1 -0
- package/dist/utils/serialize.js +1 -0
- package/dist/utils.d.ts +6 -0
- package/dist/utils.js +19 -0
- package/package.json +81 -33
- package/dist/edytor.cjs.development.js +0 -1762
- package/dist/edytor.cjs.development.js.map +0 -1
- package/dist/edytor.cjs.production.min.js +0 -2
- package/dist/edytor.cjs.production.min.js.map +0 -1
- package/dist/edytor.esm.js +0 -1691
- package/dist/edytor.esm.js.map +0 -1
- package/dist/events/index.d.ts +0 -3
- package/dist/events/onDrop.d.ts +0 -3
- package/dist/operations/deleteText.d.ts +0 -7
- package/dist/operations/formatText.d.ts +0 -20
- package/dist/operations/index.d.ts +0 -9
- package/dist/operations/insertText.d.ts +0 -5
- package/dist/operations/merge.d.ts +0 -3
- package/dist/operations/moveNode.d.ts +0 -12
- package/dist/operations/nestNode.d.ts +0 -2
- package/dist/operations/removeEmptyText.d.ts +0 -2
- package/dist/operations/splitLeaf.d.ts +0 -2
- package/dist/operations/splitNode.d.ts +0 -9
- package/dist/types.d.ts +0 -140
- package/dist/utils/common.d.ts +0 -17
- package/dist/utils/dropper.d.ts +0 -26
- package/dist/utils/index.d.ts +0 -6
- package/dist/utils/leaves.d.ts +0 -34
- package/dist/utils/nodes.d.ts +0 -14
- package/dist/utils/selection.d.ts +0 -51
- package/dist/utils/yClasses/EdytorDoc.d.ts +0 -16
- package/dist/utils/yClasses/index.d.ts +0 -3
- package/dist/utils/yClasses/yLeaf.d.ts +0 -7
- package/dist/utils/yClasses/yNode.d.ts +0 -12
- package/dist/yjs/index.d.ts +0 -1
- package/dist/yjs/wsProvider.d.ts +0 -1
- package/index.ts +0 -1
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { triggerPostMoveFlash } from '@atlaskit/pragmatic-drag-and-drop-flourish/trigger-post-move-flash';
|
|
2
|
+
import * as liveRegion from '@atlaskit/pragmatic-drag-and-drop-live-region';
|
|
3
|
+
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
|
|
4
|
+
import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
|
5
|
+
import { attachInstruction, extractInstruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
|
|
6
|
+
export class DnD {
|
|
7
|
+
state = {
|
|
8
|
+
source: null,
|
|
9
|
+
target: null,
|
|
10
|
+
position: null
|
|
11
|
+
};
|
|
12
|
+
options;
|
|
13
|
+
uniqueContextId = Symbol('dnd-context');
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.options = options;
|
|
16
|
+
}
|
|
17
|
+
calculateDropPosition(element, mouseY) {
|
|
18
|
+
const rect = element.getBoundingClientRect();
|
|
19
|
+
const relativeY = mouseY - rect.top;
|
|
20
|
+
if (relativeY < rect.height * 0.25) {
|
|
21
|
+
return 'before';
|
|
22
|
+
}
|
|
23
|
+
else if (relativeY > rect.height * 0.75) {
|
|
24
|
+
return 'after';
|
|
25
|
+
}
|
|
26
|
+
return 'inside';
|
|
27
|
+
}
|
|
28
|
+
updateDropIndicator(element, position) {
|
|
29
|
+
// Remove existing indicators
|
|
30
|
+
document.querySelectorAll('.drop-indicator').forEach((el) => {
|
|
31
|
+
if (el instanceof HTMLElement) {
|
|
32
|
+
el.remove();
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
const indicator = document.createElement('div');
|
|
36
|
+
indicator.className = 'drop-indicator';
|
|
37
|
+
switch (position) {
|
|
38
|
+
case 'before':
|
|
39
|
+
indicator.style.cssText = `
|
|
40
|
+
position: absolute;
|
|
41
|
+
left: 0;
|
|
42
|
+
right: 0;
|
|
43
|
+
top: -2px;
|
|
44
|
+
height: 4px;
|
|
45
|
+
background: #0066ff;
|
|
46
|
+
`;
|
|
47
|
+
break;
|
|
48
|
+
case 'after':
|
|
49
|
+
indicator.style.cssText = `
|
|
50
|
+
position: absolute;
|
|
51
|
+
left: 0;
|
|
52
|
+
right: 0;
|
|
53
|
+
bottom: -2px;
|
|
54
|
+
height: 4px;
|
|
55
|
+
background: #0066ff;
|
|
56
|
+
`;
|
|
57
|
+
break;
|
|
58
|
+
case 'inside':
|
|
59
|
+
indicator.style.cssText = `
|
|
60
|
+
position: absolute;
|
|
61
|
+
left: 0;
|
|
62
|
+
right: 0;
|
|
63
|
+
top: 0;
|
|
64
|
+
bottom: 0;
|
|
65
|
+
border: 2px solid #0066ff;
|
|
66
|
+
pointer-events: none;
|
|
67
|
+
`;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
element.appendChild(indicator);
|
|
71
|
+
}
|
|
72
|
+
// Svelte action that handles both dragging and dropping
|
|
73
|
+
attach = (element, block) => {
|
|
74
|
+
// Make element draggable
|
|
75
|
+
element.draggable = true;
|
|
76
|
+
const cleanup = combine(monitorForElements({
|
|
77
|
+
canMonitor: (args) => {
|
|
78
|
+
const data = args.source.data;
|
|
79
|
+
return data.uniqueContextId === this.uniqueContextId;
|
|
80
|
+
},
|
|
81
|
+
onDragStart: (args) => {
|
|
82
|
+
this.state.source = block;
|
|
83
|
+
args.source.data = {
|
|
84
|
+
uniqueContextId: this.uniqueContextId,
|
|
85
|
+
id: block.id,
|
|
86
|
+
type: 'block',
|
|
87
|
+
block
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
onDrop: (args) => {
|
|
91
|
+
// didn't drop on anything
|
|
92
|
+
if (!args.location.current.dropTargets.length) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const target = args.location.current.dropTargets[0];
|
|
96
|
+
const targetData = target.data;
|
|
97
|
+
const targetBlock = targetData.block;
|
|
98
|
+
const position = this.calculateDropPosition(target.element, args.location.current.input.clientY);
|
|
99
|
+
// Check if drop is allowed
|
|
100
|
+
if (this.options.canDrop && !this.options.canDrop(this.state.source, targetBlock)) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
this.options.onDrop?.(this.state.source, targetBlock, position);
|
|
104
|
+
triggerPostMoveFlash(target.element);
|
|
105
|
+
liveRegion.announce(`Moved ${block.type} ${position} ${targetBlock.type}`);
|
|
106
|
+
// Reset state
|
|
107
|
+
this.state = {
|
|
108
|
+
source: null,
|
|
109
|
+
target: null,
|
|
110
|
+
position: null
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}));
|
|
114
|
+
return {
|
|
115
|
+
destroy() {
|
|
116
|
+
cleanup();
|
|
117
|
+
document.querySelectorAll('.drop-indicator').forEach((el) => {
|
|
118
|
+
if (el instanceof HTMLElement) {
|
|
119
|
+
el.remove();
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { type Snippet } from 'svelte';
|
|
2
|
+
import * as Y from 'yjs';
|
|
3
|
+
import { type JSONBlock, type JSONDoc, type YBlock, type SerializableContent } from './utils/json.js';
|
|
4
|
+
import { EdytorSelection } from './selection/selection.svelte.js';
|
|
5
|
+
import { Block } from './block/block.svelte.js';
|
|
6
|
+
import { Text } from './text/text.svelte.js';
|
|
7
|
+
import { SvelteMap } from 'svelte/reactivity';
|
|
8
|
+
import { Awareness } from 'y-protocols/awareness.js';
|
|
9
|
+
import { batch } from './block/block.utils.js';
|
|
10
|
+
import type { Plugin, InitializedPlugin, BlockDefinition, MarkDefinition, InlineBlockDefinition } from './plugins.js';
|
|
11
|
+
import { HotKeys, type HotKey } from './hotkeys.js';
|
|
12
|
+
import { TRANSACTION } from './constants.js';
|
|
13
|
+
import type { InlineBlock } from './block/inlineBlock.svelte.js';
|
|
14
|
+
export type Snippets = {
|
|
15
|
+
[K in `${string}Block` | `${string}Mark`]: K extends `${string}Block` ? Snippet<[
|
|
16
|
+
{
|
|
17
|
+
block: Block;
|
|
18
|
+
children: Snippet | null;
|
|
19
|
+
content: Snippet;
|
|
20
|
+
}
|
|
21
|
+
]> : Snippet<[
|
|
22
|
+
{
|
|
23
|
+
mark: SerializableContent;
|
|
24
|
+
text: Text;
|
|
25
|
+
content: Snippet;
|
|
26
|
+
}
|
|
27
|
+
]>;
|
|
28
|
+
};
|
|
29
|
+
export type EdytorOptions = {
|
|
30
|
+
readonly?: boolean;
|
|
31
|
+
snippets?: Snippets;
|
|
32
|
+
hotKeys?: Record<string, HotKey>;
|
|
33
|
+
plugins?: Plugin[];
|
|
34
|
+
doc?: Y.Doc;
|
|
35
|
+
awareness?: Awareness;
|
|
36
|
+
sync?: boolean;
|
|
37
|
+
value?: JSONDoc;
|
|
38
|
+
onChange?: (value: JSONBlock) => void;
|
|
39
|
+
onSelectionChange?: (selection: EdytorSelection) => void;
|
|
40
|
+
placeholder?: string | Snippet<[{
|
|
41
|
+
block: Block;
|
|
42
|
+
}]>;
|
|
43
|
+
};
|
|
44
|
+
export type RootBlock = Block & {
|
|
45
|
+
readonly type: 'root';
|
|
46
|
+
readonly id: 'root';
|
|
47
|
+
readonly depth: 0;
|
|
48
|
+
};
|
|
49
|
+
export declare class Edytor {
|
|
50
|
+
node?: HTMLElement;
|
|
51
|
+
marks: Map<string, MarkDefinition>;
|
|
52
|
+
blocks: Map<string, BlockDefinition>;
|
|
53
|
+
inlineBlocks: Map<string, InlineBlockDefinition>;
|
|
54
|
+
plugins: InitializedPlugin[];
|
|
55
|
+
container: HTMLDivElement | undefined;
|
|
56
|
+
idToBlock: SvelteMap<string, Block>;
|
|
57
|
+
idToInlineBlock: SvelteMap<string, InlineBlock>;
|
|
58
|
+
idToText: SvelteMap<string, Text>;
|
|
59
|
+
nodeToText: SvelteMap<Node, Text>;
|
|
60
|
+
transaction: TRANSACTION;
|
|
61
|
+
hotKeys: HotKeys;
|
|
62
|
+
initialized: boolean;
|
|
63
|
+
readonly: boolean;
|
|
64
|
+
yRootBlock: YBlock;
|
|
65
|
+
root: Block | undefined;
|
|
66
|
+
synced: boolean;
|
|
67
|
+
edytor: this;
|
|
68
|
+
selection: EdytorSelection;
|
|
69
|
+
defaultType: string;
|
|
70
|
+
private off;
|
|
71
|
+
private onChange?;
|
|
72
|
+
placeholder?: string | Snippet<[{
|
|
73
|
+
block: Block;
|
|
74
|
+
}]>;
|
|
75
|
+
doc: Y.Doc;
|
|
76
|
+
undoManager: Y.UndoManager;
|
|
77
|
+
awareness: Awareness;
|
|
78
|
+
transact: <T>(cb: () => T) => T;
|
|
79
|
+
constructor({ snippets, readonly, hotKeys, plugins, doc, awareness, sync, value, onSelectionChange, placeholder, onChange }: EdytorOptions);
|
|
80
|
+
getBlockDefinition: <M extends "inline" | "block">(mode: M, type: string) => M extends "block" ? BlockDefinition : InlineBlockDefinition;
|
|
81
|
+
get value(): JSONBlock;
|
|
82
|
+
getDefaultBlock: (parent?: Block | Edytor | undefined) => string;
|
|
83
|
+
sync: ({ children }?: JSONDoc) => void;
|
|
84
|
+
onBeforeInput: (e: InputEvent) => Promise<void>;
|
|
85
|
+
batch: typeof batch;
|
|
86
|
+
deleteContentWithinSelection: (args_0: {}) => readonly [Text | null, number];
|
|
87
|
+
getTextById: (id: string) => Text | undefined;
|
|
88
|
+
getBlockByIdOrContent: (idOrContent: string | Text) => Block | undefined;
|
|
89
|
+
getTextNode: (idOrText: string | Text) => Promise<HTMLElement>;
|
|
90
|
+
attach: (node: HTMLDivElement) => {
|
|
91
|
+
destroy: () => void;
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export declare const useEdytor: () => Edytor;
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { getContext, hasContext, setContext, tick, onMount } from 'svelte';
|
|
2
|
+
import * as Y from 'yjs';
|
|
3
|
+
import { onBeforeInput } from './events/onBeforeInput.js';
|
|
4
|
+
import {} from './utils/json.js';
|
|
5
|
+
import { onKeyDown } from './events/onKeyDown.js';
|
|
6
|
+
import { EdytorSelection } from './selection/selection.svelte.js';
|
|
7
|
+
import { Block } from './block/block.svelte.js';
|
|
8
|
+
import { Text } from './text/text.svelte.js';
|
|
9
|
+
import { SvelteMap } from 'svelte/reactivity';
|
|
10
|
+
import { Awareness } from 'y-protocols/awareness.js';
|
|
11
|
+
import { batch } from './block/block.utils.js';
|
|
12
|
+
import { on } from 'svelte/events';
|
|
13
|
+
import { HotKeys } from './hotkeys.js';
|
|
14
|
+
import { TRANSACTION } from './constants.js';
|
|
15
|
+
import { deleteContentWithinSelection } from './edytor.utils.js';
|
|
16
|
+
export class Edytor {
|
|
17
|
+
node;
|
|
18
|
+
marks = new Map();
|
|
19
|
+
blocks = new Map();
|
|
20
|
+
inlineBlocks = new Map();
|
|
21
|
+
plugins;
|
|
22
|
+
container = $state();
|
|
23
|
+
idToBlock = new SvelteMap();
|
|
24
|
+
idToInlineBlock = new SvelteMap();
|
|
25
|
+
idToText = new SvelteMap();
|
|
26
|
+
nodeToText = new SvelteMap();
|
|
27
|
+
transaction = new TRANSACTION();
|
|
28
|
+
hotKeys;
|
|
29
|
+
initialized = $state(false);
|
|
30
|
+
readonly = $state(false);
|
|
31
|
+
yRootBlock;
|
|
32
|
+
root = $state();
|
|
33
|
+
synced = $state(false);
|
|
34
|
+
edytor = this;
|
|
35
|
+
selection;
|
|
36
|
+
defaultType = 'paragraph';
|
|
37
|
+
off = [];
|
|
38
|
+
onChange;
|
|
39
|
+
placeholder;
|
|
40
|
+
// YJS Stuff
|
|
41
|
+
doc = new Y.Doc();
|
|
42
|
+
undoManager;
|
|
43
|
+
awareness;
|
|
44
|
+
transact = (cb) => {
|
|
45
|
+
return this.doc.transact(() => {
|
|
46
|
+
const result = cb();
|
|
47
|
+
return result;
|
|
48
|
+
}, this.transaction);
|
|
49
|
+
};
|
|
50
|
+
constructor({ snippets, readonly, hotKeys, plugins, doc = this.doc, awareness = new Awareness(doc), sync, value, onSelectionChange, placeholder, onChange }) {
|
|
51
|
+
this.readonly = readonly || false;
|
|
52
|
+
this.doc = doc;
|
|
53
|
+
this.yRootBlock = this.doc.getMap('content');
|
|
54
|
+
this.awareness = awareness;
|
|
55
|
+
this.onChange = onChange;
|
|
56
|
+
// Initialize plugins
|
|
57
|
+
this.plugins = (plugins || []).map((plugin) => {
|
|
58
|
+
const initializedPlugin = plugin(this);
|
|
59
|
+
initializedPlugin.marks &&
|
|
60
|
+
Object.entries(initializedPlugin.marks).forEach(([key, snippet]) => {
|
|
61
|
+
if (typeof snippet === 'object') {
|
|
62
|
+
this.marks.set(key, snippet);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
this.marks.set(key, { snippet });
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
initializedPlugin.blocks &&
|
|
69
|
+
Object.entries(initializedPlugin.blocks).forEach(([key, definition]) => {
|
|
70
|
+
if (typeof definition === 'object') {
|
|
71
|
+
this.blocks.set(key, definition);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
this.blocks.set(key, { snippet: definition });
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
initializedPlugin.inlineBlocks &&
|
|
78
|
+
Object.entries(initializedPlugin.inlineBlocks).forEach(([key, definition]) => {
|
|
79
|
+
if (typeof definition === 'object') {
|
|
80
|
+
this.inlineBlocks.set(key, definition);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
this.inlineBlocks.set(key, { snippet: definition });
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
return initializedPlugin;
|
|
87
|
+
});
|
|
88
|
+
// We set the custom snippets after plugins are initialized in order to be able to override the plugins snippets
|
|
89
|
+
Object.entries(snippets || {}).forEach(([key, snippet]) => {
|
|
90
|
+
const isMark = key.endsWith('Mark');
|
|
91
|
+
const isInlineBlock = key.endsWith('InlineBlock');
|
|
92
|
+
const isBlock = key.endsWith('Block');
|
|
93
|
+
if (isMark) {
|
|
94
|
+
this.marks.set(key, { snippet: snippet });
|
|
95
|
+
}
|
|
96
|
+
else if (isInlineBlock) {
|
|
97
|
+
// this.inlineBlocks.set(key, { snippet: snippet as Snippet<[InlineBlockSnippetPayload]> });
|
|
98
|
+
}
|
|
99
|
+
else if (isBlock) {
|
|
100
|
+
this.blocks.set(key, { snippet: snippet });
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
this.placeholder =
|
|
104
|
+
placeholder || this.plugins.find((plugin) => plugin.placeholder)?.placeholder;
|
|
105
|
+
if (readonly || !sync) {
|
|
106
|
+
this.sync(value || { children: [] });
|
|
107
|
+
}
|
|
108
|
+
this.selection = new EdytorSelection(this, onSelectionChange);
|
|
109
|
+
this.hotKeys = new HotKeys(this, hotKeys, this.plugins);
|
|
110
|
+
this.undoManager = new Y.UndoManager(this.yRootBlock, {
|
|
111
|
+
trackedOrigins: new Set([this.transaction, null])
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
getBlockDefinition = (mode, type) => {
|
|
115
|
+
const definition = mode === 'block' ? this.blocks.get(type) : this.inlineBlocks.get(type);
|
|
116
|
+
if (type === 'root') {
|
|
117
|
+
return {};
|
|
118
|
+
}
|
|
119
|
+
if (!definition) {
|
|
120
|
+
throw new Error(`Block type ${type} is not defined`);
|
|
121
|
+
}
|
|
122
|
+
return definition;
|
|
123
|
+
};
|
|
124
|
+
get value() {
|
|
125
|
+
return {
|
|
126
|
+
type: 'root',
|
|
127
|
+
children: this.root?.value.children || []
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
getDefaultBlock = (parent = this.selection.state.startText?.parent) => {
|
|
131
|
+
if (!parent || parent instanceof Edytor) {
|
|
132
|
+
return 'paragraph';
|
|
133
|
+
}
|
|
134
|
+
for (const plugin of this.plugins) {
|
|
135
|
+
if (plugin.defaultBlock) {
|
|
136
|
+
const defaultBlock = typeof plugin.defaultBlock === 'function'
|
|
137
|
+
? plugin.defaultBlock(parent)
|
|
138
|
+
: plugin.defaultBlock;
|
|
139
|
+
if (defaultBlock) {
|
|
140
|
+
return defaultBlock;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return 'paragraph';
|
|
145
|
+
};
|
|
146
|
+
sync = ({ children = [] } = { children: [] }) => {
|
|
147
|
+
if (this.synced) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (this.readonly) {
|
|
151
|
+
// children.forEach((child) => {
|
|
152
|
+
// const block = new Block({ parent: this.root, block: child });
|
|
153
|
+
// this.root.yChildren.push([block.yBlock]);
|
|
154
|
+
// });
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
const INITIALIZED = 'INITIALIZED';
|
|
158
|
+
const initializedText = this.doc.getText(INITIALIZED);
|
|
159
|
+
this.initialized = initializedText?.toJSON() === INITIALIZED;
|
|
160
|
+
if (this.initialized) {
|
|
161
|
+
// this.yRootBlock =
|
|
162
|
+
// Root block already exists and has its children
|
|
163
|
+
this.root = new Block({
|
|
164
|
+
edytor: this,
|
|
165
|
+
yBlock: this.yRootBlock,
|
|
166
|
+
block: {
|
|
167
|
+
type: 'root',
|
|
168
|
+
id: 'root',
|
|
169
|
+
children
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
this.root = new Block({
|
|
175
|
+
edytor: this,
|
|
176
|
+
yBlock: this.yRootBlock,
|
|
177
|
+
block: {
|
|
178
|
+
type: 'root',
|
|
179
|
+
id: 'root',
|
|
180
|
+
children
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
initializedText.delete(0, initializedText.length);
|
|
185
|
+
initializedText.insert(0, INITIALIZED);
|
|
186
|
+
}
|
|
187
|
+
this.synced = true;
|
|
188
|
+
this.doc.on('update', () => {
|
|
189
|
+
const value = this.value;
|
|
190
|
+
this.onChange?.(value);
|
|
191
|
+
this.plugins.forEach((plugin) => {
|
|
192
|
+
plugin.onChange?.(value);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
onBeforeInput = onBeforeInput.bind(this);
|
|
197
|
+
batch = batch.bind(this);
|
|
198
|
+
deleteContentWithinSelection = this.batch('deleteContentWithinSelection', deleteContentWithinSelection.bind(this));
|
|
199
|
+
getTextById = (id) => {
|
|
200
|
+
const isText = id.startsWith('t');
|
|
201
|
+
if (!isText) {
|
|
202
|
+
throw new Error('Invalid id, expected text id');
|
|
203
|
+
}
|
|
204
|
+
return this.idToText.get(id);
|
|
205
|
+
};
|
|
206
|
+
getBlockByIdOrContent = (idOrContent) => {
|
|
207
|
+
if (typeof idOrContent === 'string') {
|
|
208
|
+
const isText = idOrContent.startsWith('t');
|
|
209
|
+
const isBlock = idOrContent.startsWith('b');
|
|
210
|
+
if (isBlock) {
|
|
211
|
+
return this.idToBlock.get(idOrContent);
|
|
212
|
+
}
|
|
213
|
+
else if (isText) {
|
|
214
|
+
const text = this.idToText.get(idOrContent);
|
|
215
|
+
return text?.parent;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
else if (idOrContent instanceof Text) {
|
|
219
|
+
return idOrContent.parent || undefined;
|
|
220
|
+
}
|
|
221
|
+
return undefined;
|
|
222
|
+
};
|
|
223
|
+
getTextNode = async (idOrText) => {
|
|
224
|
+
await tick();
|
|
225
|
+
const text = idOrText instanceof Text ? idOrText : this.getTextById(idOrText);
|
|
226
|
+
let node = text?.node;
|
|
227
|
+
let breakCount = 0;
|
|
228
|
+
while (!node) {
|
|
229
|
+
await tick();
|
|
230
|
+
node = text?.node;
|
|
231
|
+
breakCount++;
|
|
232
|
+
if (breakCount > 10) {
|
|
233
|
+
throw new Error('Failed to find text node');
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return node;
|
|
237
|
+
};
|
|
238
|
+
attach = (node) => {
|
|
239
|
+
this.node = node;
|
|
240
|
+
this.container = node;
|
|
241
|
+
this.selection.init();
|
|
242
|
+
this.hotKeys.init();
|
|
243
|
+
this.off.push(on(document, 'keydown', onKeyDown.bind(this)), on(node, 'click', this.selection.handleTripleClick), on(node, 'beforeinput', this.onBeforeInput));
|
|
244
|
+
this.plugins.forEach((plugin) => {
|
|
245
|
+
const action = plugin.onEdytorAttached?.({ node });
|
|
246
|
+
action && this.off.push(action);
|
|
247
|
+
});
|
|
248
|
+
return {
|
|
249
|
+
destroy: () => {
|
|
250
|
+
this.selection.destroy();
|
|
251
|
+
this.off.forEach((off) => off());
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
export const useEdytor = () => {
|
|
257
|
+
const hasEdytorContext = hasContext('edytor');
|
|
258
|
+
if (hasEdytorContext) {
|
|
259
|
+
return getContext('edytor');
|
|
260
|
+
}
|
|
261
|
+
throw new Error('No Edytor found');
|
|
262
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Block } from './block/block.svelte.js';
|
|
2
|
+
import { Text } from './text/text.svelte.js';
|
|
3
|
+
import { deltaToJson, toDeltas } from './text/deltas.js';
|
|
4
|
+
import { InlineBlock } from './block/inlineBlock.svelte.js';
|
|
5
|
+
export function deleteContentWithinSelection({}) {
|
|
6
|
+
const { startBlock, startText, endBlock, yStart, yEnd, isAtStartOfBlock, isAtEndOfBlock, endText } = this.selection.state;
|
|
7
|
+
const blocksToDelete = this.selection.state.blocks.filter((block, index) => {
|
|
8
|
+
const isFirst = index === 0;
|
|
9
|
+
const isLast = index === this.selection.state.blocks.length - 1;
|
|
10
|
+
if (isFirst) {
|
|
11
|
+
return isAtStartOfBlock;
|
|
12
|
+
}
|
|
13
|
+
else if (isLast) {
|
|
14
|
+
return isAtEndOfBlock;
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
const firstRange = startText && startBlock
|
|
21
|
+
? {
|
|
22
|
+
start: [startText.index, yStart],
|
|
23
|
+
end: [startBlock.lastText.index, startBlock.lastText.length]
|
|
24
|
+
}
|
|
25
|
+
: null;
|
|
26
|
+
if (startBlock !== blocksToDelete[0] && firstRange) {
|
|
27
|
+
// Delete the text from the yStart
|
|
28
|
+
startBlock?.deleteContentAtRange(firstRange);
|
|
29
|
+
}
|
|
30
|
+
// We need to find the content that will be merged into the first block
|
|
31
|
+
if (endBlock && endText && endBlock !== blocksToDelete[blocksToDelete.length - 1]) {
|
|
32
|
+
const contentToMerge = endBlock.content
|
|
33
|
+
.slice(endText.index, endBlock?.content.length)
|
|
34
|
+
.map((part, index) => {
|
|
35
|
+
const isFirstPart = index === 0;
|
|
36
|
+
if (part instanceof Text && isFirstPart) {
|
|
37
|
+
// Delete from offset to end
|
|
38
|
+
part.yText.delete(0, yEnd);
|
|
39
|
+
const deltas = deltaToJson(toDeltas(part.yText)[0]);
|
|
40
|
+
return deltas;
|
|
41
|
+
}
|
|
42
|
+
return part.value;
|
|
43
|
+
});
|
|
44
|
+
const value = contentToMerge.map((part) => {
|
|
45
|
+
if ('type' in part) {
|
|
46
|
+
const block = new InlineBlock({
|
|
47
|
+
parent: startBlock,
|
|
48
|
+
block: part
|
|
49
|
+
});
|
|
50
|
+
return block.yBlock;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const text = new Text({
|
|
54
|
+
parent: startBlock,
|
|
55
|
+
content: part
|
|
56
|
+
});
|
|
57
|
+
return text.yText;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
startBlock?.yContent.push(value);
|
|
61
|
+
}
|
|
62
|
+
blocksToDelete.forEach((block, index) => {
|
|
63
|
+
block.removeBlock();
|
|
64
|
+
});
|
|
65
|
+
if (endBlock?.hasChildren) {
|
|
66
|
+
const newBlocks = endBlock.children.map((child) => {
|
|
67
|
+
const newBlock = new Block({
|
|
68
|
+
parent: startBlock.parent,
|
|
69
|
+
edytor: this,
|
|
70
|
+
block: child.value
|
|
71
|
+
});
|
|
72
|
+
return newBlock.yBlock;
|
|
73
|
+
});
|
|
74
|
+
startBlock?.parent?.yChildren.insert(startBlock.index + 1, newBlocks);
|
|
75
|
+
}
|
|
76
|
+
endBlock?.removeBlock();
|
|
77
|
+
return [startText, yStart];
|
|
78
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare
|
|
1
|
+
import type { Edytor } from '../edytor.svelte.js';
|
|
2
|
+
export declare function onBeforeInput(this: Edytor, e: InputEvent): Promise<void>;
|