vzcode 0.42.0 → 0.44.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
@@ -24,8 +24,8 @@
24
24
  href="https://unpkg.com/boxicons@2.0.7/css/boxicons.min.css"
25
25
  rel="stylesheet"
26
26
  />
27
- <script type="module" crossorigin src="/assets/index-EvjpKQwB.js"></script>
28
- <link rel="stylesheet" crossorigin href="/assets/index-vD_kE3p2.css">
27
+ <script type="module" crossorigin src="/assets/index-f4vqBOE4.js"></script>
28
+ <link rel="stylesheet" crossorigin href="/assets/index-7MNYa9sP.css">
29
29
  </head>
30
30
  <body>
31
31
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vzcode",
3
- "version": "0.42.0",
3
+ "version": "0.44.0",
4
4
  "description": "Multiplayer code editor system",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -43,6 +43,18 @@
43
43
  {
44
44
  "name": "Anooj Pai",
45
45
  "url": "https://github.com/Anooj-Pai"
46
+ },
47
+ {
48
+ "name": "Jack Hankin",
49
+ "url": "https://github.com/Darmin-Geek"
50
+ },
51
+ {
52
+ "name": "Amanda Ruan",
53
+ "url": "https://github.com/aruan20"
54
+ },
55
+ {
56
+ "name": "Evan Whymark",
57
+ "url": "https://github.com/EvWhymark"
46
58
  }
47
59
  ],
48
60
  "license": "MIT",
@@ -59,11 +71,11 @@
59
71
  "@codemirror/lang-markdown": "^6.2.3",
60
72
  "@codemirror/state": "^6.3.3",
61
73
  "@codemirror/theme-one-dark": "^6.1.2",
62
- "@codemirror/view": "^6.22.1",
74
+ "@codemirror/view": "^6.22.3",
63
75
  "@lezer/highlight": "^1.2.0",
64
76
  "@replit/codemirror-indentation-markers": "^6.5.0",
65
77
  "@replit/codemirror-interact": "^6.3.0",
66
- "@replit/codemirror-minimap": "^0.5.1",
78
+ "@replit/codemirror-minimap": "^0.5.2",
67
79
  "@replit/codemirror-vscode-keymap": "^6.0.2",
68
80
  "@teamwork/websocket-json-stream": "^2.0.0",
69
81
  "@typescript/vfs": "^1.5.0",
@@ -84,26 +96,27 @@
84
96
  "diff-match-patch": "^1.0.5",
85
97
  "dotenv": "^16.3.1",
86
98
  "express": "^4.18.2",
99
+ "ignore": "^5.3.0",
87
100
  "json0-ot-diff": "^1.1.2",
88
101
  "ngrok": "^4.3.3",
89
102
  "open": "^9.1.0",
90
- "openai": "^4.20.1",
103
+ "openai": "^4.23.0",
91
104
  "react": "^18.2.0",
92
105
  "react-bootstrap": "^2.9.1",
93
106
  "react-dom": "^18.2.0",
94
107
  "sharedb": "^4.1.2",
95
108
  "sharedb-client-browser": "^4.4.0",
96
- "vizhub-ui": "^3.10.0",
97
- "ws": "^8.14.2"
109
+ "vizhub-ui": "^3.13.0",
110
+ "ws": "^8.15.1"
98
111
  },
99
112
  "devDependencies": {
100
- "@types/react": "^18.2.42",
101
- "@types/react-dom": "^18.2.17",
113
+ "@types/react": "^18.2.45",
114
+ "@types/react-dom": "^18.2.18",
102
115
  "@vitejs/plugin-react": "^4.2.1",
103
- "prettier": "^3.1.0",
116
+ "prettier": "^3.1.1",
104
117
  "sass": "^1.69.5",
105
118
  "typescript": "^5.3.3",
106
- "vite": "^5.0.6",
107
- "vitest": "^1.0.1"
119
+ "vite": "^5.0.10",
120
+ "vitest": "^1.0.4"
108
121
  }
109
122
  }
@@ -294,7 +294,20 @@ export const getOrCreateEditor = ({
294
294
 
295
295
  // VSCode keybindings
296
296
  // See https://github.com/replit/codemirror-vscode-keymap#usage
297
- extensions.push(keymap.of(vscodeKeymap));
297
+ // extensions.push(keymap.of(vscodeKeymap));
298
+ extensions.push(
299
+ keymap.of(
300
+ vscodeKeymap.map((binding) => {
301
+ // Here we override the Shift+Enter behavior specifically,
302
+ // as that can be used to trigger a manual save/Prettier,
303
+ // and the default behavior from the keymap interferes.
304
+ if (binding.key === 'Enter') {
305
+ delete binding.shift;
306
+ }
307
+ return binding;
308
+ }),
309
+ ),
310
+ );
298
311
 
299
312
  const editor = new EditorView({
300
313
  state: EditorState.create({
@@ -4,7 +4,6 @@ import {
4
4
  ViewPlugin,
5
5
  Decoration,
6
6
  WidgetType,
7
- keymap,
8
7
  } from '@codemirror/view';
9
8
  import {
10
9
  Annotation,
@@ -30,15 +29,17 @@ export const widgets = ({
30
29
  rules: [
31
30
  // hex color picker
32
31
  // Inspired by https://github.com/replit/codemirror-interact/blob/master/dev/index.ts#L71
32
+ // Extended to support single and double quotes
33
33
  {
34
- regexp: /\"\#([0-9]|[A-F]|[a-f]){6}\"/g,
34
+ regexp: /["']\#([0-9]|[A-F]|[a-f]){6}["']/g,
35
35
  cursor: 'pointer',
36
36
  onClick(text, setText, e) {
37
37
  const res =
38
- /\"(?<hex>\#([0-9]|[A-F]|[a-f]){6})\"/.exec(
38
+ /(?<quote>["'])(?<hex>\#([0-9]|[A-F]|[a-f]){6})\k<quote>/.exec(
39
39
  text,
40
40
  );
41
41
  const startingColor = res.groups?.hex;
42
+ const quoteType = res.groups?.quote;
42
43
 
43
44
  const sel = document.createElement('input');
44
45
  sel.type = 'color';
@@ -54,11 +55,11 @@ export const widgets = ({
54
55
  if (onInteract) onInteract();
55
56
  if (el.value) {
56
57
  setText(
57
- `"${
58
+ `${quoteType}${
58
59
  valueIsUpper
59
60
  ? el.value.toUpperCase()
60
61
  : el.value
61
- }"`,
62
+ }${quoteType}`,
62
63
  );
63
64
  }
64
65
  };
@@ -210,11 +211,12 @@ const rgb2Hex = (r: number, g: number, b: number): string =>
210
211
  '#' + r.toString(16) + g.toString(16) + b.toString(16);
211
212
 
212
213
  const colorCircleTheme = EditorView.baseTheme({
213
- '.color-circle-parent': { display: 'inline-block' },
214
+ '.color-circle-parent': {
215
+ display: 'inline-block',
216
+ cursor: 'inherit',
217
+ },
214
218
  });
215
219
 
216
- const colorCircleAnnotation = Annotation.define();
217
-
218
220
  export const colorsInTextPlugin: Extension = [
219
221
  ViewPlugin.fromClass(
220
222
  class {
@@ -232,7 +234,8 @@ export const colorsInTextPlugin: Extension = [
232
234
  const lines = v.view.state.doc.iter();
233
235
  let line = lines.next();
234
236
 
235
- //Offset is the number of characters before the hex so the circle can be placed properly.
237
+ // Offset is the number of characters before the hex
238
+ // so the circle can be placed properly.
236
239
  let offset = 0;
237
240
  while (!line.done) {
238
241
  if (line.value === '\n') {
@@ -240,15 +243,16 @@ export const colorsInTextPlugin: Extension = [
240
243
  line = lines.next();
241
244
  continue;
242
245
  }
243
- const hexColorOccurances = line.value.matchAll(
244
- /\"\#([0-9]|[A-F]|[a-f]){6}\"/g,
246
+ const hexColorOccurences = line.value.matchAll(
247
+ // /\"\#([0-9]|[A-F]|[a-f]){6}\"/g,
248
+ /["']\#([0-9]|[A-F]|[a-f]){6}["']/g,
245
249
  );
246
- let hexOccurance = hexColorOccurances.next();
250
+ let hexOccurance = hexColorOccurences.next();
247
251
  while (!hexOccurance.done) {
248
252
  const offsetColorInfo = hexOccurance.value;
249
253
  offsetColorInfo.index += offset;
250
254
  colorInfos.push(offsetColorInfo);
251
- hexOccurance = hexColorOccurances.next();
255
+ hexOccurance = hexColorOccurences.next();
252
256
  }
253
257
  offset += line.value.length;
254
258
  line = lines.next();
@@ -257,7 +261,7 @@ export const colorsInTextPlugin: Extension = [
257
261
  return Decoration.set(
258
262
  colorInfos.map((colorInfo) => {
259
263
  return {
260
- //9 is the length of the hex color string including quotation marks
264
+ // 9 is the length of the hex color string including quotation marks
261
265
  from: colorInfo.index + 9,
262
266
  to: colorInfo.index + 9,
263
267
  value: Decoration.widget({
@@ -287,8 +291,13 @@ class ColorWidget extends WidgetType {
287
291
 
288
292
  toDOM(view: EditorView): HTMLElement {
289
293
  const parent = document.createElement('div');
294
+ const size = 20;
295
+ const innerSize = 16;
290
296
 
291
- parent.setAttribute('style', 'width:10px;height:10px');
297
+ parent.setAttribute(
298
+ 'style',
299
+ `width:${size}px;height:${size}px`,
300
+ );
292
301
  parent.className = 'color-circle-parent';
293
302
  const svg = document.createElementNS(
294
303
  'http://www.w3.org/2000/svg',
@@ -301,15 +310,23 @@ class ColorWidget extends WidgetType {
301
310
  colorCircle.setAttributeNS(
302
311
  null,
303
312
  'fill',
304
- this.color.replace(/\"/g, ''),
313
+ this.color.replace(/["']/g, ''),
314
+ );
315
+ colorCircle.setAttributeNS(null, 'stroke', 'black');
316
+ colorCircle.setAttributeNS(
317
+ null,
318
+ 'r',
319
+ '' + innerSize / 2,
320
+ );
321
+ colorCircle.setAttributeNS(null, 'cx', '' + size / 2);
322
+ colorCircle.setAttributeNS(
323
+ null,
324
+ 'cy',
325
+ '' + (size / 2 - 1),
305
326
  );
306
- colorCircle.setAttributeNS(null, 'r', '5');
307
- colorCircle.setAttributeNS(null, 'cx', '5');
308
- colorCircle.setAttributeNS(null, 'cy', '5');
309
327
 
310
- svg.setAttributeNS(null, 'viewBox', '0 0 10 10');
311
- svg.setAttributeNS(null, 'width', '10');
312
- svg.setAttributeNS(null, 'height', '10');
328
+ svg.setAttributeNS(null, 'width', '' + size);
329
+ svg.setAttributeNS(null, 'height', '' + size);
313
330
 
314
331
  svg.appendChild(colorCircle);
315
332
 
@@ -94,6 +94,7 @@ export const VZCodeProvider = ({
94
94
  initialUsername,
95
95
  children,
96
96
  codeError = null,
97
+ enableManualPretter = false,
97
98
  }: {
98
99
  content: VZCodeContent;
99
100
  shareDBDoc: ShareDBDoc<VZCodeContent>;
@@ -107,17 +108,19 @@ export const VZCodeProvider = ({
107
108
  initialUsername: Username;
108
109
  children: React.ReactNode;
109
110
  codeError?: string | null;
111
+ enableManualPretter?: boolean;
110
112
  }) => {
111
113
  // Auto-run Pretter after local changes.
112
114
  const {
113
115
  prettierError,
114
116
  }: {
115
117
  prettierError: string | null;
116
- } = usePrettier(
118
+ } = usePrettier({
117
119
  shareDBDoc,
118
120
  submitOperation,
119
121
  prettierWorker,
120
- );
122
+ enableManualPretter,
123
+ });
121
124
 
122
125
  // The error message shows either:
123
126
  // * `prettierError` - errors from Prettier, client-side only
@@ -6,7 +6,7 @@ import { VZCodeContext } from './VZCodeContext';
6
6
  // The middle portion of the VZCode environment, containing:
7
7
  // * The sidebar
8
8
  // * The settings modal
9
- export const VZLeft = () => {
9
+ export const VZLeft = ({ enableUsernameField = true }) => {
10
10
  // TODO leverage this context in deeper levels of the component tree.
11
11
  const {
12
12
  files,
@@ -50,6 +50,7 @@ export const VZLeft = () => {
50
50
  setTheme={setTheme}
51
51
  username={username}
52
52
  setUsername={setUsername}
53
+ enableUsernameField={enableUsernameField}
53
54
  />
54
55
  </div>
55
56
  );
@@ -13,7 +13,7 @@ import { VZCodeContext } from './VZCodeContext';
13
13
  // * The code error overlay
14
14
  // * The presence notifications
15
15
  // * The UI for AI Assist
16
- export const VZMiddle = () => {
16
+ export const VZMiddle = ({ enableAIAssist = true }) => {
17
17
  const { codeEditorWidth } = useContext(
18
18
  SplitPaneResizeContext,
19
19
  );
@@ -78,7 +78,7 @@ export const VZMiddle = () => {
78
78
  docPresence={docPresence}
79
79
  localPresence={localPresence}
80
80
  />
81
- {content && activeFileId ? (
81
+ {enableAIAssist && content && activeFileId ? (
82
82
  <AIAssistWidget
83
83
  activeFileId={activeFileId}
84
84
  shareDBDoc={shareDBDoc}
@@ -12,3 +12,4 @@ export {
12
12
  SplitPaneResizeContext,
13
13
  } from './SplitPaneResizeContext';
14
14
  export { useSubmitOperation } from './useSubmitOperation';
15
+ export { shouldTriggerRun } from './usePrettier';
@@ -1,4 +1,4 @@
1
- import { JSONOp } from 'ot-json1';
1
+ import { JSONOp } from '../../ot';
2
2
  import {
3
3
  FileId,
4
4
  ShareDBDoc,
@@ -20,16 +20,58 @@ const extension = (fileName: string) => {
20
20
  }
21
21
  };
22
22
 
23
- export const usePrettier = (
24
- shareDBDoc: ShareDBDoc<VZCodeContent> | null,
23
+ // export const shouldTriggerRun = (event: KeyboardEvent) => {
24
+ // if(event.ctrlKey && event.key === 's') {
25
+ // return true;
26
+ // }
27
+ // }
28
+
29
+ export const shouldTriggerRun = (event: KeyboardEvent) => {
30
+ const isMac = /Mac|iMac|MacBook/i.test(
31
+ navigator.userAgent,
32
+ );
33
+ const ctrlOrCmd = isMac ? event.metaKey : event.ctrlKey;
34
+
35
+ if (ctrlOrCmd && event.key === 's') {
36
+ // Save
37
+ return true;
38
+ } else if (
39
+ event.key === 'F5' || // Run (F5)
40
+ (ctrlOrCmd && event.key === 'Enter') || // Ctrl+Enter or Cmd+Enter
41
+ (event.shiftKey && event.key === 'Enter') || // Shift+Enter
42
+ // Let's not override the browser's default behavior for refresh page.
43
+ // (ctrlOrCmd && event.key === 'r') || // Ctrl+R or Cmd+R
44
+
45
+ (event.altKey && event.key === 'Enter') || // Alt+Enter
46
+ (ctrlOrCmd &&
47
+ event.shiftKey &&
48
+ (event.key === 'B' || event.key === 'b')) || // Ctrl+Shift+B or Cmd+Shift+B
49
+ (ctrlOrCmd && event.shiftKey && event.key === 'F10') || // Ctrl+Shift+F10 or Cmd+Shift+F10
50
+ event.key === 'F8' ||
51
+ event.key === 'F9'
52
+ ) {
53
+ return true;
54
+ }
55
+ return false;
56
+ };
57
+
58
+ export const usePrettier = ({
59
+ shareDBDoc,
60
+ submitOperation,
61
+ prettierWorker,
62
+ enableManualPretter,
63
+ }: {
64
+ shareDBDoc: ShareDBDoc<VZCodeContent> | null;
25
65
  submitOperation: (
26
66
  next: (content: VZCodeContent) => VZCodeContent,
27
- ) => void,
28
- prettierWorker: Worker,
29
- ) => {
67
+ ) => void;
68
+ prettierWorker: Worker;
69
+ enableManualPretter: boolean;
70
+ }) => {
30
71
  // The set of files that have been modified
31
72
  // since the last Prettier run.
32
- const dirtyFiles: Set<FileId> = new Set<FileId>();
73
+ // const dirtyFiles: Set<FileId> = new Set<FileId>();
74
+ const dirtyFilesRef = useRef<Set<FileId>>(new Set());
33
75
 
34
76
  // State to hold the error from Prettier
35
77
  // `null` means no errors
@@ -95,6 +137,9 @@ export const usePrettier = (
95
137
  // Get the files
96
138
  const files = content.files;
97
139
 
140
+ // Get the set of dirty files
141
+ const dirtyFiles = dirtyFilesRef.current;
142
+
98
143
  // Get the dirty files
99
144
  const dirtyFileIds = Array.from(dirtyFiles);
100
145
 
@@ -118,20 +163,35 @@ export const usePrettier = (
118
163
  }
119
164
  };
120
165
 
121
- // Function to debounce running Prettier
166
+ let debouncePrettier;
122
167
  let debounceTimeout;
123
- function debouncePrettier() {
124
- clearTimeout(debounceTimeout);
125
- debounceTimeout = setTimeout(
126
- runPrettier,
127
- autoPrettierDebounceTimeMS,
168
+ let handleKeyDown;
169
+ if (enableManualPretter) {
170
+ handleKeyDown = (event: KeyboardEvent) => {
171
+ if (shouldTriggerRun(event)) {
172
+ event.preventDefault();
173
+ runPrettier();
174
+ }
175
+ };
176
+ document.addEventListener('keydown', handleKeyDown);
177
+ } else {
178
+ // Function to debounce running Prettier
179
+
180
+ debouncePrettier = () => {
181
+ clearTimeout(debounceTimeout);
182
+ debounceTimeout = setTimeout(
183
+ runPrettier,
184
+ autoPrettierDebounceTimeMS,
185
+ );
186
+ };
187
+ //Stops prettier from running when you perform a mouse move or a keydown
188
+ window.addEventListener(
189
+ 'mousemove',
190
+ debouncePrettier,
128
191
  );
192
+ window.addEventListener('keydown', debouncePrettier);
129
193
  }
130
194
 
131
- //Stops prettier from running when you perform a mouse move or a keydown
132
- window.addEventListener('mousemove', debouncePrettier);
133
- window.addEventListener('keydown', debouncePrettier);
134
-
135
195
  // Listen for changes
136
196
  shareDBDoc.on(
137
197
  'op batch',
@@ -170,11 +230,12 @@ export const usePrettier = (
170
230
  const fileId: FileId = op[1];
171
231
 
172
232
  // Add the file id to the set of dirty files
173
- dirtyFiles.add(fileId);
233
+ dirtyFilesRef.current.add(fileId);
174
234
 
175
235
  // Debounce running Prettier
176
- debouncePrettier();
177
-
236
+ if (!enableManualPretter) {
237
+ debouncePrettier();
238
+ }
178
239
  // console.log('Op is for file', fileId);
179
240
  }
180
241
  } else {
@@ -190,17 +251,24 @@ export const usePrettier = (
190
251
  'message',
191
252
  handleMessage,
192
253
  );
193
- window.removeEventListener(
194
- 'keydown',
195
- debouncePrettier,
196
- );
197
- window.removeEventListener(
198
- 'mousemove',
199
- debouncePrettier,
200
- );
254
+ if (enableManualPretter) {
255
+ document.removeEventListener(
256
+ 'keydown',
257
+ handleKeyDown,
258
+ );
259
+ } else {
260
+ window.removeEventListener(
261
+ 'keydown',
262
+ debouncePrettier,
263
+ );
264
+ window.removeEventListener(
265
+ 'mousemove',
266
+ debouncePrettier,
267
+ );
201
268
 
202
- // Clear the timeout
203
- clearTimeout(debounceTimeout);
269
+ // Clear the timeout
270
+ clearTimeout(debounceTimeout);
271
+ }
204
272
  };
205
273
  }, [shareDBDoc]);
206
274
  return { prettierError }; // Return the errors for use in other files
@@ -244,4 +244,24 @@ onmessage = async ({ data }) => {
244
244
  };
245
245
  postMessage(linterResponse);
246
246
  }
247
+
248
+ // Handle the transpile-request event, which
249
+ // transpiles TypeScript to JavaScript.
250
+ if (data.event === 'transpile-request') {
251
+ const tsCode = data.tsCode;
252
+
253
+ const compilerOptions = {
254
+ jsx: ts.JsxEmit.React,
255
+ };
256
+
257
+ const jsCode = ts.transpileModule(tsCode, {
258
+ compilerOptions,
259
+ }).outputText;
260
+
261
+ postMessage({
262
+ event: 'transpile-response',
263
+ jsCode,
264
+ fileId: data.fileId,
265
+ });
266
+ }
247
267
  };
package/src/index.ts CHANGED
@@ -21,4 +21,5 @@ export {
21
21
  SplitPaneResizeProvider,
22
22
  SplitPaneResizeContext,
23
23
  useSubmitOperation,
24
+ shouldTriggerRun,
24
25
  } from './client';