@kerebron/extension-basic-editor 0.4.27 → 0.4.29
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/esm/ExtensionBaseKeymap.d.ts +7 -0
- package/esm/ExtensionBaseKeymap.d.ts.map +1 -0
- package/esm/ExtensionBaseKeymap.js +56 -0
- package/esm/ExtensionBaseKeymap.js.map +1 -0
- package/esm/ExtensionBasicCodeEditor.d.ts +11 -0
- package/esm/ExtensionBasicCodeEditor.d.ts.map +1 -0
- package/esm/ExtensionBasicCodeEditor.js +62 -0
- package/esm/ExtensionBasicCodeEditor.js.map +1 -0
- package/esm/ExtensionBasicEditor.d.ts +49 -0
- package/esm/ExtensionBasicEditor.d.ts.map +1 -0
- package/esm/ExtensionBasicEditor.js +95 -0
- package/esm/ExtensionBasicEditor.js.map +1 -0
- package/esm/ExtensionDropcursor.d.ts +19 -0
- package/esm/ExtensionDropcursor.d.ts.map +1 -0
- package/esm/ExtensionDropcursor.js +187 -0
- package/esm/ExtensionDropcursor.js.map +1 -0
- package/esm/ExtensionGapcursor.d.ts +32 -0
- package/esm/ExtensionGapcursor.d.ts.map +1 -0
- package/esm/ExtensionGapcursor.js +250 -0
- package/esm/ExtensionGapcursor.js.map +1 -0
- package/esm/ExtensionHistory.d.ts +14 -0
- package/esm/ExtensionHistory.d.ts.map +1 -0
- package/esm/ExtensionHistory.js +35 -0
- package/esm/ExtensionHistory.js.map +1 -0
- package/esm/ExtensionHtml.d.ts +15 -0
- package/esm/ExtensionHtml.d.ts.map +1 -0
- package/esm/ExtensionHtml.js +100 -0
- package/esm/ExtensionHtml.js.map +1 -0
- package/esm/ExtensionMediaUpload.d.ts +24 -0
- package/esm/ExtensionMediaUpload.d.ts.map +1 -0
- package/esm/ExtensionMediaUpload.js +168 -0
- package/esm/ExtensionMediaUpload.js.map +1 -0
- package/esm/ExtensionSelection.d.ts +11 -0
- package/esm/ExtensionSelection.d.ts.map +1 -0
- package/esm/ExtensionSelection.js +230 -0
- package/esm/ExtensionSelection.js.map +1 -0
- package/esm/ExtensionTextAlign.d.ts +11 -0
- package/esm/ExtensionTextAlign.d.ts.map +1 -0
- package/esm/ExtensionTextAlign.js +40 -0
- package/esm/ExtensionTextAlign.js.map +1 -0
- package/esm/MarkBookmark.d.ts +8 -0
- package/esm/MarkBookmark.d.ts.map +1 -0
- package/esm/MarkBookmark.js +17 -0
- package/esm/MarkBookmark.js.map +1 -0
- package/esm/MarkChange.d.ts +8 -0
- package/esm/MarkChange.d.ts.map +1 -0
- package/esm/MarkChange.js +14 -0
- package/esm/MarkChange.js.map +1 -0
- package/esm/MarkCode.d.ts +11 -0
- package/esm/MarkCode.d.ts.map +1 -0
- package/esm/MarkCode.js +24 -0
- package/esm/MarkCode.js.map +1 -0
- package/esm/MarkHighlight.d.ts +8 -0
- package/esm/MarkHighlight.d.ts.map +1 -0
- package/esm/MarkHighlight.js +36 -0
- package/esm/MarkHighlight.js.map +1 -0
- package/esm/MarkItalic.d.ts +11 -0
- package/esm/MarkItalic.d.ts.map +1 -0
- package/esm/MarkItalic.js +30 -0
- package/esm/MarkItalic.js.map +1 -0
- package/esm/MarkLink.d.ts +8 -0
- package/esm/MarkLink.d.ts.map +1 -0
- package/esm/MarkLink.js +30 -0
- package/esm/MarkLink.js.map +1 -0
- package/esm/MarkStrike.d.ts +11 -0
- package/esm/MarkStrike.d.ts.map +1 -0
- package/esm/MarkStrike.js +27 -0
- package/esm/MarkStrike.js.map +1 -0
- package/esm/MarkStrong.d.ts +11 -0
- package/esm/MarkStrong.d.ts.map +1 -0
- package/esm/MarkStrong.js +39 -0
- package/esm/MarkStrong.js.map +1 -0
- package/esm/MarkSubscript.d.ts +11 -0
- package/esm/MarkSubscript.d.ts.map +1 -0
- package/esm/MarkSubscript.js +31 -0
- package/esm/MarkSubscript.js.map +1 -0
- package/esm/MarkSuperscript.d.ts +11 -0
- package/esm/MarkSuperscript.d.ts.map +1 -0
- package/esm/MarkSuperscript.js +31 -0
- package/esm/MarkSuperscript.js.map +1 -0
- package/esm/MarkTextColor.d.ts +8 -0
- package/esm/MarkTextColor.d.ts.map +1 -0
- package/esm/MarkTextColor.js +28 -0
- package/esm/MarkTextColor.js.map +1 -0
- package/esm/MarkUnderline.d.ts +11 -0
- package/esm/MarkUnderline.d.ts.map +1 -0
- package/esm/MarkUnderline.js +34 -0
- package/esm/MarkUnderline.js.map +1 -0
- package/esm/NodeAside.d.ts +8 -0
- package/esm/NodeAside.d.ts.map +1 -0
- package/esm/NodeAside.js +17 -0
- package/esm/NodeAside.js.map +1 -0
- package/esm/NodeBlockquote.d.ts +13 -0
- package/esm/NodeBlockquote.d.ts.map +1 -0
- package/esm/NodeBlockquote.js +35 -0
- package/esm/NodeBlockquote.js.map +1 -0
- package/esm/NodeBookmark.d.ts +8 -0
- package/esm/NodeBookmark.d.ts.map +1 -0
- package/esm/NodeBookmark.js +20 -0
- package/esm/NodeBookmark.js.map +1 -0
- package/esm/NodeBulletList.d.ts +13 -0
- package/esm/NodeBulletList.d.ts.map +1 -0
- package/esm/NodeBulletList.js +35 -0
- package/esm/NodeBulletList.js.map +1 -0
- package/esm/NodeCodeBlock.d.ts +9 -0
- package/esm/NodeCodeBlock.d.ts.map +1 -0
- package/esm/NodeCodeBlock.js +51 -0
- package/esm/NodeCodeBlock.js.map +1 -0
- package/esm/NodeDefinitionDesc.d.ts +8 -0
- package/esm/NodeDefinitionDesc.d.ts.map +1 -0
- package/esm/NodeDefinitionDesc.js +17 -0
- package/esm/NodeDefinitionDesc.js.map +1 -0
- package/esm/NodeDefinitionList.d.ts +13 -0
- package/esm/NodeDefinitionList.d.ts.map +1 -0
- package/esm/NodeDefinitionList.js +29 -0
- package/esm/NodeDefinitionList.js.map +1 -0
- package/esm/NodeDefinitionTerm.d.ts +8 -0
- package/esm/NodeDefinitionTerm.d.ts.map +1 -0
- package/esm/NodeDefinitionTerm.js +17 -0
- package/esm/NodeDefinitionTerm.js.map +1 -0
- package/esm/NodeDocument.d.ts +7 -0
- package/esm/NodeDocument.d.ts.map +1 -0
- package/esm/NodeDocument.js +20 -0
- package/esm/NodeDocument.js.map +1 -0
- package/esm/NodeDocumentCode.d.ts +7 -0
- package/esm/NodeDocumentCode.d.ts.map +1 -0
- package/esm/NodeDocumentCode.js +30 -0
- package/esm/NodeDocumentCode.js.map +1 -0
- package/esm/NodeFrontmatter.d.ts +8 -0
- package/esm/NodeFrontmatter.d.ts.map +1 -0
- package/esm/NodeFrontmatter.js +17 -0
- package/esm/NodeFrontmatter.js.map +1 -0
- package/esm/NodeHardBreak.d.ts +14 -0
- package/esm/NodeHardBreak.d.ts.map +1 -0
- package/esm/NodeHardBreak.js +68 -0
- package/esm/NodeHardBreak.js.map +1 -0
- package/esm/NodeHeading.d.ts +13 -0
- package/esm/NodeHeading.d.ts.map +1 -0
- package/esm/NodeHeading.js +50 -0
- package/esm/NodeHeading.js.map +1 -0
- package/esm/NodeHorizontalRule.d.ts +11 -0
- package/esm/NodeHorizontalRule.d.ts.map +1 -0
- package/esm/NodeHorizontalRule.js +30 -0
- package/esm/NodeHorizontalRule.js.map +1 -0
- package/esm/NodeImage.d.ts +8 -0
- package/esm/NodeImage.d.ts.map +1 -0
- package/esm/NodeImage.js +34 -0
- package/esm/NodeImage.js.map +1 -0
- package/esm/NodeInlineShortCode.d.ts +11 -0
- package/esm/NodeInlineShortCode.d.ts.map +1 -0
- package/esm/NodeInlineShortCode.js +40 -0
- package/esm/NodeInlineShortCode.js.map +1 -0
- package/esm/NodeListItem.d.ts +14 -0
- package/esm/NodeListItem.d.ts.map +1 -0
- package/esm/NodeListItem.js +202 -0
- package/esm/NodeListItem.js.map +1 -0
- package/esm/NodeMath.d.ts +8 -0
- package/esm/NodeMath.d.ts.map +1 -0
- package/esm/NodeMath.js +100 -0
- package/esm/NodeMath.js.map +1 -0
- package/esm/NodeOrderedList.d.ts +23 -0
- package/esm/NodeOrderedList.d.ts.map +1 -0
- package/esm/NodeOrderedList.js +53 -0
- package/esm/NodeOrderedList.js.map +1 -0
- package/esm/NodeParagraph.d.ts +11 -0
- package/esm/NodeParagraph.d.ts.map +1 -0
- package/esm/NodeParagraph.js +45 -0
- package/esm/NodeParagraph.js.map +1 -0
- package/esm/NodeTaskItem.d.ts +24 -0
- package/esm/NodeTaskItem.d.ts.map +1 -0
- package/esm/NodeTaskItem.js +147 -0
- package/esm/NodeTaskItem.js.map +1 -0
- package/esm/NodeTaskList.d.ts +11 -0
- package/esm/NodeTaskList.d.ts.map +1 -0
- package/esm/NodeTaskList.js +26 -0
- package/esm/NodeTaskList.js.map +1 -0
- package/esm/NodeText.d.ts +7 -0
- package/esm/NodeText.d.ts.map +1 -0
- package/esm/NodeText.js +10 -0
- package/esm/NodeText.js.map +1 -0
- package/esm/NodeVideo.d.ts +8 -0
- package/esm/NodeVideo.d.ts.map +1 -0
- package/esm/NodeVideo.js +46 -0
- package/esm/NodeVideo.js.map +1 -0
- package/esm/package.json +3 -0
- package/esm/remote-selection/ExtensionRemoteSelection.d.ts +24 -0
- package/esm/remote-selection/ExtensionRemoteSelection.d.ts.map +1 -0
- package/esm/remote-selection/ExtensionRemoteSelection.js +23 -0
- package/esm/remote-selection/ExtensionRemoteSelection.js.map +1 -0
- package/esm/remote-selection/remoteSelectionPlugin.d.ts +25 -0
- package/esm/remote-selection/remoteSelectionPlugin.d.ts.map +1 -0
- package/esm/remote-selection/remoteSelectionPlugin.js +97 -0
- package/esm/remote-selection/remoteSelectionPlugin.js.map +1 -0
- package/package.json +6 -3
- package/src/ExtensionBaseKeymap.ts +64 -0
- package/src/ExtensionBasicCodeEditor.ts +82 -0
- package/src/ExtensionBasicEditor.ts +97 -0
- package/src/ExtensionDropcursor.ts +221 -0
- package/src/ExtensionGapcursor.ts +278 -0
- package/src/ExtensionHistory.ts +48 -0
- package/src/ExtensionHtml.ts +158 -0
- package/src/ExtensionMediaUpload.ts +258 -0
- package/src/ExtensionSelection.ts +379 -0
- package/src/ExtensionTextAlign.ts +50 -0
- package/src/MarkBookmark.ts +20 -0
- package/src/MarkChange.ts +17 -0
- package/src/MarkCode.ts +35 -0
- package/src/MarkHighlight.ts +38 -0
- package/src/MarkItalic.ts +41 -0
- package/src/MarkLink.ts +32 -0
- package/src/MarkStrike.ts +38 -0
- package/src/MarkStrong.ts +52 -0
- package/src/MarkSubscript.ts +42 -0
- package/src/MarkSuperscript.ts +42 -0
- package/src/MarkTextColor.ts +29 -0
- package/src/MarkUnderline.ts +47 -0
- package/src/NodeAside.ts +19 -0
- package/src/NodeBlockquote.ts +51 -0
- package/src/NodeBookmark.ts +23 -0
- package/src/NodeBulletList.ts +51 -0
- package/src/NodeCodeBlock.ts +60 -0
- package/src/NodeDefinitionDesc.ts +19 -0
- package/src/NodeDefinitionList.ts +46 -0
- package/src/NodeDefinitionTerm.ts +19 -0
- package/src/NodeDocument.ts +22 -0
- package/src/NodeDocumentCode.ts +33 -0
- package/src/NodeFrontmatter.ts +19 -0
- package/src/NodeHardBreak.ts +92 -0
- package/src/NodeHeading.ts +76 -0
- package/src/NodeHorizontalRule.ts +43 -0
- package/src/NodeImage.ts +36 -0
- package/src/NodeInlineShortCode.ts +55 -0
- package/src/NodeListItem.ts +320 -0
- package/src/NodeMath.ts +109 -0
- package/src/NodeOrderedList.ts +79 -0
- package/src/NodeParagraph.ts +60 -0
- package/src/NodeTaskItem.ts +190 -0
- package/src/NodeTaskList.ts +38 -0
- package/src/NodeText.ts +12 -0
- package/src/NodeVideo.ts +44 -0
- package/src/remote-selection/ExtensionRemoteSelection.ts +45 -0
- package/src/remote-selection/remoteSelectionPlugin.ts +157 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { NodeSpec, NodeType } from 'prosemirror-model';
|
|
2
|
+
|
|
3
|
+
import { type CoreEditor, Node } from '@kerebron/editor';
|
|
4
|
+
import {
|
|
5
|
+
getHtmlAttributes,
|
|
6
|
+
setHtmlAttributes,
|
|
7
|
+
} from '@kerebron/editor/utilities';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
type InputRule,
|
|
11
|
+
wrappingInputRule,
|
|
12
|
+
} from '@kerebron/editor/plugins/input-rules';
|
|
13
|
+
import {
|
|
14
|
+
type CommandFactories,
|
|
15
|
+
type CommandShortcuts,
|
|
16
|
+
} from '@kerebron/editor/commands';
|
|
17
|
+
|
|
18
|
+
export class NodeOrderedList extends Node {
|
|
19
|
+
override name = 'ordered_list';
|
|
20
|
+
requires = ['doc'];
|
|
21
|
+
|
|
22
|
+
override attributes = {
|
|
23
|
+
type: {
|
|
24
|
+
default: '1',
|
|
25
|
+
fromDom(element: HTMLElement) {
|
|
26
|
+
return element.hasAttribute('type')
|
|
27
|
+
? element.getAttribute('type')
|
|
28
|
+
: '1';
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
start: {
|
|
32
|
+
default: 1,
|
|
33
|
+
fromDom(element: HTMLElement) {
|
|
34
|
+
return element.hasAttribute('start')
|
|
35
|
+
? +element.getAttribute('start')!
|
|
36
|
+
: 1;
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
override getNodeSpec(): NodeSpec {
|
|
42
|
+
return {
|
|
43
|
+
group: 'block',
|
|
44
|
+
content: 'list_item+',
|
|
45
|
+
parseDOM: [
|
|
46
|
+
{ tag: 'ol', getAttrs: (element) => setHtmlAttributes(this, element) },
|
|
47
|
+
],
|
|
48
|
+
toDOM: (node) => ['ol', getHtmlAttributes(this, node), 0],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
override getInputRules(type: NodeType): InputRule[] {
|
|
53
|
+
return [
|
|
54
|
+
/// Given a list node type, returns an input rule that turns a number
|
|
55
|
+
/// followed by a dot at the start of a textblock into an ordered list.
|
|
56
|
+
wrappingInputRule(
|
|
57
|
+
/^(\d+)\.\s$/,
|
|
58
|
+
type,
|
|
59
|
+
(match) => ({ order: +match[1] }),
|
|
60
|
+
(match, node) => node.childCount + node.attrs.order == +match[1],
|
|
61
|
+
),
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
override getCommandFactories(
|
|
66
|
+
editor: CoreEditor,
|
|
67
|
+
type: NodeType,
|
|
68
|
+
): Partial<CommandFactories> {
|
|
69
|
+
return {
|
|
70
|
+
'toggleOrderedList': () => editor.commandFactories.wrapInList(type),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
override getKeyboardShortcuts(): Partial<CommandShortcuts> {
|
|
75
|
+
return {
|
|
76
|
+
'Shift-Ctrl-7': 'toggleOrderedList',
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type NodeSpec, type NodeType } from 'prosemirror-model';
|
|
2
|
+
import { type CoreEditor, Node } from '@kerebron/editor';
|
|
3
|
+
import {
|
|
4
|
+
type CommandFactories,
|
|
5
|
+
type CommandShortcuts,
|
|
6
|
+
} from '@kerebron/editor/commands';
|
|
7
|
+
|
|
8
|
+
type TextAlign = 'left' | 'center' | 'right' | 'justify';
|
|
9
|
+
|
|
10
|
+
export class NodeParagraph extends Node {
|
|
11
|
+
override name = 'paragraph';
|
|
12
|
+
requires = ['doc'];
|
|
13
|
+
|
|
14
|
+
override getNodeSpec(): NodeSpec {
|
|
15
|
+
return {
|
|
16
|
+
content: 'inline*',
|
|
17
|
+
group: 'block',
|
|
18
|
+
attrs: {
|
|
19
|
+
textAlign: { default: null },
|
|
20
|
+
},
|
|
21
|
+
parseDOM: [
|
|
22
|
+
{
|
|
23
|
+
tag: 'p',
|
|
24
|
+
getAttrs(dom) {
|
|
25
|
+
const element = dom as HTMLElement;
|
|
26
|
+
const style = element.style.textAlign;
|
|
27
|
+
if (
|
|
28
|
+
style === 'center' || style === 'right' || style === 'justify'
|
|
29
|
+
) {
|
|
30
|
+
return { textAlign: style };
|
|
31
|
+
}
|
|
32
|
+
return { textAlign: null };
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
toDOM(node) {
|
|
37
|
+
const align = node.attrs.textAlign as TextAlign | null;
|
|
38
|
+
if (align && align !== 'left') {
|
|
39
|
+
return ['p', { style: `text-align: ${align}` }, 0];
|
|
40
|
+
}
|
|
41
|
+
return ['p', 0];
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
override getCommandFactories(
|
|
47
|
+
editor: CoreEditor,
|
|
48
|
+
type: NodeType,
|
|
49
|
+
): Partial<CommandFactories> {
|
|
50
|
+
return {
|
|
51
|
+
'setParagraph': () => editor.commandFactories.setBlockType(type),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
override getKeyboardShortcuts(): Partial<CommandShortcuts> {
|
|
56
|
+
return {
|
|
57
|
+
'Shift-Ctrl-0': 'setParagraph',
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { Node as PmNode, NodeSpec, type NodeType } from 'prosemirror-model';
|
|
2
|
+
import { type CoreEditor, Node } from '@kerebron/editor';
|
|
3
|
+
import {
|
|
4
|
+
type CommandFactories,
|
|
5
|
+
type CommandShortcuts,
|
|
6
|
+
} from '@kerebron/editor/commands';
|
|
7
|
+
import { wrappingInputRule } from '@kerebron/editor/plugins/input-rules';
|
|
8
|
+
import {
|
|
9
|
+
liftListItem,
|
|
10
|
+
sinkListItem,
|
|
11
|
+
splitListItem,
|
|
12
|
+
} from './NodeListItem.js';
|
|
13
|
+
import { NodeViewConstructor } from '@kerebron/editor/DummyEditorView';
|
|
14
|
+
import { EditorView } from 'prosemirror-view';
|
|
15
|
+
import { Transaction } from 'prosemirror-state';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Matches a task item to a - [ ] on input.
|
|
19
|
+
*/
|
|
20
|
+
export const inputRegex = /^\s*(\[([( |x])?\])\s$/;
|
|
21
|
+
|
|
22
|
+
export interface TaskItemOptions {
|
|
23
|
+
onReadOnlyChecked?: (node: PmNode, checked: boolean) => boolean;
|
|
24
|
+
nested: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class NodeTaskItem extends Node {
|
|
28
|
+
override name = 'task_item';
|
|
29
|
+
requires = ['doc'];
|
|
30
|
+
|
|
31
|
+
public constructor(protected override config: Partial<TaskItemOptions> = {}) {
|
|
32
|
+
super(config);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
override getNodeSpec(): NodeSpec {
|
|
36
|
+
return {
|
|
37
|
+
attrs: {
|
|
38
|
+
checked: {
|
|
39
|
+
default: false,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
content: this.config.nested ? 'paragraph block*' : 'paragraph+',
|
|
43
|
+
parseDOM: [{
|
|
44
|
+
tag: 'li[data-checked]',
|
|
45
|
+
getAttrs(node) {
|
|
46
|
+
return {
|
|
47
|
+
checked: node.getAttribute('data-checked') === 'true',
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
}],
|
|
51
|
+
defining: true,
|
|
52
|
+
toDOM(node) {
|
|
53
|
+
return [
|
|
54
|
+
'li',
|
|
55
|
+
{
|
|
56
|
+
'data-type': this.name,
|
|
57
|
+
'data-checked': node.attrs.checked,
|
|
58
|
+
},
|
|
59
|
+
[
|
|
60
|
+
'label',
|
|
61
|
+
[
|
|
62
|
+
'input',
|
|
63
|
+
{
|
|
64
|
+
type: 'checkbox',
|
|
65
|
+
checked: node.attrs.checked ? 'checked' : null,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
['span'],
|
|
69
|
+
],
|
|
70
|
+
['div', 0],
|
|
71
|
+
];
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
override getCommandFactories(
|
|
77
|
+
editor: CoreEditor,
|
|
78
|
+
type: NodeType,
|
|
79
|
+
): Partial<CommandFactories> {
|
|
80
|
+
return {
|
|
81
|
+
'splitListItem': () => splitListItem(type),
|
|
82
|
+
'liftListItem': () => liftListItem(type),
|
|
83
|
+
'sinkListItem': () => sinkListItem(type),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
override getNodeView(editor: CoreEditor): NodeViewConstructor {
|
|
88
|
+
return (node: PmNode, view: EditorView, getPos) => {
|
|
89
|
+
const listItem = document.createElement('li');
|
|
90
|
+
const checkboxWrapper = document.createElement('label');
|
|
91
|
+
const checkboxStyler = document.createElement('span');
|
|
92
|
+
const checkbox = document.createElement('input');
|
|
93
|
+
const content = document.createElement('div');
|
|
94
|
+
|
|
95
|
+
// Style for inline checkbox and text
|
|
96
|
+
listItem.style.display = 'flex';
|
|
97
|
+
listItem.style.alignItems = 'flex-start';
|
|
98
|
+
listItem.style.gap = '0.5em';
|
|
99
|
+
checkboxWrapper.style.flexShrink = '0';
|
|
100
|
+
checkboxWrapper.style.marginTop = '0.25em';
|
|
101
|
+
content.style.flex = '1';
|
|
102
|
+
|
|
103
|
+
checkboxWrapper.contentEditable = 'false';
|
|
104
|
+
checkbox.type = 'checkbox';
|
|
105
|
+
checkbox.addEventListener('mousedown', (event) => event.preventDefault());
|
|
106
|
+
checkbox.addEventListener('change', (event) => {
|
|
107
|
+
const isEditable = true; // TODO editor.isEditable
|
|
108
|
+
|
|
109
|
+
// if the editor isn’t editable and we don't have a handler for
|
|
110
|
+
// readonly checks we have to undo the latest change
|
|
111
|
+
if (!isEditable && !this.config.onReadOnlyChecked) {
|
|
112
|
+
checkbox.checked = !checkbox.checked;
|
|
113
|
+
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const { checked } = event.target as any;
|
|
118
|
+
|
|
119
|
+
if (isEditable && typeof getPos === 'function') {
|
|
120
|
+
editor
|
|
121
|
+
.chain()
|
|
122
|
+
.focus(undefined, { scrollIntoView: false })
|
|
123
|
+
.command(({ tr }: { tr: Transaction }) => {
|
|
124
|
+
const position = getPos();
|
|
125
|
+
|
|
126
|
+
if (typeof position !== 'number') {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
const currentNode = tr.doc.nodeAt(position);
|
|
130
|
+
|
|
131
|
+
tr.setNodeMarkup(position, undefined, {
|
|
132
|
+
...currentNode?.attrs,
|
|
133
|
+
checked,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return true;
|
|
137
|
+
})
|
|
138
|
+
.run();
|
|
139
|
+
}
|
|
140
|
+
if (!isEditable && this.config.onReadOnlyChecked) {
|
|
141
|
+
// Reset state if onReadOnlyChecked returns false
|
|
142
|
+
if (!this.config.onReadOnlyChecked(node, checked)) {
|
|
143
|
+
checkbox.checked = !checkbox.checked;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
listItem.dataset.checked = node.attrs.checked;
|
|
149
|
+
checkbox.checked = node.attrs.checked;
|
|
150
|
+
|
|
151
|
+
checkboxWrapper.append(checkbox, checkboxStyler);
|
|
152
|
+
listItem.append(checkboxWrapper, content);
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
dom: listItem,
|
|
156
|
+
contentDOM: content,
|
|
157
|
+
update: (updatedNode) => {
|
|
158
|
+
if (updatedNode.type.name !== this.type) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
listItem.dataset.checked = updatedNode.attrs.checked;
|
|
163
|
+
checkbox.checked = updatedNode.attrs.checked;
|
|
164
|
+
|
|
165
|
+
return true;
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
override getKeyboardShortcuts(): Partial<CommandShortcuts> {
|
|
172
|
+
return {
|
|
173
|
+
'Enter': 'splitListItem',
|
|
174
|
+
'Tab': 'sinkListItem',
|
|
175
|
+
'Shift-Tab': 'liftListItem',
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
override getInputRules(type: NodeType) {
|
|
180
|
+
return [
|
|
181
|
+
wrappingInputRule(
|
|
182
|
+
inputRegex,
|
|
183
|
+
type,
|
|
184
|
+
(match) => ({
|
|
185
|
+
checked: match[match.length - 1] === 'x',
|
|
186
|
+
}),
|
|
187
|
+
),
|
|
188
|
+
];
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { NodeSpec, NodeType } from 'prosemirror-model';
|
|
2
|
+
|
|
3
|
+
import { type CoreEditor, Node } from '@kerebron/editor';
|
|
4
|
+
import {
|
|
5
|
+
type CommandFactories,
|
|
6
|
+
type CommandShortcuts,
|
|
7
|
+
} from '@kerebron/editor/commands';
|
|
8
|
+
|
|
9
|
+
export class NodeTaskList extends Node {
|
|
10
|
+
override name = 'task_list';
|
|
11
|
+
requires = ['doc'];
|
|
12
|
+
|
|
13
|
+
override getNodeSpec(): NodeSpec {
|
|
14
|
+
return {
|
|
15
|
+
content: 'task_item+',
|
|
16
|
+
group: 'block',
|
|
17
|
+
parseDOM: [{ tag: `ul[data-type="${this.name}"]` }],
|
|
18
|
+
toDOM() {
|
|
19
|
+
return ['ul', { 'data-type': this.name }, 0];
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
override getCommandFactories(
|
|
25
|
+
editor: CoreEditor,
|
|
26
|
+
type: NodeType,
|
|
27
|
+
): Partial<CommandFactories> {
|
|
28
|
+
return {
|
|
29
|
+
'toggleTaskList': () => editor.commandFactories.wrapInList(type),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
override getKeyboardShortcuts(): Partial<CommandShortcuts> {
|
|
34
|
+
return {
|
|
35
|
+
'Shift-Ctrl-9': 'toggleTaskList',
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/NodeText.ts
ADDED
package/src/NodeVideo.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type NodeSpec } from 'prosemirror-model';
|
|
2
|
+
import { Node } from '@kerebron/editor';
|
|
3
|
+
|
|
4
|
+
export class NodeVideo extends Node {
|
|
5
|
+
override name = 'video';
|
|
6
|
+
requires = ['doc'];
|
|
7
|
+
|
|
8
|
+
override getNodeSpec(): NodeSpec {
|
|
9
|
+
return {
|
|
10
|
+
attrs: {
|
|
11
|
+
src: {},
|
|
12
|
+
title: { default: null },
|
|
13
|
+
controls: { default: true },
|
|
14
|
+
width: { default: null },
|
|
15
|
+
height: { default: null },
|
|
16
|
+
},
|
|
17
|
+
group: 'block',
|
|
18
|
+
draggable: true,
|
|
19
|
+
parseDOM: [
|
|
20
|
+
{
|
|
21
|
+
tag: 'video[src]',
|
|
22
|
+
getAttrs(dom: HTMLElement) {
|
|
23
|
+
return {
|
|
24
|
+
src: dom.getAttribute('src'),
|
|
25
|
+
title: dom.getAttribute('title'),
|
|
26
|
+
controls: dom.hasAttribute('controls'),
|
|
27
|
+
width: dom.getAttribute('width'),
|
|
28
|
+
height: dom.getAttribute('height'),
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
toDOM(node) {
|
|
34
|
+
const { src, title, controls, width, height } = node.attrs;
|
|
35
|
+
const attrs: any = { src };
|
|
36
|
+
if (title) attrs.title = title;
|
|
37
|
+
if (controls) attrs.controls = ''; // Boolean attribute - just needs to be present
|
|
38
|
+
if (width) attrs.width = width;
|
|
39
|
+
if (height) attrs.height = height;
|
|
40
|
+
return ['video', attrs];
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Plugin } from 'prosemirror-state';
|
|
2
|
+
import { type CoreEditor, Extension } from '@kerebron/editor';
|
|
3
|
+
|
|
4
|
+
import { remoteSelectionPlugin } from './remoteSelectionPlugin.js';
|
|
5
|
+
|
|
6
|
+
type Color = string;
|
|
7
|
+
|
|
8
|
+
export interface SelectionState {
|
|
9
|
+
clientId: number;
|
|
10
|
+
user: {
|
|
11
|
+
name: string;
|
|
12
|
+
color: Color;
|
|
13
|
+
colorLight: Color;
|
|
14
|
+
};
|
|
15
|
+
cursor?: {
|
|
16
|
+
anchor: number;
|
|
17
|
+
head: number;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { remoteSelectionPluginKey } from './remoteSelectionPlugin.js';
|
|
22
|
+
|
|
23
|
+
export class ExtensionRemoteSelection extends Extension {
|
|
24
|
+
override name = 'remote-selection';
|
|
25
|
+
|
|
26
|
+
private remoteStates: SelectionState[] = [];
|
|
27
|
+
|
|
28
|
+
override getProseMirrorPlugins(): Plugin[] {
|
|
29
|
+
return [
|
|
30
|
+
remoteSelectionPlugin(this, this.editor),
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getRemoteStates(): SelectionState[] {
|
|
35
|
+
return this.remoteStates;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
setRemoteStates(states: SelectionState[]) {
|
|
39
|
+
this.remoteStates = states;
|
|
40
|
+
const event = new CustomEvent('remoteSelectionChange', {
|
|
41
|
+
detail: {},
|
|
42
|
+
});
|
|
43
|
+
this.editor.dispatchEvent(event);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Decoration, DecorationSet } from 'prosemirror-view';
|
|
2
|
+
import { Plugin, PluginKey } from 'prosemirror-state';
|
|
3
|
+
import { type DecorationAttrs } from 'prosemirror-view';
|
|
4
|
+
|
|
5
|
+
import type { CoreEditor } from '@kerebron/editor';
|
|
6
|
+
|
|
7
|
+
import type { ExtensionRemoteSelection } from './ExtensionRemoteSelection.js';
|
|
8
|
+
|
|
9
|
+
export const remoteSelectionPluginKey = new PluginKey('remote-selection');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Default generator for a cursor element
|
|
13
|
+
*/
|
|
14
|
+
export const defaultCursorBuilder = (user: any): HTMLElement => {
|
|
15
|
+
const cursor = document.createElement('span');
|
|
16
|
+
cursor.classList.add('kb-yjs__cursor');
|
|
17
|
+
cursor.setAttribute('style', `border-color: ${user.color};`);
|
|
18
|
+
const userDiv = document.createElement('div');
|
|
19
|
+
userDiv.setAttribute('style', `background-color: ${user.color}`);
|
|
20
|
+
userDiv.insertBefore(document.createTextNode(user.name), null);
|
|
21
|
+
const nonbreakingSpace1 = document.createTextNode('\u2060');
|
|
22
|
+
const nonbreakingSpace2 = document.createTextNode('\u2060');
|
|
23
|
+
cursor.insertBefore(nonbreakingSpace1, null);
|
|
24
|
+
cursor.insertBefore(userDiv, null);
|
|
25
|
+
cursor.insertBefore(nonbreakingSpace2, null);
|
|
26
|
+
return cursor;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Default generator for the selection attributes
|
|
31
|
+
*/
|
|
32
|
+
export const defaultSelectionBuilder = (user: any): DecorationAttrs => {
|
|
33
|
+
return {
|
|
34
|
+
style: `background-color: ${user.color}70`,
|
|
35
|
+
class: 'kb-yjs__selection',
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const rxValidColor = /^#[0-9a-fA-F]{6}$/;
|
|
40
|
+
|
|
41
|
+
export const createDecorations = (
|
|
42
|
+
state: any,
|
|
43
|
+
extension: ExtensionRemoteSelection,
|
|
44
|
+
createCursor: (
|
|
45
|
+
user: { name: string; color: string },
|
|
46
|
+
clientId: number,
|
|
47
|
+
) => Element,
|
|
48
|
+
createSelection: (
|
|
49
|
+
user: { name: string; color: string },
|
|
50
|
+
clientId: number,
|
|
51
|
+
) => DecorationAttrs,
|
|
52
|
+
): any => {
|
|
53
|
+
const decorations: Decoration[] = [];
|
|
54
|
+
|
|
55
|
+
const remoteStates = extension.getRemoteStates();
|
|
56
|
+
if (remoteStates.length === 0) {
|
|
57
|
+
return DecorationSet.create(state.doc, []);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (const remoteState of remoteStates) {
|
|
61
|
+
if (remoteState.cursor != null) {
|
|
62
|
+
const user = remoteState.user || {};
|
|
63
|
+
if (user.color == null) {
|
|
64
|
+
user.color = '#ffa500';
|
|
65
|
+
} else if (!rxValidColor.test(user.color)) {
|
|
66
|
+
// We only support 6-digit RGB colors in y-prosemirror
|
|
67
|
+
console.warn('A user uses an unsupported color format', user);
|
|
68
|
+
}
|
|
69
|
+
if (user.name == null) {
|
|
70
|
+
user.name = `User: ${remoteState.clientId}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const cursor = remoteState.cursor;
|
|
74
|
+
let anchor = cursor.anchor || null;
|
|
75
|
+
let head = cursor.head || null;
|
|
76
|
+
|
|
77
|
+
if (anchor !== null && head !== null) {
|
|
78
|
+
const maxsize = Math.max(state.doc.content.size - 1, 0);
|
|
79
|
+
anchor = Math.min(anchor, maxsize);
|
|
80
|
+
head = Math.min(head, maxsize);
|
|
81
|
+
decorations.push(
|
|
82
|
+
Decoration.widget(
|
|
83
|
+
head,
|
|
84
|
+
() => createCursor(user, remoteState.clientId),
|
|
85
|
+
{
|
|
86
|
+
key: remoteState.clientId + '',
|
|
87
|
+
side: 10,
|
|
88
|
+
},
|
|
89
|
+
),
|
|
90
|
+
);
|
|
91
|
+
const from = Math.min(anchor, head);
|
|
92
|
+
const to = Math.max(anchor, head);
|
|
93
|
+
decorations.push(
|
|
94
|
+
Decoration.inline(
|
|
95
|
+
from,
|
|
96
|
+
to,
|
|
97
|
+
createSelection(user, remoteState.clientId),
|
|
98
|
+
{
|
|
99
|
+
inclusiveEnd: true,
|
|
100
|
+
inclusiveStart: false,
|
|
101
|
+
},
|
|
102
|
+
),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return DecorationSet.create(state.doc, decorations);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const remoteSelectionPlugin = (
|
|
111
|
+
extension: ExtensionRemoteSelection,
|
|
112
|
+
editor: CoreEditor,
|
|
113
|
+
{
|
|
114
|
+
cursorBuilder = defaultCursorBuilder,
|
|
115
|
+
selectionBuilder = defaultSelectionBuilder,
|
|
116
|
+
}: {
|
|
117
|
+
cursorBuilder?: (user: any, clientId: number) => HTMLElement;
|
|
118
|
+
selectionBuilder?: (user: any, clientId: number) => DecorationAttrs;
|
|
119
|
+
} = {},
|
|
120
|
+
) => {
|
|
121
|
+
return new Plugin({
|
|
122
|
+
key: remoteSelectionPluginKey,
|
|
123
|
+
state: {
|
|
124
|
+
init(_, state) {
|
|
125
|
+
return createDecorations(
|
|
126
|
+
state,
|
|
127
|
+
extension,
|
|
128
|
+
cursorBuilder,
|
|
129
|
+
selectionBuilder,
|
|
130
|
+
);
|
|
131
|
+
},
|
|
132
|
+
apply(tr, prevState, _oldState, newState) {
|
|
133
|
+
const remoteCursorState = tr.getMeta(remoteSelectionPluginKey);
|
|
134
|
+
// TODO validate: isChangeOrigin
|
|
135
|
+
// const state = remoteSelectionPluginKey.getState(newState);
|
|
136
|
+
|
|
137
|
+
if (
|
|
138
|
+
(remoteCursorState?.isChangeOrigin) ||
|
|
139
|
+
(remoteCursorState?.remotePositionUpdated)
|
|
140
|
+
) {
|
|
141
|
+
return createDecorations(
|
|
142
|
+
newState,
|
|
143
|
+
extension,
|
|
144
|
+
cursorBuilder,
|
|
145
|
+
selectionBuilder,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
return prevState.map(tr.mapping, tr.doc);
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
props: {
|
|
152
|
+
decorations: (state) => {
|
|
153
|
+
return remoteSelectionPluginKey.getState(state);
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
};
|