@live-change/wysiwyg-frontend 0.2.23 → 0.2.25
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.
|
@@ -3,7 +3,7 @@ import CodeBlockView from "./CodeBlockView.js"
|
|
|
3
3
|
|
|
4
4
|
function arrowHandler(dir) {
|
|
5
5
|
return (state, dispatch, view) => {
|
|
6
|
-
if (state.selection
|
|
6
|
+
if (state.selection?.empty && view.endOfTextblock(dir)) {
|
|
7
7
|
let side = dir == "left" || dir == "up" ? -1 : 1
|
|
8
8
|
let $head = state.selection.$head
|
|
9
9
|
let nextPos = Selection.near(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { TextSelection } from 'prosemirror-state'
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
} from
|
|
1
|
+
import { Selection, TextSelection } from 'prosemirror-state'
|
|
2
|
+
import { Node as PMNode } from 'prosemirror-model';
|
|
3
|
+
import { EditorState as CMState, Transaction as CMTransaction } from '@codemirror/state';
|
|
4
|
+
import { EditorView as CMView, keymap as cmKeymap, drawSelection } from '@codemirror/view';
|
|
5
5
|
import {javascript} from "@codemirror/lang-javascript"
|
|
6
6
|
import {defaultKeymap} from "@codemirror/commands"
|
|
7
7
|
import {syntaxHighlighting, defaultHighlightStyle} from "@codemirror/language"
|
|
@@ -9,6 +9,23 @@ import {syntaxHighlighting, defaultHighlightStyle} from "@codemirror/language"
|
|
|
9
9
|
import {exitCode} from "prosemirror-commands"
|
|
10
10
|
import {undo, redo} from "prosemirror-history"
|
|
11
11
|
|
|
12
|
+
const computeChange = (oldVal, newVal) => {
|
|
13
|
+
if (oldVal === newVal) {
|
|
14
|
+
return null
|
|
15
|
+
}
|
|
16
|
+
let start = 0
|
|
17
|
+
let oldEnd = oldVal.length
|
|
18
|
+
let newEnd = newVal.length
|
|
19
|
+
while (start < oldEnd && oldVal.charCodeAt(start) === newVal.charCodeAt(start)) {
|
|
20
|
+
start += 1
|
|
21
|
+
}
|
|
22
|
+
while (oldEnd > start && newEnd > start && oldVal.charCodeAt(oldEnd - 1) === newVal.charCodeAt(newEnd - 1)) {
|
|
23
|
+
oldEnd -= 1
|
|
24
|
+
newEnd -= 1
|
|
25
|
+
}
|
|
26
|
+
return { from: start, to: oldEnd, text: newVal.slice(start, newEnd) }
|
|
27
|
+
}
|
|
28
|
+
|
|
12
29
|
class CodeBlockView {
|
|
13
30
|
constructor(node, editor, getPos) {
|
|
14
31
|
// Store for later
|
|
@@ -19,6 +36,17 @@ class CodeBlockView {
|
|
|
19
36
|
|
|
20
37
|
this.language = node.attrs.language
|
|
21
38
|
|
|
39
|
+
// This flag is used to avoid an update loop between the outer and
|
|
40
|
+
// inner editor
|
|
41
|
+
this.updating = false
|
|
42
|
+
|
|
43
|
+
const changeFilter = CMState.changeFilter.of((tr) => {
|
|
44
|
+
if (!tr.docChanged && !this.updating) {
|
|
45
|
+
this.forwardSelection()
|
|
46
|
+
}
|
|
47
|
+
return true
|
|
48
|
+
})
|
|
49
|
+
|
|
22
50
|
// Select language plugin
|
|
23
51
|
let languagePlugins = []
|
|
24
52
|
switch(this.language) {
|
|
@@ -30,9 +58,16 @@ class CodeBlockView {
|
|
|
30
58
|
}
|
|
31
59
|
|
|
32
60
|
// Create a CodeMirror instance
|
|
33
|
-
this.cm = new
|
|
61
|
+
this.cm = new CMView({
|
|
62
|
+
dispatch: this.dispatch.bind(this),
|
|
63
|
+
})
|
|
64
|
+
// The editor's outer node is our DOM representation
|
|
65
|
+
this.dom = this.cm.dom
|
|
66
|
+
|
|
67
|
+
const cmState = CMState.create({
|
|
34
68
|
doc: this.node.textContent,
|
|
35
69
|
extensions: [
|
|
70
|
+
changeFilter,
|
|
36
71
|
cmKeymap.of([
|
|
37
72
|
...this.codeMirrorKeymap(),
|
|
38
73
|
...defaultKeymap
|
|
@@ -40,43 +75,82 @@ class CodeBlockView {
|
|
|
40
75
|
drawSelection(),
|
|
41
76
|
syntaxHighlighting(defaultHighlightStyle),
|
|
42
77
|
...languagePlugins,
|
|
43
|
-
|
|
44
|
-
]
|
|
78
|
+
],
|
|
45
79
|
})
|
|
46
|
-
// The editor's outer node is our DOM representation
|
|
47
|
-
this.dom = this.cm.dom
|
|
48
80
|
|
|
49
|
-
|
|
50
|
-
// inner editor
|
|
51
|
-
this.updating = false
|
|
81
|
+
this.cm.setState(cmState)
|
|
52
82
|
}
|
|
53
83
|
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
84
|
+
forwardSelection() {
|
|
85
|
+
if (!this.cm.hasFocus) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const { state } = this.view;
|
|
90
|
+
const selection = this.asProseMirrorSelection(state.doc)
|
|
91
|
+
|
|
92
|
+
if (!selection.eq(state.selection)) {
|
|
93
|
+
this.view.dispatch(state.tr.setSelection(selection))
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
asProseMirrorSelection(doc) {
|
|
98
|
+
const offset = this.getPos() + 1
|
|
99
|
+
const { anchor, head } = this.cm.state.selection.main
|
|
100
|
+
return TextSelection.create(doc, anchor + offset, head + offset)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
dispatch(cmTr) {
|
|
104
|
+
this.cm.setState(cmTr.state);
|
|
105
|
+
|
|
106
|
+
if (cmTr.docChanged && !this.updating) {
|
|
107
|
+
const start = this.getPos() + 1
|
|
108
|
+
const cmValue = cmTr.state.doc.toString()
|
|
109
|
+
const change = computeChange(this.node.textContent, cmValue)
|
|
110
|
+
if (!change) {
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
const content = change.text ? this.view.state.schema.text(change.text) : null
|
|
114
|
+
const tr = this.view.state.tr.replaceWith(change.from + start, change.to + start, content)
|
|
69
115
|
this.view.dispatch(tr)
|
|
116
|
+
this.forwardSelection()
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
maybeEscape(unit, dir) {
|
|
121
|
+
return (view) => {
|
|
122
|
+
const { state } = view
|
|
123
|
+
const { selection } = state
|
|
124
|
+
console.log("maybeEscape", unit, dir, "AT", selection.from, selection.to)
|
|
125
|
+
const offsetToPos = () => {
|
|
126
|
+
const offset = selection.main.from
|
|
127
|
+
const line = state.doc.lineAt(offset)
|
|
128
|
+
return { line: line.number, ch: offset - line.from }
|
|
129
|
+
};
|
|
130
|
+
const pos = offsetToPos()
|
|
131
|
+
const hasSelection = state.selection.ranges.some((r) => !r.empty)
|
|
132
|
+
const firstLine = 1
|
|
133
|
+
const lastLine = state.doc.lineAt(state.doc.length).number
|
|
134
|
+
if (
|
|
135
|
+
hasSelection
|
|
136
|
+
|| pos.line !== (dir < 0 ? firstLine : lastLine)
|
|
137
|
+
|| (unit === 'char' && pos.ch !== (dir < 0 ? 0 : state.doc.line(pos.line).length))
|
|
138
|
+
) return false
|
|
139
|
+
const targetPos = this.getPos() + (dir < 0 ? 0 : this.node.nodeSize)
|
|
140
|
+
const pmSelection = Selection.near(this.view.state.doc.resolve(targetPos), dir)
|
|
141
|
+
this.view.dispatch(this.view.state.tr.setSelection(pmSelection).scrollIntoView())
|
|
142
|
+
this.view.focus()
|
|
143
|
+
return true
|
|
70
144
|
}
|
|
71
145
|
}
|
|
72
146
|
|
|
73
147
|
codeMirrorKeymap() {
|
|
74
148
|
let view = this.view
|
|
75
149
|
return [
|
|
76
|
-
{key: "ArrowUp", run:
|
|
77
|
-
{key: "ArrowLeft", run:
|
|
78
|
-
{key: "ArrowDown", run:
|
|
79
|
-
{key: "ArrowRight", run:
|
|
150
|
+
{key: "ArrowUp", run: this.maybeEscape("line", -1)},
|
|
151
|
+
{key: "ArrowLeft", run: this.maybeEscape("char", -1)},
|
|
152
|
+
{key: "ArrowDown", run: this.maybeEscape("line", 1)},
|
|
153
|
+
{key: "ArrowRight", run: this.maybeEscape("char", 1)},
|
|
80
154
|
{key: "Ctrl-Enter", run: () => {
|
|
81
155
|
if (!exitCode(view.state, view.dispatch)) return false
|
|
82
156
|
view.focus()
|
|
@@ -91,48 +165,38 @@ class CodeBlockView {
|
|
|
91
165
|
]
|
|
92
166
|
}
|
|
93
167
|
|
|
94
|
-
maybeEscape(unit, dir) {
|
|
95
|
-
let {state} = this.cm, {main} = state.selection
|
|
96
|
-
if (!main.empty) return false
|
|
97
|
-
if (unit == "line") main = state.doc.lineAt(main.head)
|
|
98
|
-
if (dir < 0 ? main.from > 0 : main.to < state.doc.length) return false
|
|
99
|
-
let targetPos = this.getPos() + (dir < 0 ? 0 : this.node.nodeSize)
|
|
100
|
-
let selection = Selection.near(this.view.state.doc.resolve(targetPos), dir)
|
|
101
|
-
let tr = this.view.state.tr.setSelection(selection).scrollIntoView()
|
|
102
|
-
this.view.dispatch(tr)
|
|
103
|
-
this.view.focus()
|
|
104
|
-
}
|
|
105
|
-
|
|
106
168
|
update(node) {
|
|
107
169
|
if (node.type != this.node.type) return false
|
|
108
170
|
if (node.attrs.language != this.language) return false
|
|
109
|
-
if (this.updating) return true
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
while (start < curEnd &&
|
|
114
|
-
curText.charCodeAt(start) == newText.charCodeAt(start)) {
|
|
115
|
-
++start
|
|
116
|
-
}
|
|
117
|
-
while (curEnd > start && newEnd > start &&
|
|
118
|
-
curText.charCodeAt(curEnd - 1) == newText.charCodeAt(newEnd - 1)) {
|
|
119
|
-
curEnd--
|
|
120
|
-
newEnd--
|
|
121
|
-
}
|
|
171
|
+
//if (this.updating) return true
|
|
172
|
+
this.node = node
|
|
173
|
+
const change = computeChange(this.cm.state.doc.toString(), node.textContent)
|
|
174
|
+
if (change) {
|
|
122
175
|
this.updating = true
|
|
123
176
|
this.cm.dispatch({
|
|
124
|
-
changes: {
|
|
125
|
-
from: start, to: curEnd,
|
|
126
|
-
insert: newText.slice(start, newEnd)
|
|
127
|
-
}
|
|
177
|
+
changes: { from: change.from, to: change.to, insert: change.text },
|
|
128
178
|
})
|
|
129
179
|
this.updating = false
|
|
130
180
|
}
|
|
131
181
|
return true
|
|
132
182
|
}
|
|
133
183
|
|
|
134
|
-
|
|
135
|
-
|
|
184
|
+
focus() {
|
|
185
|
+
this.cm.focus()
|
|
186
|
+
this.forwardSelection()
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
selectNode() {
|
|
190
|
+
this.focus()
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
stopEvent() {
|
|
194
|
+
return true
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
destroy() {
|
|
198
|
+
this.cm.destroy()
|
|
199
|
+
}
|
|
136
200
|
}
|
|
137
201
|
|
|
138
202
|
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
<ComponentEditor v-bind="{
|
|
4
4
|
...attrs, class: ['relative', editorClass]
|
|
5
5
|
}">
|
|
6
|
-
<node-view-content :class="[editorContentClass]"
|
|
7
|
-
:style="style" />
|
|
6
|
+
<node-view-content :class="[editorContentClass]" />
|
|
8
7
|
<div class="absolute top-0 right-0 pr-4 max-h-0 align-items-center z-5 edit-buttons ">
|
|
9
8
|
<Button icon="pi pi-eye"
|
|
10
9
|
:class="[
|
|
@@ -42,7 +42,7 @@ export const richEditorNodes = {
|
|
|
42
42
|
heading: ({ content, attrs }, r) => h('h'+(+attrs.level), { }, r(content)),
|
|
43
43
|
blockquote: ({ content }, r) => h('blockquote', { }, r(content)),
|
|
44
44
|
codeBlock: ({ content }, r) => {
|
|
45
|
-
const code = content.map(t => t.text).join('')
|
|
45
|
+
const code = (content ?? []).map(t => t.text).join('')
|
|
46
46
|
const tree = javascriptParser.parse(code)
|
|
47
47
|
let pos = 0
|
|
48
48
|
let output = []
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-change/wysiwyg-frontend",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.25",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"memDev": "lcli memDev --enableSessions --initScript ./init.js --dbAccess",
|
|
6
6
|
"localDevInit": "rm tmp.db; lcli localDev --enableSessions --initScript ./init.js",
|
|
@@ -100,5 +100,5 @@
|
|
|
100
100
|
"author": "",
|
|
101
101
|
"license": "ISC",
|
|
102
102
|
"description": "",
|
|
103
|
-
"gitHead": "
|
|
103
|
+
"gitHead": "e1f5d14fd79629abbc3e272e171aea587b4db90a"
|
|
104
104
|
}
|