@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.empty && view.endOfTextblock(dir)) {
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
- EditorView, keymap as cmKeymap, drawSelection
4
- } from "@codemirror/view"
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 EditorView({
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
- EditorView.updateListener.of(update => this.forwardUpdate(update))
44
- ]
78
+ ],
45
79
  })
46
- // The editor's outer node is our DOM representation
47
- this.dom = this.cm.dom
48
80
 
49
- // This flag is used to avoid an update loop between the outer and
50
- // inner editor
51
- this.updating = false
81
+ this.cm.setState(cmState)
52
82
  }
53
83
 
54
- forwardUpdate(update) {
55
- if (this.updating || !this.cm.hasFocus) return
56
- let offset = this.getPos() + 1, {main} = update.state.selection
57
- let selection = TextSelection.create(this.view.state.doc,
58
- offset + main.from, offset + main.to)
59
- if (update.docChanged || !this.view.state.selection.eq(selection)) {
60
- let tr = this.view.state.tr.setSelection(selection)
61
- update.changes.iterChanges((fromA, toA, fromB, toB, text) => {
62
- if (text.length)
63
- tr.replaceWith(offset + fromA, offset + toA,
64
- this.schema.text(text.toString()))
65
- else
66
- tr.delete(offset + fromA, offset + toA)
67
- offset += (toB - fromB) - (toA - fromA)
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: () => this.maybeEscape("line", -1)},
77
- {key: "ArrowLeft", run: () => this.maybeEscape("char", -1)},
78
- {key: "ArrowDown", run: () => this.maybeEscape("line", 1)},
79
- {key: "ArrowRight", run: () => this.maybeEscape("char", 1)},
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
- let newText = node.textContent, curText = this.cm.state.doc.toString()
111
- if (newText != curText) {
112
- let start = 0, curEnd = curText.length, newEnd = newText.length
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
- selectNode() { this.cm.focus() }
135
- stopEvent() { return true }
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.23",
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": "1fd24d66ea25432bff4a9a9cb8435a489bfaa602"
103
+ "gitHead": "e1f5d14fd79629abbc3e272e171aea587b4db90a"
104
104
  }