vzcode 0.28.0 → 0.30.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 (46) hide show
  1. package/dist/assets/index-836bdce7.css +1 -0
  2. package/dist/assets/index-c3a6c525.js +115 -0
  3. package/dist/assets/worker-ed0e2778.js +366 -0
  4. package/dist/index.html +2 -2
  5. package/package.json +10 -6
  6. package/src/client/AIAssist.ts +110 -19
  7. package/src/client/AIAssistWidget/index.tsx +101 -0
  8. package/src/client/AIAssistWidget/style.scss +10 -0
  9. package/src/client/App/VZCodeContext.tsx +249 -0
  10. package/src/client/App/VZLeft.tsx +56 -0
  11. package/src/client/App/VZMiddle.tsx +91 -0
  12. package/src/client/App/VZRight.tsx +15 -0
  13. package/src/client/App/index.tsx +106 -0
  14. package/src/client/{style.scss → App/style.scss} +14 -3
  15. package/src/client/App/useShareDB.ts +110 -0
  16. package/src/client/CodeEditor/getOrCreateEditor.ts +54 -6
  17. package/src/client/CodeEditor/index.tsx +9 -0
  18. package/src/client/CodeEditor/json1PresenceDisplay.ts +38 -9
  19. package/src/client/CodeEditor/typeScriptCompletions.ts +108 -0
  20. package/src/client/CodeEditor/typeScriptLinter.ts +45 -0
  21. package/src/client/CodeErrorOverlay/index.tsx +27 -1
  22. package/src/client/CodeErrorOverlay/style.scss +11 -11
  23. package/src/client/SplitPaneResizeContext.tsx +198 -39
  24. package/src/client/TabList/index.tsx +1 -27
  25. package/src/client/{Resizer → VZResizer}/index.tsx +37 -13
  26. package/src/client/VZSettings.tsx +1 -1
  27. package/src/client/VZSidebar/CreateFileModal.tsx +93 -0
  28. package/src/client/VZSidebar/DeleteConfirmationModal.tsx +42 -24
  29. package/src/client/VZSidebar/index.tsx +69 -9
  30. package/src/client/useFileCRUD.ts +19 -19
  31. package/src/client/useOpenDirectories.ts +12 -8
  32. package/src/client/usePrettier/index.ts +13 -1
  33. package/src/client/useSubmitOperation.ts +16 -19
  34. package/src/client/useTypeScript/index.ts +44 -0
  35. package/src/client/useTypeScript/requestTypes.ts +29 -0
  36. package/src/client/useTypeScript/worker.ts +242 -0
  37. package/src/client/usernameLocalStorage.ts +1 -1
  38. package/src/client/vzReducer/createInitialState.ts +4 -5
  39. package/src/index.ts +5 -1
  40. package/src/server/handleAIAssist.js +76 -12
  41. package/src/server/index.js +55 -2
  42. package/src/types.ts +78 -1
  43. package/dist/assets/index-a9585cbc.js +0 -111
  44. package/dist/assets/index-d30c9f1b.css +0 -1
  45. package/src/client/App.tsx +0 -286
  46. /package/src/client/{Resizer → VZResizer}/styles.scss +0 -0
@@ -0,0 +1,91 @@
1
+ import { useContext } from 'react';
2
+ import { SplitPaneResizeContext } from '../SplitPaneResizeContext';
3
+ import { TabList } from '../TabList';
4
+ import { CodeEditor } from '../CodeEditor';
5
+ import { CodeErrorOverlay } from '../CodeErrorOverlay';
6
+ import { PresenceNotifications } from '../PresenceNotifications';
7
+ import { AIAssistWidget } from '../AIAssistWidget';
8
+ import { VZCodeContext } from './VZCodeContext';
9
+
10
+ // The middle portion of the VZCode environment, containing:
11
+ // * The list of tabs at the top
12
+ // * The code editor itself
13
+ // * The code error overlay
14
+ // * The presence notifications
15
+ // * The UI for AI Assist
16
+ export const VZMiddle = () => {
17
+ const { codeEditorWidth } = useContext(
18
+ SplitPaneResizeContext,
19
+ );
20
+
21
+ // TODO leverage this context in deeper levels of the component tree.
22
+ const {
23
+ content,
24
+ shareDBDoc,
25
+ submitOperation,
26
+ localPresence,
27
+ docPresence,
28
+ files,
29
+ createFile,
30
+ activeFileId,
31
+ setActiveFileId,
32
+ tabList,
33
+ openTab,
34
+ closeTabs,
35
+ theme,
36
+ username,
37
+ editorCache,
38
+ editorWantsFocus,
39
+ editorNoLongerWantsFocus,
40
+ errorMessage,
41
+ typeScriptWorker,
42
+ } = useContext(VZCodeContext);
43
+
44
+ return activeFileId !== null ? (
45
+ <div
46
+ className="middle"
47
+ style={{ width: codeEditorWidth + 'px' }}
48
+ >
49
+ <TabList
50
+ files={files}
51
+ tabList={tabList}
52
+ activeFileId={activeFileId}
53
+ setActiveFileId={setActiveFileId}
54
+ openTab={openTab}
55
+ closeTabs={closeTabs}
56
+ createFile={createFile}
57
+ />
58
+ {content && activeFileId ? (
59
+ <CodeEditor
60
+ shareDBDoc={shareDBDoc}
61
+ submitOperation={submitOperation}
62
+ localPresence={localPresence}
63
+ docPresence={docPresence}
64
+ activeFileId={activeFileId}
65
+ theme={theme}
66
+ editorCache={editorCache}
67
+ editorWantsFocus={editorWantsFocus}
68
+ editorNoLongerWantsFocus={
69
+ editorNoLongerWantsFocus
70
+ }
71
+ username={username}
72
+ typeScriptWorker={typeScriptWorker}
73
+ tabList={tabList}
74
+ />
75
+ ) : null}
76
+ <CodeErrorOverlay errorMessage={errorMessage} />
77
+ <PresenceNotifications
78
+ docPresence={docPresence}
79
+ localPresence={localPresence}
80
+ />
81
+ {content && activeFileId ? (
82
+ <AIAssistWidget
83
+ activeFileId={activeFileId}
84
+ shareDBDoc={shareDBDoc}
85
+ editorCache={editorCache}
86
+ tabList={tabList}
87
+ />
88
+ ) : null}
89
+ </div>
90
+ ) : null;
91
+ };
@@ -0,0 +1,15 @@
1
+ // Displays the Vite devserver iframe.
2
+ // TODO test this out, think this through.
3
+ // Purpose: support folks using VZCode as the editor
4
+ // for their Vite-based projects.
5
+ const enableIframe = false;
6
+
7
+ export const VZRight = () => {
8
+ return (
9
+ <div className="right">
10
+ {enableIframe ? (
11
+ <iframe src="http://localhost:5173/"></iframe>
12
+ ) : null}
13
+ </div>
14
+ );
15
+ };
@@ -0,0 +1,106 @@
1
+ import { useContext } from 'react';
2
+ import ShareDBClient from 'sharedb-client-browser/dist/sharedb-client-umd.cjs';
3
+ import { json1Presence } from '../../ot';
4
+ import { Username } from '../../types';
5
+ // @ts-ignore
6
+ import PrettierWorker from '../usePrettier/worker?worker';
7
+ // @ts-ignore
8
+ import TypeScriptWorker from '../useTypeScript/worker?worker';
9
+ import { SplitPaneResizeProvider } from '../SplitPaneResizeContext';
10
+ import { VZResizer } from '../VZResizer';
11
+ import {
12
+ useInitialUsername,
13
+ usePersistUsername,
14
+ } from '../usernameLocalStorage';
15
+ import { useShareDB } from './useShareDB';
16
+ import { VZLeft } from './VZLeft';
17
+ import { VZMiddle } from './VZMiddle';
18
+ import { VZRight } from './VZRight';
19
+ import {
20
+ VZCodeContext,
21
+ VZCodeProvider,
22
+ } from './VZCodeContext';
23
+ import './style.scss';
24
+
25
+ // Instantiate the Prettier worker.
26
+ const prettierWorker = new PrettierWorker();
27
+
28
+ // Instantiate the TypeScript worker.
29
+ const typeScriptWorker = new TypeScriptWorker();
30
+
31
+ // Register our custom JSON1 OT type that supports presence.
32
+ // See https://github.com/vizhub-core/json1-presence
33
+ ShareDBClient.types.register(json1Presence.type);
34
+
35
+ // Establish the singleton ShareDB connection over WebSockets.
36
+ // TODO consider using reconnecting WebSocket
37
+ const { Connection } = ShareDBClient;
38
+ const wsProtocol =
39
+ window.location.protocol === 'https:'
40
+ ? 'wss://'
41
+ : 'ws://';
42
+ const socket = new WebSocket(
43
+ wsProtocol + window.location.host + '/ws',
44
+ );
45
+ const connection = new Connection(socket);
46
+
47
+ // Stores the username to local storage.
48
+ // TODO consider if there's a cleaner pattern for this.
49
+ // This makes sense in VZCode itself, but for an app with
50
+ // authentication that wraps VZCode, it is not required.
51
+ const PersistUsername = () => {
52
+ const { username } = useContext(VZCodeContext);
53
+ usePersistUsername(username);
54
+ return null;
55
+ };
56
+
57
+ function App() {
58
+ const {
59
+ shareDBDoc,
60
+ content,
61
+ localPresence,
62
+ docPresence,
63
+ submitOperation,
64
+ } = useShareDB({
65
+ connection,
66
+ });
67
+
68
+ // Get the initial username from localStorage.
69
+ const initialUsername: Username = useInitialUsername();
70
+
71
+ // Feature flag for enabling the right-side panel.
72
+ // Useful for debugging dual split pane functionality.
73
+ // We may want to add this as an actual VZCode feature,
74
+ // for running the code that the VZCode user is developing
75
+ // in the same browser window as the VZCode editor,
76
+ // so that multiple browser windows are not required.
77
+ const enableRightPanel = true;
78
+
79
+ return (
80
+ <SplitPaneResizeProvider>
81
+ <VZCodeProvider
82
+ content={content}
83
+ shareDBDoc={shareDBDoc}
84
+ submitOperation={submitOperation}
85
+ localPresence={localPresence}
86
+ docPresence={docPresence}
87
+ prettierWorker={prettierWorker}
88
+ typeScriptWorker={typeScriptWorker}
89
+ initialUsername={initialUsername}
90
+ >
91
+ <div className="app">
92
+ <VZLeft />
93
+ <VZMiddle />
94
+ {enableRightPanel ? <VZRight /> : null}
95
+ <VZResizer side="left" />
96
+ {enableRightPanel ? (
97
+ <VZResizer side="right" />
98
+ ) : null}
99
+ </div>
100
+ <PersistUsername />
101
+ </VZCodeProvider>
102
+ </SplitPaneResizeProvider>
103
+ );
104
+ }
105
+
106
+ export default App;
@@ -49,16 +49,27 @@ body,
49
49
  position: relative;
50
50
 
51
51
  .left,
52
- .right {
52
+ .middle {
53
53
  display: flex;
54
54
 
55
55
  /* To suport PrettierErrorOverlay, which is position: absolute */
56
56
  position: relative;
57
57
  }
58
58
 
59
- .right {
60
- flex: 1;
59
+ .middle {
60
+ // flex: 1;
61
61
  flex-direction: column;
62
62
  min-width: 0;
63
63
  }
64
+
65
+ .right {
66
+ flex: 1;
67
+ background-color: white;
68
+
69
+ iframe {
70
+ width: 100%;
71
+ height: 100%;
72
+ border: none;
73
+ }
74
+ }
64
75
  }
@@ -0,0 +1,110 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { randomId } from '../../randomId';
3
+ import { ShareDBDoc, VZCodeContent } from '../../types';
4
+ import { useSubmitOperation } from '../useSubmitOperation';
5
+
6
+ // Set up the connection to ShareDB.
7
+ export const useShareDB = ({
8
+ connection,
9
+ }: {
10
+ connection: any;
11
+ }) => {
12
+ // The ShareDB document.
13
+ const [shareDBDoc, setShareDBDoc] =
14
+ useState<ShareDBDoc<VZCodeContent> | null>(null);
15
+
16
+ // Local ShareDB presence, for broadcasting our cursor position
17
+ // so other clients can see it.
18
+ // See https://share.github.io/sharedb/api/local-presence
19
+ const [localPresence, setLocalPresence] = useState(null);
20
+
21
+ // The document-level presence object, which emits
22
+ // changes in remote presence.
23
+ const [docPresence, setDocPresence] = useState(null);
24
+
25
+ // The `doc.data` part of the ShareDB document,
26
+ // updated on each change to decouple rendering from ShareDB.
27
+ // Starts out as `null` until the document is loaded.
28
+ const [content, setContent] =
29
+ useState<VZCodeContent | null>(null);
30
+
31
+ useEffect(() => {
32
+ // Since there is only ever a single document,
33
+ // these things are pretty arbitrary.
34
+ // * `collection` - the ShareDB collection to use
35
+ // * `id` - the id of the ShareDB document to use
36
+ const collection = 'documents';
37
+ const id = '1';
38
+
39
+ // Initialize the ShareDB document.
40
+ const shareDBDoc = connection.get(collection, id);
41
+
42
+ // Subscribe to the document to get updates.
43
+ // This callback gets called once only.
44
+ shareDBDoc.subscribe(() => {
45
+ // Expose ShareDB doc to downstream logic.
46
+ setShareDBDoc(shareDBDoc);
47
+
48
+ // Set initial data.
49
+ setContent(shareDBDoc.data);
50
+
51
+ // Listen for all changes and update `data`.
52
+ // This decouples rendering logic from ShareDB.
53
+ // This callback gets called on each change.
54
+ shareDBDoc.on('op', () => {
55
+ // TODO consider excluding file contents from this,
56
+ // because currently we are re-rendering the entire
57
+ // document on each file change (each keystroke while editing).
58
+ // This is not required, because no part of the app outside
59
+ // of the CodeMirror integration uses those file contents.
60
+ // The Sidebar only needs to know file names, not contents.
61
+ setContent(shareDBDoc.data);
62
+ });
63
+
64
+ // Set up presence.
65
+ // See https://github.com/share/sharedb/blob/master/examples/rich-text-presence/client.js#L53
66
+ const docPresence = connection.getDocPresence(
67
+ collection,
68
+ id,
69
+ );
70
+
71
+ // Subscribe to receive remote presence updates.
72
+ docPresence.subscribe(function (error) {
73
+ if (error) throw error;
74
+ });
75
+
76
+ // Set up our local presence for broadcasting this client's presence.
77
+ const generateTimestampedId = () => {
78
+ const timestamp = Date.now().toString(36);
79
+ const randomPart = randomId();
80
+ return `${timestamp}-${randomPart}`;
81
+ };
82
+ setLocalPresence(
83
+ docPresence.create(generateTimestampedId()),
84
+ );
85
+
86
+ // Store docPresence so child components can listen for changes.
87
+ setDocPresence(docPresence);
88
+ });
89
+
90
+ // TODO unsubscribe from presence
91
+ // TODO unsubscribe from doc
92
+ return () => {
93
+ // shareDBDoc.destroy();
94
+ // docPresence.destroy();
95
+ };
96
+ }, []);
97
+
98
+ // A convenience function for mutating the ShareDB document
99
+ // based submitting OT ops generated by diffing the JSON.
100
+ const submitOperation =
101
+ useSubmitOperation<VZCodeContent>(shareDBDoc);
102
+
103
+ return {
104
+ shareDBDoc,
105
+ content,
106
+ localPresence,
107
+ docPresence,
108
+ submitOperation,
109
+ };
110
+ };
@@ -4,10 +4,13 @@ import {
4
4
  EditorState,
5
5
  } from '@codemirror/state';
6
6
  import { javascript } from '@codemirror/lang-javascript';
7
+ import { json } from '@codemirror/lang-json';
7
8
  import { markdown } from '@codemirror/lang-markdown';
8
9
  import { html } from '@codemirror/lang-html';
9
10
  import { css } from '@codemirror/lang-css';
10
11
  import { json1Sync } from 'codemirror-ot';
12
+ import { autocompletion } from '@codemirror/autocomplete';
13
+ import { Diagnostic, linter } from '@codemirror/lint';
11
14
  import { json1Presence, textUnicode } from '../../ot';
12
15
  import {
13
16
  FileId,
@@ -29,18 +32,30 @@ import {
29
32
  } from '../useEditorCache';
30
33
  import { ThemeLabel, themeOptionsByLabel } from '../themes';
31
34
  import { AIAssist } from '../AIAssist';
35
+ import { typeScriptCompletions } from './typeScriptCompletions';
36
+ import { typeScriptLinter } from './typeScriptLinter';
37
+ import { TabState } from '../vzReducer';
38
+
39
+ // Feature flag to enable TypeScript completions & TypeScript Linter.
40
+ const enableTypeScriptCompletions = true;
41
+ const enableTypeScriptLinter = true;
42
+
43
+ // Enables TypeScript +JSX support in CodeMirror.
44
+ const tsx = () =>
45
+ javascript({ jsx: true, typescript: true });
32
46
 
33
47
  // Language extensions for CodeMirror.
34
48
  // Keys are file extensions.
35
49
  // Values are CodeMirror extensions.
36
50
  // TODO consider moving this to a separate file.
37
51
  const languageExtensions = {
38
- js: javascript,
39
- json: javascript,
40
- jsx: javascript,
41
- ts: javascript,
42
- html: html,
43
- css: css,
52
+ json,
53
+ tsx,
54
+ js: tsx,
55
+ jsx: tsx,
56
+ ts: tsx,
57
+ html,
58
+ css,
44
59
  md: markdown,
45
60
  };
46
61
 
@@ -69,6 +84,8 @@ export const getOrCreateEditor = ({
69
84
  usernameRef,
70
85
  aiAssistEndpoint,
71
86
  aiAssistOptions,
87
+ typeScriptWorker,
88
+ tabList,
72
89
  }: {
73
90
  fileId: FileId;
74
91
 
@@ -92,6 +109,8 @@ export const getOrCreateEditor = ({
92
109
  aiAssistOptions?: {
93
110
  [key: string]: any;
94
111
  };
112
+ typeScriptWorker: Worker;
113
+ tabList: Array<TabState>;
95
114
  }): EditorCacheValue => {
96
115
  // Cache hit
97
116
  if (editorCache.has(fileId)) {
@@ -209,12 +228,41 @@ export const getOrCreateEditor = ({
209
228
 
210
229
  extensions.push(
211
230
  AIAssist({
231
+ shareDBDoc,
212
232
  fileId,
233
+ tabList,
213
234
  aiAssistEndpoint,
214
235
  aiAssistOptions,
215
236
  }),
216
237
  );
217
238
 
239
+ // Add the extension that provides TypeScript completions.
240
+ if (enableTypeScriptCompletions) {
241
+ extensions.push(
242
+ autocompletion({
243
+ override: [
244
+ typeScriptCompletions({
245
+ typeScriptWorker,
246
+ fileName: name,
247
+ }),
248
+ ],
249
+ }),
250
+ );
251
+ }
252
+ if (enableTypeScriptLinter) {
253
+ extensions.push(
254
+ linter(
255
+ typeScriptLinter({
256
+ typeScriptWorker,
257
+ fileName: name,
258
+ shareDBDoc,
259
+ fileId,
260
+ }) as unknown as () => Diagnostic[],
261
+ //Needs the unknown because we are returning a Promise<Diagnostic>
262
+ ),
263
+ );
264
+ }
265
+
218
266
  const editor = new EditorView({
219
267
  state: EditorState.create({
220
268
  doc: text,
@@ -17,6 +17,7 @@ import {
17
17
  } from '../useEditorCache';
18
18
  import { getOrCreateEditor } from './getOrCreateEditor';
19
19
  import './style.scss';
20
+ import { TabState } from '../vzReducer';
20
21
 
21
22
  export const CodeEditor = ({
22
23
  activeFileId,
@@ -32,6 +33,8 @@ export const CodeEditor = ({
32
33
  username,
33
34
  aiAssistEndpoint,
34
35
  aiAssistOptions,
36
+ typeScriptWorker,
37
+ tabList,
35
38
  }: {
36
39
  activeFileId: FileId;
37
40
  shareDBDoc: ShareDBDoc<VZCodeContent> | null;
@@ -62,6 +65,9 @@ export const CodeEditor = ({
62
65
  aiAssistOptions?: {
63
66
  [key: string]: string;
64
67
  };
68
+ typeScriptWorker: Worker;
69
+
70
+ tabList: Array<TabState>;
65
71
  }) => {
66
72
  const ref = useRef<HTMLDivElement>(null);
67
73
 
@@ -111,6 +117,8 @@ export const CodeEditor = ({
111
117
  usernameRef,
112
118
  aiAssistEndpoint,
113
119
  aiAssistOptions,
120
+ typeScriptWorker,
121
+ tabList,
114
122
  }),
115
123
  [
116
124
  activeFileId,
@@ -124,6 +132,7 @@ export const CodeEditor = ({
124
132
  usernameRef,
125
133
  aiAssistEndpoint,
126
134
  aiAssistOptions,
135
+ typeScriptWorker,
127
136
  ],
128
137
  );
129
138
 
@@ -5,6 +5,8 @@ import {
5
5
  Decoration,
6
6
  } from '@codemirror/view';
7
7
  import { Annotation, RangeSet } from '@codemirror/state';
8
+ import ColorHash from 'color-hash';
9
+ import { Username } from '../../types';
8
10
 
9
11
  // Deals with receiving the broadcasted presence cursor locations
10
12
  // from other clients and displaying them.
@@ -50,8 +52,9 @@ export const json1PresenceDisplay = ({
50
52
  const presence = presenceState[id];
51
53
  const { start, end } = presence;
52
54
  const from = start[start.length - 1];
53
- // TODO support selection ranges (first attempt introduced layout errors)
54
55
  const to = end[end.length - 1];
56
+ const userColor = new ColorHash().rgb(id);
57
+ const { username } = presence;
55
58
  if (from === to) {
56
59
  return {
57
60
  from,
@@ -59,7 +62,11 @@ export const json1PresenceDisplay = ({
59
62
  value: Decoration.widget({
60
63
  side: -1,
61
64
  block: false,
62
- widget: new PresenceWidget(id),
65
+ widget: new PresenceWidget(
66
+ id,
67
+ userColor,
68
+ username,
69
+ ),
63
70
  }),
64
71
  };
65
72
  } else {
@@ -70,8 +77,8 @@ export const json1PresenceDisplay = ({
70
77
  class: 'cm-json1-presence',
71
78
  attributes: {
72
79
  style: `
73
- background-color: rgba(255, 255, 0, 0.5);
74
- box-shadow: 1px 0 0 yellow;
80
+ background-color: rgba(${userColor}, 0.75);
81
+ mix-blend-mode: luminosity;
75
82
  `,
76
83
  },
77
84
  }),
@@ -122,9 +129,17 @@ const pathMatches = (path, presence) => {
122
129
  // Displays a single remote presence cursor.
123
130
  class PresenceWidget extends WidgetType {
124
131
  id: string;
125
- constructor(id: string) {
132
+ color: string;
133
+ username: Username;
134
+ constructor(
135
+ id: string,
136
+ color: string,
137
+ username: Username,
138
+ ) {
126
139
  super();
127
140
  this.id = id;
141
+ this.color = color;
142
+ this.username = username;
128
143
  }
129
144
 
130
145
  eq(other: PresenceWidget) {
@@ -135,14 +150,30 @@ class PresenceWidget extends WidgetType {
135
150
  const span = document.createElement('span');
136
151
  span.setAttribute('aria-hidden', 'true');
137
152
  span.className = 'cm-json1-presence';
138
-
139
153
  // This child is what actually displays the presence.
140
154
  // Nested so that the layout is not impacted.
141
155
  //
142
156
  // The initial attempt using the top level span to render
143
157
  // the cursor caused a wonky layout with adjacent characters shifting
144
158
  // left and right by 1 pixel or so.
145
- span.appendChild(document.createElement('div'));
159
+ const div = document.createElement('div');
160
+ span.appendChild(div);
161
+ div.style.borderLeft = `1px solid rgba(${this.color})`;
162
+
163
+ // background color behind username
164
+ const userDiv = document.createElement('div');
165
+ userDiv.className = 'remote-cursor-username';
166
+ userDiv.style.top = `-20px`;
167
+ userDiv.style.height = `20px`;
168
+ userDiv.style.width = `${this.username.length * 11}px`;
169
+ userDiv.style.backgroundColor = `rgba(${this.color})`;
170
+ userDiv.style.color = `black`;
171
+ userDiv.style.textAlign = `center`;
172
+ userDiv.appendChild(
173
+ document.createTextNode(this.username),
174
+ );
175
+ span.appendChild(userDiv);
176
+
146
177
  return span;
147
178
  }
148
179
 
@@ -150,7 +181,6 @@ class PresenceWidget extends WidgetType {
150
181
  return false;
151
182
  }
152
183
  }
153
-
154
184
  const presenceTheme = EditorView.baseTheme({
155
185
  '.cm-json1-presence': {
156
186
  position: 'relative',
@@ -161,6 +191,5 @@ const presenceTheme = EditorView.baseTheme({
161
191
  bottom: '0',
162
192
  left: '0',
163
193
  right: '0',
164
- borderLeft: '1px solid yellow',
165
194
  },
166
195
  });