vzcode 0.74.0 → 0.76.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.
package/dist/index.html CHANGED
@@ -20,8 +20,8 @@
20
20
  href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap"
21
21
  rel="stylesheet"
22
22
  />
23
- <script type="module" crossorigin src="/assets/index-BaaNjNJK.js"></script>
24
- <link rel="stylesheet" crossorigin href="/assets/index-Buql6gU0.css">
23
+ <script type="module" crossorigin src="/assets/index--cETBO9l.js"></script>
24
+ <link rel="stylesheet" crossorigin href="/assets/index-BpuOxvvE.css">
25
25
  </head>
26
26
  <body>
27
27
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vzcode",
3
- "version": "0.74.0",
3
+ "version": "0.76.0",
4
4
  "description": "Multiplayer code editor system",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -63,7 +63,7 @@
63
63
  },
64
64
  "homepage": "https://github.com/vizhub-core/vzcode#readme",
65
65
  "dependencies": {
66
- "@codemirror/autocomplete": "^6.14.0",
66
+ "@codemirror/autocomplete": "^6.15.0",
67
67
  "@codemirror/lang-css": "^6.2.1",
68
68
  "@codemirror/lang-html": "^6.4.8",
69
69
  "@codemirror/lang-javascript": "^6.2.2",
@@ -90,7 +90,7 @@
90
90
  "@uiw/codemirror-themes": "^4.21.24",
91
91
  "body-parser": "^1.20.2",
92
92
  "codemirror": "^6.0.1",
93
- "codemirror-ot": "^4.3.0",
93
+ "codemirror-ot": "^4.4.0",
94
94
  "color-hash": "^2.0.2",
95
95
  "d3-array": "^3.2.4",
96
96
  "diff-match-patch": "^1.0.5",
@@ -112,14 +112,14 @@
112
112
  "ws": "^8.16.0"
113
113
  },
114
114
  "devDependencies": {
115
- "@types/react": "^18.2.64",
116
- "@types/react-dom": "^18.2.21",
115
+ "@types/react": "^18.2.65",
116
+ "@types/react-dom": "^18.2.22",
117
117
  "@vitejs/plugin-react": "^4.2.1",
118
118
  "concurrently": "^8.2.2",
119
119
  "prettier": "^3.2.5",
120
120
  "sass": "^1.71.1",
121
121
  "typescript": "^5.4.2",
122
- "vite": "^5.1.5",
122
+ "vite": "^5.1.6",
123
123
  "vitest": "^1.3.1"
124
124
  }
125
125
  }
@@ -0,0 +1,409 @@
1
+ // From https://github.com/replit/codemirror-interact/blob/master/src/interact.ts
2
+ // as of March 13, 2024
3
+ // Copied into this repo so that we can leverage the changes in PR
4
+ // https://github.com/replit/codemirror-interact/pull/19
5
+ // TODO move back to using the npm package once the PR is merged
6
+
7
+ // TODO: custom style
8
+ // TODO: custom state for each rule?
9
+ import {
10
+ EditorView,
11
+ ViewPlugin,
12
+ PluginValue,
13
+ Decoration,
14
+ } from '@codemirror/view';
15
+ import {
16
+ StateEffect,
17
+ StateField,
18
+ Facet,
19
+ MapMode,
20
+ } from '@codemirror/state';
21
+
22
+ interface Target {
23
+ pos: number;
24
+ text: string;
25
+ rule: InteractRule;
26
+ }
27
+
28
+ export interface InteractRule {
29
+ regexp: RegExp;
30
+ cursor?: string;
31
+ style?: any;
32
+ className?: string;
33
+ onClick?: (
34
+ text: string,
35
+ setText: (t: string) => void,
36
+ e: MouseEvent,
37
+ ) => void;
38
+ onDrag?: (
39
+ text: string,
40
+ setText: (t: string) => void,
41
+ e: MouseEvent,
42
+ ) => void;
43
+ onDragStart?: (
44
+ text: string,
45
+ setText: (t: string) => void,
46
+ e: MouseEvent,
47
+ ) => void;
48
+ onDragEnd?: (
49
+ text: string,
50
+ setText: (t: string) => void,
51
+ ) => void;
52
+ }
53
+
54
+ const interactField = StateField.define<Target | null>({
55
+ create: () => null,
56
+ update: (value, tr) => {
57
+ for (const e of tr.effects) {
58
+ if (e.is(setInteract)) {
59
+ return e.value;
60
+ }
61
+ }
62
+
63
+ if (!value) {
64
+ return null;
65
+ }
66
+
67
+ if (!tr.changes.empty) {
68
+ const newPos = tr.changes.mapPos(
69
+ value.pos,
70
+ -1,
71
+ MapMode.TrackDel,
72
+ );
73
+ const newEnd = tr.changes.mapPos(
74
+ value.pos + value.text.length,
75
+ -1,
76
+ MapMode.TrackDel,
77
+ );
78
+
79
+ if (newPos === null || newEnd === null) {
80
+ return null;
81
+ }
82
+
83
+ // if the text doesn't match anymore, we'll just return null
84
+ // rather than checking if the rule matches again
85
+ if (
86
+ tr.newDoc.sliceString(newPos, newEnd) !== value.text
87
+ ) {
88
+ return null;
89
+ }
90
+
91
+ return { ...value, pos: newPos };
92
+ }
93
+
94
+ return value;
95
+ },
96
+
97
+ provide: (field) => [
98
+ EditorView.decorations.from(field, (target) => {
99
+ if (!target) {
100
+ return Decoration.none;
101
+ }
102
+
103
+ const from = target.pos;
104
+ const to = target.pos + target.text.length;
105
+ const className = target.rule.className;
106
+
107
+ return Decoration.set(
108
+ mark({ className }).range(from, to),
109
+ );
110
+ }),
111
+ EditorView.contentAttributes.from(field, (target) => {
112
+ if (!target || !target.rule.cursor) {
113
+ return { style: '' };
114
+ }
115
+
116
+ return { style: `cursor: ${target.rule.cursor}` };
117
+ }),
118
+ ],
119
+ });
120
+
121
+ const setInteract = StateEffect.define<Target | null>();
122
+
123
+ const mark = (e: { className?: string }) =>
124
+ Decoration.mark({
125
+ class: `cm-interact ${e?.className ?? ''}`,
126
+ });
127
+
128
+ const interactTheme = EditorView.theme({
129
+ '.cm-interact': {
130
+ background: 'rgba(128, 128, 255, 0.2)',
131
+ borderRadius: '4px',
132
+ },
133
+ });
134
+
135
+ /**
136
+ * A rule that defines a type of value and its interaction.
137
+ *
138
+ * @example
139
+ * ```
140
+ * // a number dragger
141
+ * interactRule.of({
142
+ * // the regexp matching the value
143
+ * regexp: /-?\b\d+\.?\d*\b/g,
144
+ * // set cursor to 'ew-resize'on hover
145
+ * cursor: 'ew-resize'
146
+ * // change number value based on mouse X movement on drag
147
+ * onDrag: (text, setText, e) => {
148
+ * const newVal = Number(text) + e.movementX;
149
+ * if (isNaN(newVal)) return;
150
+ * setText(newVal.toString());
151
+ * },
152
+ * })
153
+ * ```
154
+ */
155
+ export const interactRule = Facet.define<InteractRule>();
156
+
157
+ export const interactModKey = Facet.define<ModKey, ModKey>({
158
+ combine: (values) => values[values.length - 1],
159
+ });
160
+
161
+ interface ViewState extends PluginValue {
162
+ target: Target | null;
163
+ dragging: boolean;
164
+ mouseX: number;
165
+ mouseY: number;
166
+ getMatch(): Target | null;
167
+ updateText(target: Target): (text: string) => void;
168
+ setTarget(target: Target | null): void;
169
+ isModKeyDown(e: KeyboardEvent | MouseEvent): boolean;
170
+ startDrag(e: MouseEvent): void;
171
+ endDrag(): void;
172
+ }
173
+
174
+ const interactViewPlugin = ViewPlugin.define<ViewState>(
175
+ (view) => ({
176
+ target: null,
177
+ dragging: false,
178
+ mouseX: 0,
179
+ mouseY: 0,
180
+
181
+ // Get current match under cursor from all rules
182
+ getMatch() {
183
+ const rules = view.state.facet(interactRule);
184
+ const pos = view.posAtCoords({
185
+ x: this.mouseX,
186
+ y: this.mouseY,
187
+ });
188
+ if (!pos) return null;
189
+ const line = view.state.doc.lineAt(pos);
190
+ const lpos = pos - line.from;
191
+ let match = null;
192
+
193
+ for (const rule of rules) {
194
+ // @ts-ignore
195
+ for (const m of line.text.matchAll(rule.regexp)) {
196
+ if (m.index === undefined) continue;
197
+ const text = m[0];
198
+ if (!text) continue;
199
+ const start = m.index;
200
+ const end = m.index + text.length;
201
+ if (lpos < start || lpos > end) continue;
202
+ // If there are overlap matches from different rules, use the smaller one
203
+ if (!match || text.length < match.text.length) {
204
+ match = {
205
+ rule: rule,
206
+ pos: line.from + start,
207
+ text: text,
208
+ };
209
+ }
210
+ }
211
+ }
212
+
213
+ return match;
214
+ },
215
+
216
+ updateText(target) {
217
+ return (text) => {
218
+ view.dispatch({
219
+ effects: setInteract.of({ ...target, text }),
220
+ changes: {
221
+ from: target.pos,
222
+ to: target.pos + target.text.length,
223
+ insert: text,
224
+ },
225
+ });
226
+ };
227
+ },
228
+
229
+ setTarget(target) {
230
+ this.target = target;
231
+ view.dispatch({ effects: setInteract.of(target) });
232
+ },
233
+
234
+ isModKeyDown(e) {
235
+ const modkey = view.state.facet(interactModKey);
236
+
237
+ const isMac =
238
+ Boolean(window.navigator) &&
239
+ window.navigator.userAgent.includes('Macintosh');
240
+
241
+ switch (modkey) {
242
+ case 'alt':
243
+ return e.altKey;
244
+ case 'shift':
245
+ return e.shiftKey;
246
+ case 'ctrl':
247
+ return e.ctrlKey;
248
+ case 'meta':
249
+ return e.metaKey;
250
+ case 'mod':
251
+ return isMac ? e.metaKey : e.ctrlKey;
252
+ }
253
+
254
+ throw new Error(`Invalid mod key: ${modkey}`);
255
+ },
256
+
257
+ update(update) {
258
+ const target = update.state.field(
259
+ interactField,
260
+ false,
261
+ );
262
+
263
+ // the field isn't mounted
264
+ if (target === undefined) {
265
+ return;
266
+ }
267
+
268
+ if (this.target !== target) {
269
+ this.target = target;
270
+ if (target === null) {
271
+ this.endDrag();
272
+ }
273
+ }
274
+ },
275
+
276
+ startDrag(e: MouseEvent) {
277
+ if (this.dragging) return;
278
+ if (!this.target) return;
279
+ this.dragging = true;
280
+ if (!this.target.rule.onDragStart) return;
281
+ this.target.rule.onDragStart(
282
+ this.target.text,
283
+ this.updateText(this.target),
284
+ e,
285
+ );
286
+ },
287
+
288
+ endDrag() {
289
+ if (!this.dragging) return;
290
+ this.dragging = false;
291
+ if (!this.target?.rule.onDragEnd) return;
292
+ this.target.rule.onDragEnd(
293
+ this.target.text,
294
+ this.updateText(this.target),
295
+ );
296
+ },
297
+ }),
298
+ {
299
+ eventHandlers: {
300
+ mousedown(e, _view) {
301
+ if (!this.isModKeyDown(e)) return;
302
+ if (!this.target) return;
303
+
304
+ e.preventDefault();
305
+
306
+ // if (this.target.rule.onClick) {
307
+ // this.target.rule.onClick(
308
+ // this.target.text,
309
+ // this.updateText(this.target),
310
+ // e,
311
+ // );
312
+ // }
313
+
314
+ if (this.target.rule.onClick) {
315
+ this.target.rule.onClick(
316
+ this.target.text,
317
+ (text) => {
318
+ this.target &&
319
+ this.updateText(this.target)(text);
320
+ },
321
+ e,
322
+ );
323
+ }
324
+
325
+ if (this.target.rule.onDrag) {
326
+ this.startDrag(e);
327
+ }
328
+ },
329
+
330
+ mousemove(e, _view) {
331
+ this.mouseX = e.clientX;
332
+ this.mouseY = e.clientY;
333
+
334
+ if (!this.isModKeyDown(e)) {
335
+ if (this.target) {
336
+ this.setTarget(null);
337
+ }
338
+
339
+ return;
340
+ }
341
+
342
+ if (this.target && this.dragging) {
343
+ if (this.target.rule.onDrag) {
344
+ this.target.rule.onDrag(
345
+ this.target.text,
346
+ this.updateText(this.target),
347
+ e,
348
+ );
349
+ }
350
+ } else {
351
+ this.setTarget(this.getMatch());
352
+ }
353
+ },
354
+
355
+ mouseup(e, _view) {
356
+ this.endDrag();
357
+
358
+ if (this.target && !this.isModKeyDown(e)) {
359
+ this.setTarget(null);
360
+ }
361
+
362
+ if (this.isModKeyDown(e)) {
363
+ this.setTarget(this.getMatch());
364
+ }
365
+ },
366
+
367
+ mouseleave(e, _view) {
368
+ this.endDrag();
369
+ if (this.target) {
370
+ this.setTarget(null);
371
+ }
372
+ },
373
+
374
+ // TODO: fix these keybindings
375
+ // these currently don't do anything because CodeMirror's keybinding
376
+ // system prevents these events from firing.
377
+
378
+ keydown(e, _view) {
379
+ if (!this.target && this.isModKeyDown(e)) {
380
+ this.setTarget(this.getMatch());
381
+ }
382
+ },
383
+
384
+ keyup(e, _view) {
385
+ if (this.target && !this.isModKeyDown(e)) {
386
+ this.endDrag();
387
+ this.setTarget(null);
388
+ }
389
+ },
390
+ },
391
+ },
392
+ );
393
+
394
+ type ModKey = 'alt' | 'shift' | 'meta' | 'ctrl' | 'mod';
395
+
396
+ interface InteractConfig {
397
+ rules?: InteractRule[];
398
+ key?: ModKey;
399
+ }
400
+
401
+ const interact = (cfg: InteractConfig = {}) => [
402
+ interactField,
403
+ interactTheme,
404
+ interactViewPlugin,
405
+ interactModKey.of(cfg.key ?? 'alt'),
406
+ (cfg.rules ?? []).map((r) => interactRule.of(r)),
407
+ ];
408
+
409
+ export default interact;
@@ -6,6 +6,7 @@
6
6
 
7
7
  .cm-editor {
8
8
  flex: 1;
9
+ width: 100%;
9
10
  }
10
11
 
11
12
  .cm-scroller {
@@ -1,15 +1,14 @@
1
- import interact from '@replit/codemirror-interact';
1
+ // TODO move back to this way of importing when this PR is merged:
2
+ // https://github.com/replit/codemirror-interact/pull/19/files
3
+ // import interact from '@replit/codemirror-interact';
4
+ import interact from './codemirror-interact';
2
5
 
3
6
  import {
4
7
  ViewPlugin,
5
8
  Decoration,
6
9
  WidgetType,
7
10
  } from '@codemirror/view';
8
- import {
9
- Annotation,
10
- Extension,
11
- RangeSet,
12
- } from '@codemirror/state';
11
+ import { Extension, RangeSet } from '@codemirror/state';
13
12
  import { EditorView } from 'codemirror';
14
13
 
15
14
  // Interactive code widgets.
@@ -149,7 +149,7 @@ export const Item = ({
149
149
  >
150
150
  <div className="name">
151
151
  {isRenaming ? (
152
- <React.Fragment>
152
+ <>
153
153
  <i className="file-icon">
154
154
  <FileSVG />
155
155
  </i>
@@ -163,7 +163,7 @@ export const Item = ({
163
163
  onBlur={onBlur}
164
164
  onChange={onChange}
165
165
  />
166
- </React.Fragment>
166
+ </>
167
167
  ) : (
168
168
  children
169
169
  )}
@@ -100,10 +100,9 @@ export const generateAIResponse = async ({
100
100
 
101
101
  shareDBDoc.submitOp(op, { source: AISourceName });
102
102
 
103
- // Wait for 500ms
104
103
  if (slowdown) {
105
104
  await new Promise((resolve) => {
106
- setTimeout(resolve, 1000);
105
+ setTimeout(resolve, 2000);
107
106
  });
108
107
  }
109
108