@jvs-milkdown/prose 1.0.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.
Files changed (119) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +11 -0
  3. package/lib/changeset.d.ts +2 -0
  4. package/lib/changeset.d.ts.map +1 -0
  5. package/lib/changeset.js +2 -0
  6. package/lib/changeset.js.map +1 -0
  7. package/lib/commands.d.ts +2 -0
  8. package/lib/commands.d.ts.map +1 -0
  9. package/lib/commands.js +2 -0
  10. package/lib/commands.js.map +1 -0
  11. package/lib/dropcursor.d.ts +2 -0
  12. package/lib/dropcursor.d.ts.map +1 -0
  13. package/lib/dropcursor.js +2 -0
  14. package/lib/dropcursor.js.map +1 -0
  15. package/lib/gapcursor.d.ts +2 -0
  16. package/lib/gapcursor.d.ts.map +1 -0
  17. package/lib/gapcursor.js +2 -0
  18. package/lib/gapcursor.js.map +1 -0
  19. package/lib/history.d.ts +2 -0
  20. package/lib/history.d.ts.map +1 -0
  21. package/lib/history.js +2 -0
  22. package/lib/history.js.map +1 -0
  23. package/lib/index.d.ts +132 -0
  24. package/lib/index.d.ts.map +1 -0
  25. package/lib/index.js +402 -0
  26. package/lib/index.js.map +1 -0
  27. package/lib/inputrules.d.ts +2 -0
  28. package/lib/inputrules.d.ts.map +1 -0
  29. package/lib/inputrules.js +2 -0
  30. package/lib/inputrules.js.map +1 -0
  31. package/lib/keymap.d.ts +2 -0
  32. package/lib/keymap.d.ts.map +1 -0
  33. package/lib/keymap.js +2 -0
  34. package/lib/keymap.js.map +1 -0
  35. package/lib/model.d.ts +2 -0
  36. package/lib/model.d.ts.map +1 -0
  37. package/lib/model.js +2 -0
  38. package/lib/model.js.map +1 -0
  39. package/lib/schema-list.d.ts +2 -0
  40. package/lib/schema-list.d.ts.map +1 -0
  41. package/lib/schema-list.js +2 -0
  42. package/lib/schema-list.js.map +1 -0
  43. package/lib/state.d.ts +2 -0
  44. package/lib/state.d.ts.map +1 -0
  45. package/lib/state.js +2 -0
  46. package/lib/state.js.map +1 -0
  47. package/lib/style/gapcursor.css +25 -0
  48. package/lib/style/prosemirror.css +54 -0
  49. package/lib/style/tables.css +48 -0
  50. package/lib/tables.d.ts +2 -0
  51. package/lib/tables.d.ts.map +1 -0
  52. package/lib/tables.js +2 -0
  53. package/lib/tables.js.map +1 -0
  54. package/lib/toolkit/browser.d.ts +17 -0
  55. package/lib/toolkit/browser.d.ts.map +1 -0
  56. package/lib/toolkit/index.d.ts +5 -0
  57. package/lib/toolkit/index.d.ts.map +1 -0
  58. package/lib/toolkit/input-rules/common.d.ts +20 -0
  59. package/lib/toolkit/input-rules/common.d.ts.map +1 -0
  60. package/lib/toolkit/input-rules/custom-input-rules.d.ts +7 -0
  61. package/lib/toolkit/input-rules/custom-input-rules.d.ts.map +1 -0
  62. package/lib/toolkit/input-rules/index.d.ts +5 -0
  63. package/lib/toolkit/input-rules/index.d.ts.map +1 -0
  64. package/lib/toolkit/input-rules/mark-rule.d.ts +5 -0
  65. package/lib/toolkit/input-rules/mark-rule.d.ts.map +1 -0
  66. package/lib/toolkit/input-rules/node-rule.d.ts +5 -0
  67. package/lib/toolkit/input-rules/node-rule.d.ts.map +1 -0
  68. package/lib/toolkit/position/index.d.ts +13 -0
  69. package/lib/toolkit/position/index.d.ts.map +1 -0
  70. package/lib/toolkit/prose/helper.d.ts +6 -0
  71. package/lib/toolkit/prose/helper.d.ts.map +1 -0
  72. package/lib/toolkit/prose/index.d.ts +5 -0
  73. package/lib/toolkit/prose/index.d.ts.map +1 -0
  74. package/lib/toolkit/prose/node.d.ts +17 -0
  75. package/lib/toolkit/prose/node.d.ts.map +1 -0
  76. package/lib/toolkit/prose/schema.d.ts +4 -0
  77. package/lib/toolkit/prose/schema.d.ts.map +1 -0
  78. package/lib/toolkit/prose/selection.d.ts +19 -0
  79. package/lib/toolkit/prose/selection.d.ts.map +1 -0
  80. package/lib/toolkit/prose/types.d.ts +3 -0
  81. package/lib/toolkit/prose/types.d.ts.map +1 -0
  82. package/lib/transform.d.ts +2 -0
  83. package/lib/transform.d.ts.map +1 -0
  84. package/lib/transform.js +2 -0
  85. package/lib/transform.js.map +1 -0
  86. package/lib/tsconfig.tsbuildinfo +1 -0
  87. package/lib/view.d.ts +2 -0
  88. package/lib/view.d.ts.map +1 -0
  89. package/lib/view.js +2 -0
  90. package/lib/view.js.map +1 -0
  91. package/package.json +141 -0
  92. package/src/changeset.ts +1 -0
  93. package/src/commands.ts +1 -0
  94. package/src/dropcursor.ts +1 -0
  95. package/src/gapcursor.ts +1 -0
  96. package/src/history.ts +1 -0
  97. package/src/index.ts +1 -0
  98. package/src/inputrules.ts +1 -0
  99. package/src/keymap.ts +1 -0
  100. package/src/model.ts +1 -0
  101. package/src/schema-list.ts +1 -0
  102. package/src/state.ts +1 -0
  103. package/src/tables.ts +1 -0
  104. package/src/toolkit/browser.ts +39 -0
  105. package/src/toolkit/index.ts +4 -0
  106. package/src/toolkit/input-rules/common.ts +25 -0
  107. package/src/toolkit/input-rules/custom-input-rules.ts +113 -0
  108. package/src/toolkit/input-rules/index.ts +4 -0
  109. package/src/toolkit/input-rules/mark-rule.ts +60 -0
  110. package/src/toolkit/input-rules/node-rule.ts +51 -0
  111. package/src/toolkit/position/index.ts +108 -0
  112. package/src/toolkit/prose/helper.ts +30 -0
  113. package/src/toolkit/prose/index.ts +4 -0
  114. package/src/toolkit/prose/node.ts +63 -0
  115. package/src/toolkit/prose/schema.ts +19 -0
  116. package/src/toolkit/prose/selection.ts +97 -0
  117. package/src/toolkit/prose/types.ts +3 -0
  118. package/src/transform.ts +1 -0
  119. package/src/view.ts +1 -0
package/lib/view.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from 'prosemirror-view';
2
+ //# sourceMappingURL=view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,141 @@
1
+ {
2
+ "name": "@jvs-milkdown/prose",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/Milkdown/milkdown.git",
8
+ "directory": "packages/prose"
9
+ },
10
+ "files": [
11
+ "lib",
12
+ "src"
13
+ ],
14
+ "type": "module",
15
+ "sideEffects": false,
16
+ "main": "./lib/index.js",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./lib/index.d.ts",
20
+ "import": "./lib/index.js"
21
+ },
22
+ "./changeset": {
23
+ "types": "./lib/changeset.d.ts",
24
+ "import": "./lib/changeset.js"
25
+ },
26
+ "./commands": {
27
+ "types": "./lib/commands.d.ts",
28
+ "import": "./lib/commands.js"
29
+ },
30
+ "./dropcursor": {
31
+ "types": "./lib/dropcursor.d.ts",
32
+ "import": "./lib/dropcursor.js"
33
+ },
34
+ "./gapcursor": {
35
+ "types": "./lib/gapcursor.d.ts",
36
+ "import": "./lib/gapcursor.js"
37
+ },
38
+ "./history": {
39
+ "types": "./lib/history.d.ts",
40
+ "import": "./lib/history.js"
41
+ },
42
+ "./inputrules": {
43
+ "types": "./lib/inputrules.d.ts",
44
+ "import": "./lib/inputrules.js"
45
+ },
46
+ "./keymap": {
47
+ "types": "./lib/keymap.d.ts",
48
+ "import": "./lib/keymap.js"
49
+ },
50
+ "./model": {
51
+ "types": "./lib/model.d.ts",
52
+ "import": "./lib/model.js"
53
+ },
54
+ "./schema-list": {
55
+ "types": "./lib/schema-list.d.ts",
56
+ "import": "./lib/schema-list.js"
57
+ },
58
+ "./state": {
59
+ "types": "./lib/state.d.ts",
60
+ "import": "./lib/state.js"
61
+ },
62
+ "./transform": {
63
+ "types": "./lib/transform.d.ts",
64
+ "import": "./lib/transform.js"
65
+ },
66
+ "./view": {
67
+ "types": "./lib/view.d.ts",
68
+ "import": "./lib/view.js"
69
+ },
70
+ "./tables": {
71
+ "types": "./lib/tables.d.ts",
72
+ "import": "./lib/tables.js"
73
+ },
74
+ "./view/style/prosemirror.css": "./lib/style/prosemirror.css",
75
+ "./tables/style/tables.css": "./lib/style/tables.css",
76
+ "./gapcursor/style/gapcursor.css": "./lib/style/gapcursor.css"
77
+ },
78
+ "dependencies": {
79
+ "prosemirror-changeset": "^2.3.1",
80
+ "prosemirror-commands": "^1.7.1",
81
+ "prosemirror-dropcursor": "^1.8.2",
82
+ "prosemirror-gapcursor": "^1.4.0",
83
+ "prosemirror-history": "^1.5.0",
84
+ "prosemirror-inputrules": "^1.5.1",
85
+ "prosemirror-keymap": "^1.2.3",
86
+ "prosemirror-model": "^1.25.4",
87
+ "prosemirror-schema-list": "^1.5.1",
88
+ "prosemirror-state": "^1.4.4",
89
+ "prosemirror-tables": "^1.8.1",
90
+ "prosemirror-transform": "^1.10.5",
91
+ "prosemirror-view": "^1.41.3",
92
+ "@jvs-milkdown/exception": "1.0.0"
93
+ },
94
+ "scripts": {
95
+ "build": "rollup -c"
96
+ },
97
+ "types": "./lib/index.d.ts",
98
+ "typesVersions": {
99
+ "*": {
100
+ "changeset": [
101
+ "lib/changeset.d.ts"
102
+ ],
103
+ "commands": [
104
+ "lib/commands.d.ts"
105
+ ],
106
+ "dropcursor": [
107
+ "lib/dropcursor.d.ts"
108
+ ],
109
+ "gapcursor": [
110
+ "lib/gapcursor.d.ts"
111
+ ],
112
+ "history": [
113
+ "lib/history.d.ts"
114
+ ],
115
+ "inputrules": [
116
+ "lib/inputrules.d.ts"
117
+ ],
118
+ "keymap": [
119
+ "lib/keymap.d.ts"
120
+ ],
121
+ "model": [
122
+ "lib/model.d.ts"
123
+ ],
124
+ "schema-list": [
125
+ "lib/schema-list.d.ts"
126
+ ],
127
+ "state": [
128
+ "lib/state.d.ts"
129
+ ],
130
+ "transform": [
131
+ "lib/transform.d.ts"
132
+ ],
133
+ "view": [
134
+ "lib/view.d.ts"
135
+ ],
136
+ "tables": [
137
+ "lib/tables.d.ts"
138
+ ]
139
+ }
140
+ }
141
+ }
@@ -0,0 +1 @@
1
+ export * from 'prosemirror-changeset'
@@ -0,0 +1 @@
1
+ export * from 'prosemirror-commands'
@@ -0,0 +1 @@
1
+ export * from 'prosemirror-dropcursor'
@@ -0,0 +1 @@
1
+ export * from 'prosemirror-gapcursor'
package/src/history.ts ADDED
@@ -0,0 +1 @@
1
+ export * from 'prosemirror-history'
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './toolkit'
@@ -0,0 +1 @@
1
+ export * from 'prosemirror-inputrules'
package/src/keymap.ts ADDED
@@ -0,0 +1 @@
1
+ export * from 'prosemirror-keymap'
package/src/model.ts ADDED
@@ -0,0 +1 @@
1
+ export * from 'prosemirror-model'
@@ -0,0 +1 @@
1
+ export * from 'prosemirror-schema-list'
package/src/state.ts ADDED
@@ -0,0 +1 @@
1
+ export * from 'prosemirror-state'
package/src/tables.ts ADDED
@@ -0,0 +1 @@
1
+ export * from 'prosemirror-tables'
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Copy paste from:
3
+ * https://github.com/ProseMirror/prosemirror-view/blob/master/src/browser.ts
4
+ */
5
+
6
+ const nav = typeof navigator != 'undefined' ? navigator : null
7
+ const doc = typeof document != 'undefined' ? document : null
8
+ const agent = (nav && nav.userAgent) || ''
9
+
10
+ const ie_edge = /Edge\/(\d+)/.exec(agent)
11
+ const ie_upto10 = /MSIE \d/.exec(agent)
12
+ const ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(agent)
13
+
14
+ export const ie = !!(ie_upto10 || ie_11up || ie_edge)
15
+ export const ie_version = ie_upto10
16
+ ? (document as typeof document & { documentMode: unknown }).documentMode
17
+ : ie_11up
18
+ ? +ie_11up[1]!
19
+ : ie_edge
20
+ ? +ie_edge[1]!
21
+ : 0
22
+ export const gecko = !ie && /gecko\/(\d+)/i.test(agent)
23
+ export const gecko_version =
24
+ gecko && +(/Firefox\/(\d+)/.exec(agent) || [0, 0])[1]
25
+
26
+ const _chrome = !ie && /Chrome\/(\d+)/.exec(agent)
27
+ export const chrome = !!_chrome
28
+ export const chrome_version = _chrome ? +_chrome[1]! : 0
29
+ export const safari = !ie && !!nav && /Apple Computer/.test(nav.vendor)
30
+ // Is true for both iOS and iPadOS for convenience
31
+ export const ios =
32
+ safari && (/Mobile\/\w+/.test(agent) || (!!nav && nav.maxTouchPoints > 2))
33
+ export const mac = ios || (nav ? /Mac/.test(nav.platform) : false)
34
+ export const android = /Android \d/.test(agent)
35
+ export const webkit =
36
+ !!doc && 'webkitFontSmoothing' in doc.documentElement.style
37
+ export const webkit_version = webkit
38
+ ? +(/\bAppleWebKit\/(\d+)/.exec(navigator.userAgent) || [0, 0])[1]
39
+ : 0
@@ -0,0 +1,4 @@
1
+ export * as browser from './browser'
2
+ export * from './input-rules'
3
+ export * from './position'
4
+ export * from './prose'
@@ -0,0 +1,25 @@
1
+ import type { Attrs } from '../../model'
2
+ import type { Transaction } from '../../state'
3
+
4
+ /// @internal
5
+ export interface Captured {
6
+ group: string | undefined
7
+ fullMatch: string
8
+ start: number
9
+ end: number
10
+ }
11
+
12
+ /// @internal
13
+ export interface BeforeDispatch {
14
+ match: string[]
15
+ start: number
16
+ end: number
17
+ tr: Transaction
18
+ }
19
+
20
+ /// @internal
21
+ export interface Options {
22
+ getAttr?: (match: RegExpMatchArray) => Attrs
23
+ updateCaptured?: (captured: Captured) => Partial<Captured>
24
+ beforeDispatch?: (options: BeforeDispatch) => void
25
+ }
@@ -0,0 +1,113 @@
1
+ import type { InputRule } from '../../inputrules'
2
+ import type { EditorState, TextSelection, Transaction } from '../../state'
3
+ import type { EditorView } from '../../view'
4
+
5
+ import { Plugin, PluginKey } from '../../state'
6
+ import { android, chrome } from '../browser'
7
+
8
+ function run(
9
+ view: EditorView,
10
+ from: number,
11
+ to: number,
12
+ text: string,
13
+ rules: InputRule[],
14
+ plugin: Plugin
15
+ ) {
16
+ if (view.composing) return false
17
+ const state = view.state
18
+ const $from = state.doc.resolve(from)
19
+ if ($from.parent.type.spec.code) return false
20
+ const textBefore =
21
+ $from.parent.textBetween(
22
+ Math.max(0, $from.parentOffset - 500),
23
+ $from.parentOffset,
24
+ undefined,
25
+ '\uFFFC'
26
+ ) + text
27
+ for (let _matcher of rules) {
28
+ const matcher = _matcher as unknown as {
29
+ match: RegExp
30
+ handler: (
31
+ state: EditorState,
32
+ match: string[],
33
+ from: number,
34
+ to: number
35
+ ) => Transaction
36
+ undoable?: boolean
37
+ }
38
+ const match = matcher.match.exec(textBefore)
39
+ const tr =
40
+ match &&
41
+ match[0] &&
42
+ matcher.handler(state, match, from - (match[0].length - text.length), to)
43
+ if (!tr) continue
44
+ if (matcher.undoable !== false)
45
+ tr.setMeta(plugin, { transform: tr, from, to, text })
46
+ view.dispatch(tr)
47
+ return true
48
+ }
49
+ return false
50
+ }
51
+
52
+ export const customInputRulesKey = new PluginKey('MILKDOWN_CUSTOM_INPUTRULES')
53
+ export function customInputRules({ rules }: { rules: InputRule[] }): Plugin {
54
+ const plugin: Plugin = new Plugin({
55
+ key: customInputRulesKey,
56
+ isInputRules: true,
57
+
58
+ state: {
59
+ init() {
60
+ return null
61
+ },
62
+ apply(this: typeof plugin, tr, prev) {
63
+ const stored = tr.getMeta(this)
64
+ if (stored) return stored
65
+ return tr.selectionSet || tr.docChanged ? null : prev
66
+ },
67
+ },
68
+ props: {
69
+ handleTextInput(view, from, to, text) {
70
+ return run(view, from, to, text, rules, plugin)
71
+ },
72
+ handleDOMEvents: {
73
+ compositionend: (view) => {
74
+ setTimeout(() => {
75
+ const { $cursor } = view.state.selection as TextSelection
76
+ if ($cursor) run(view, $cursor.pos, $cursor.pos, '', rules, plugin)
77
+ })
78
+ return false
79
+ },
80
+ keydown: (view, event) => {
81
+ // On Chrome Android, prosemirror-view suppresses Enter keydown events
82
+ // to avoid input corruption during composition. It then relies on DOM
83
+ // mutation detection to retroactively handle Enter. However, this
84
+ // fallback fails with custom node views (e.g. list-item-block) whose
85
+ // wrapper DOM structure prevents the Enter detection heuristics from
86
+ // recognizing the mutation. We intercept Enter here — before
87
+ // prosemirror-view's suppression — and manually route it through
88
+ // handleKeyDown so that keymaps (splitListItem, etc.) work correctly.
89
+ if (!(android && chrome && (event as KeyboardEvent).key === 'Enter'))
90
+ return false
91
+ if (view.composing) return false
92
+ if (
93
+ view.someProp('handleKeyDown', (f) =>
94
+ f(view, event as KeyboardEvent)
95
+ )
96
+ ) {
97
+ event.preventDefault()
98
+ return true
99
+ }
100
+ return false
101
+ },
102
+ },
103
+ handleKeyDown(view, event) {
104
+ if (event.key !== 'Enter') return false
105
+ const { $cursor } = view.state.selection as TextSelection
106
+ if ($cursor)
107
+ return run(view, $cursor.pos, $cursor.pos, '\n', rules, plugin)
108
+ return false
109
+ },
110
+ },
111
+ })
112
+ return plugin
113
+ }
@@ -0,0 +1,4 @@
1
+ export * from './custom-input-rules'
2
+ export * from './mark-rule'
3
+ export * from './node-rule'
4
+ export * from './common'
@@ -0,0 +1,60 @@
1
+ import type { Mark, MarkType } from '../../model'
2
+ import type { Captured, Options } from './common'
3
+
4
+ import { InputRule } from '../../inputrules'
5
+
6
+ /// Create an input rule for a mark.
7
+ export function markRule(
8
+ regexp: RegExp,
9
+ markType: MarkType,
10
+ options: Options = {}
11
+ ): InputRule {
12
+ return new InputRule(regexp, (state, match, start, end) => {
13
+ const { tr } = state
14
+ const matchLength = match.length
15
+
16
+ let group = match[matchLength - 1]
17
+ let fullMatch = match[0]
18
+ let initialStoredMarks: readonly Mark[] = []
19
+
20
+ let markEnd: number
21
+
22
+ const captured: Captured = {
23
+ group,
24
+ fullMatch,
25
+ start,
26
+ end,
27
+ }
28
+
29
+ const result = options.updateCaptured?.(captured)
30
+ Object.assign(captured, result)
31
+ ;({ group, fullMatch, start, end } = captured)
32
+
33
+ if (fullMatch === null) return null
34
+
35
+ if (group?.trim() === '') return null
36
+
37
+ if (group) {
38
+ const startSpaces = fullMatch.search(/\S/)
39
+ const textStart = start + fullMatch.indexOf(group)
40
+ const textEnd = textStart + group.length
41
+
42
+ initialStoredMarks = tr.storedMarks ?? []
43
+
44
+ if (textEnd < end) tr.delete(textEnd, end)
45
+
46
+ if (textStart > start) tr.delete(start + startSpaces, textStart)
47
+
48
+ markEnd = start + startSpaces + group.length
49
+
50
+ const attrs = options.getAttr?.(match)
51
+
52
+ tr.addMark(start, markEnd, markType.create(attrs))
53
+ tr.setStoredMarks(initialStoredMarks)
54
+
55
+ options.beforeDispatch?.({ match, start, end, tr })
56
+ }
57
+
58
+ return tr
59
+ })
60
+ }
@@ -0,0 +1,51 @@
1
+ import type { NodeType } from '../../model'
2
+ import type { Captured, Options } from './common'
3
+
4
+ import { InputRule } from '../../inputrules'
5
+
6
+ /// Create an input rule for a node.
7
+ export function nodeRule(
8
+ regexp: RegExp,
9
+ nodeType: NodeType,
10
+ options: Options = {}
11
+ ): InputRule {
12
+ return new InputRule(regexp, (state, match, start, end) => {
13
+ const { tr } = state
14
+ let group: string | undefined = match[1]
15
+ let fullMatch = match[0]
16
+
17
+ const captured: Captured = {
18
+ group,
19
+ fullMatch,
20
+ start,
21
+ end,
22
+ }
23
+
24
+ const result = options.updateCaptured?.(captured)
25
+ Object.assign(captured, result)
26
+ ;({ group, fullMatch, start, end } = captured)
27
+
28
+ if (fullMatch === null) return null
29
+
30
+ if (!group || group.trim() === '') return null
31
+
32
+ const attrs = options.getAttr?.(match)
33
+ const node = nodeType.createAndFill(attrs)
34
+
35
+ if (node) {
36
+ tr.replaceRangeWith(
37
+ nodeType.isBlock ? tr.doc.resolve(start).before() : start,
38
+ end,
39
+ node
40
+ )
41
+ options.beforeDispatch?.({
42
+ match: [fullMatch, group],
43
+ start,
44
+ end,
45
+ tr,
46
+ })
47
+ }
48
+
49
+ return tr
50
+ })
51
+ }
@@ -0,0 +1,108 @@
1
+ import { expectDomTypeError, missingRootElement } from '@jvs-milkdown/exception'
2
+
3
+ import type { EditorView } from '../../view'
4
+
5
+ type Point = [top: number, left: number]
6
+
7
+ export function calculateNodePosition(
8
+ view: EditorView,
9
+ target: HTMLElement,
10
+ handler: (
11
+ selectedRect: DOMRect,
12
+ targetRect: DOMRect,
13
+ parentRect: DOMRect
14
+ ) => Point
15
+ ) {
16
+ const state = view.state
17
+ const { from } = state.selection
18
+
19
+ const { node } = view.domAtPos(from)
20
+ const element = node instanceof Text ? node.parentElement : node
21
+ if (!(element instanceof HTMLElement)) throw expectDomTypeError(element)
22
+
23
+ const selectedNodeRect = element.getBoundingClientRect()
24
+ const targetNodeRect = target.getBoundingClientRect()
25
+ const parent = target.parentElement
26
+ if (!parent) throw expectDomTypeError(parent)
27
+
28
+ const parentNodeRect = parent.getBoundingClientRect()
29
+
30
+ const [top, left] = handler(selectedNodeRect, targetNodeRect, parentNodeRect)
31
+
32
+ target.style.top = `${top}px`
33
+ target.style.left = `${left}px`
34
+ }
35
+
36
+ interface Rect {
37
+ left: number
38
+ right: number
39
+ top: number
40
+ bottom: number
41
+ }
42
+
43
+ export function calculateTextPosition(
44
+ view: EditorView,
45
+ target: HTMLElement,
46
+ handler: (
47
+ start: Rect,
48
+ end: Rect,
49
+ targetRect: DOMRect,
50
+ parentRect: DOMRect
51
+ ) => Point
52
+ ) {
53
+ const state = view.state
54
+ const { from, to } = state.selection
55
+ const start = view.coordsAtPos(from)
56
+ const end = view.coordsAtPos(to)
57
+
58
+ const targetNodeRect = target.getBoundingClientRect()
59
+ const parent = target.parentElement
60
+ if (!parent) throw missingRootElement()
61
+
62
+ const parentNodeRect = parent.getBoundingClientRect()
63
+
64
+ const [top, left] = handler(start, end, targetNodeRect, parentNodeRect)
65
+
66
+ target.style.top = `${top}px`
67
+ target.style.left = `${left}px`
68
+ }
69
+
70
+ function minMax(value = 0, min = 0, max = 0): number {
71
+ return Math.min(Math.max(value, min), max)
72
+ }
73
+
74
+ export function posToDOMRect(
75
+ view: EditorView,
76
+ from: number,
77
+ to: number
78
+ ): DOMRect {
79
+ const minPos = 0
80
+ const maxPos = view.state.doc.content.size
81
+ const resolvedFrom = minMax(from, minPos, maxPos)
82
+ const resolvedEnd = minMax(to, minPos, maxPos)
83
+ const start = view.coordsAtPos(resolvedFrom)
84
+ const end = view.coordsAtPos(resolvedEnd, -1)
85
+ const top = Math.min(start.top, end.top)
86
+ const bottom = Math.max(start.bottom, end.bottom)
87
+ const left = Math.min(start.left, end.left)
88
+ const right = Math.max(start.right, end.right)
89
+ const width = right - left
90
+ const height = bottom - top
91
+ const x = left
92
+ const y = top
93
+ const data = {
94
+ top,
95
+ bottom,
96
+ left,
97
+ right,
98
+ width,
99
+ height,
100
+ x,
101
+ y,
102
+ }
103
+
104
+ return {
105
+ ...data,
106
+ toJSON: () => data,
107
+ }
108
+ }
@@ -0,0 +1,30 @@
1
+ import type { Slice, NodeType, Node as ProseNode } from '../../model'
2
+ import type { Transaction } from '../../state'
3
+
4
+ export function cloneTr(tr: Transaction): Transaction {
5
+ return Object.assign(Object.create(tr), tr).setTime(Date.now())
6
+ }
7
+
8
+ export function equalNodeType(
9
+ nodeType: NodeType | NodeType[],
10
+ node: ProseNode
11
+ ) {
12
+ return (
13
+ (Array.isArray(nodeType) && nodeType.includes(node.type)) ||
14
+ node.type === nodeType
15
+ )
16
+ }
17
+
18
+ export function isTextOnlySlice(slice: Slice): ProseNode | false {
19
+ if (slice.content.childCount === 1) {
20
+ const node = slice.content.firstChild
21
+ if (node?.type.name === 'text' && node.marks.length === 0) return node
22
+
23
+ if (node?.type.name === 'paragraph' && node.childCount === 1) {
24
+ const _node = node.firstChild
25
+ if (_node?.type.name === 'text' && _node.marks.length === 0) return _node
26
+ }
27
+ }
28
+
29
+ return false
30
+ }
@@ -0,0 +1,4 @@
1
+ export * from './helper'
2
+ export * from './node'
3
+ export * from './schema'
4
+ export * from './selection'