@kerebron/extension-lsp 0.4.3 → 0.4.5

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 (30) hide show
  1. package/README.md +157 -47
  2. package/assets/lsp-status.css +60 -0
  3. package/esm/extension-lsp/src/LspStatus.d.ts +35 -0
  4. package/esm/extension-lsp/src/LspStatus.d.ts.map +1 -0
  5. package/esm/extension-lsp/src/LspStatus.js +128 -0
  6. package/esm/extension-menu/src/CustomMenuPlugin.d.ts +65 -0
  7. package/esm/extension-menu/src/CustomMenuPlugin.d.ts.map +1 -0
  8. package/esm/extension-menu/src/CustomMenuPlugin.js +1186 -0
  9. package/esm/extension-menu/src/ExtensionCustomMenu.d.ts +14 -0
  10. package/esm/extension-menu/src/ExtensionCustomMenu.d.ts.map +1 -0
  11. package/esm/extension-menu/src/ExtensionCustomMenu.js +57 -0
  12. package/esm/extension-menu/src/buildMenu.d.ts +5 -0
  13. package/esm/extension-menu/src/buildMenu.d.ts.map +1 -0
  14. package/esm/extension-menu/src/buildMenu.js +331 -0
  15. package/esm/extension-menu/src/icons.d.ts +15 -0
  16. package/esm/extension-menu/src/icons.d.ts.map +1 -0
  17. package/esm/extension-menu/src/icons.js +123 -0
  18. package/esm/extension-menu/src/menu.d.ts +81 -0
  19. package/esm/extension-menu/src/menu.d.ts.map +1 -0
  20. package/esm/extension-menu/src/menu.js +350 -0
  21. package/esm/extension-menu/src/mod.d.ts +3 -0
  22. package/esm/extension-menu/src/mod.d.ts.map +1 -0
  23. package/esm/extension-menu/src/mod.js +2 -0
  24. package/esm/extension-menu/src/prompt.d.ts +36 -0
  25. package/esm/extension-menu/src/prompt.d.ts.map +1 -0
  26. package/esm/extension-menu/src/prompt.js +158 -0
  27. package/esm/extension-yjs/src/CollaborationStatus.d.ts +59 -0
  28. package/esm/extension-yjs/src/CollaborationStatus.d.ts.map +1 -0
  29. package/esm/extension-yjs/src/CollaborationStatus.js +286 -0
  30. package/package.json +4 -1
@@ -0,0 +1,286 @@
1
+ import { Plugin, PluginKey } from 'prosemirror-state';
2
+ const CSS_PREFIX = 'kb-collab-status';
3
+ /**
4
+ * Plugin key for accessing collaboration status state
5
+ */
6
+ export const collaborationStatusPluginKey = new PluginKey('collaboration-status');
7
+ /**
8
+ * Creates a ProseMirror plugin that tracks collaboration status and user presence.
9
+ * This plugin maintains state that can be accessed by other components.
10
+ */
11
+ export function collaborationStatusPlugin(awareness, options = {}) {
12
+ return new Plugin({
13
+ key: collaborationStatusPluginKey,
14
+ state: {
15
+ init() {
16
+ return {
17
+ status: 'connecting',
18
+ users: getUsers(awareness),
19
+ };
20
+ },
21
+ apply(tr, value) {
22
+ const meta = tr.getMeta(collaborationStatusPluginKey);
23
+ if (meta) {
24
+ return { ...value, ...meta };
25
+ }
26
+ return value;
27
+ },
28
+ },
29
+ view(editorView) {
30
+ const updateUsers = () => {
31
+ const users = getUsers(awareness);
32
+ editorView.dispatch(editorView.state.tr.setMeta(collaborationStatusPluginKey, { users }));
33
+ };
34
+ awareness.on('change', updateUsers);
35
+ return {
36
+ destroy() {
37
+ awareness.off('change', updateUsers);
38
+ },
39
+ };
40
+ },
41
+ });
42
+ }
43
+ function getUsers(awareness) {
44
+ const users = [];
45
+ awareness.getStates().forEach((state, clientId) => {
46
+ if (state.user) {
47
+ users.push({
48
+ clientId,
49
+ name: state.user.name || `User ${clientId}`,
50
+ color: state.user.color || '#888888',
51
+ colorLight: state.user.colorLight,
52
+ });
53
+ }
54
+ });
55
+ return users;
56
+ }
57
+ /**
58
+ * A menu element that displays collaboration status, user count, and a dropdown of users.
59
+ */
60
+ export class CollaborationStatusElement {
61
+ constructor(options) {
62
+ Object.defineProperty(this, "awareness", {
63
+ enumerable: true,
64
+ configurable: true,
65
+ writable: true,
66
+ value: void 0
67
+ });
68
+ Object.defineProperty(this, "provider", {
69
+ enumerable: true,
70
+ configurable: true,
71
+ writable: true,
72
+ value: void 0
73
+ });
74
+ Object.defineProperty(this, "dom", {
75
+ enumerable: true,
76
+ configurable: true,
77
+ writable: true,
78
+ value: null
79
+ });
80
+ Object.defineProperty(this, "statusDot", {
81
+ enumerable: true,
82
+ configurable: true,
83
+ writable: true,
84
+ value: null
85
+ });
86
+ Object.defineProperty(this, "userCount", {
87
+ enumerable: true,
88
+ configurable: true,
89
+ writable: true,
90
+ value: null
91
+ });
92
+ Object.defineProperty(this, "dropdown", {
93
+ enumerable: true,
94
+ configurable: true,
95
+ writable: true,
96
+ value: null
97
+ });
98
+ Object.defineProperty(this, "userList", {
99
+ enumerable: true,
100
+ configurable: true,
101
+ writable: true,
102
+ value: null
103
+ });
104
+ Object.defineProperty(this, "isOpen", {
105
+ enumerable: true,
106
+ configurable: true,
107
+ writable: true,
108
+ value: false
109
+ });
110
+ Object.defineProperty(this, "status", {
111
+ enumerable: true,
112
+ configurable: true,
113
+ writable: true,
114
+ value: 'connecting'
115
+ });
116
+ this.awareness = options.awareness;
117
+ this.provider = options.provider;
118
+ }
119
+ render(view) {
120
+ // Create main container
121
+ this.dom = document.createElement('div');
122
+ this.dom.className = `${CSS_PREFIX}`;
123
+ this.dom.setAttribute('role', 'button');
124
+ this.dom.setAttribute('aria-haspopup', 'true');
125
+ this.dom.setAttribute('aria-expanded', 'false');
126
+ // Create status indicator button
127
+ const button = document.createElement('button');
128
+ button.type = 'button';
129
+ button.className = `${CSS_PREFIX}__button`;
130
+ button.title = 'Collaboration status';
131
+ button.setAttribute('aria-label', 'Collaboration status');
132
+ // Status dot
133
+ this.statusDot = document.createElement('span');
134
+ this.statusDot.className =
135
+ `${CSS_PREFIX}__dot ${CSS_PREFIX}__dot--connecting`;
136
+ button.appendChild(this.statusDot);
137
+ // User count badge
138
+ this.userCount = document.createElement('span');
139
+ this.userCount.className = `${CSS_PREFIX}__count`;
140
+ this.userCount.textContent = '0';
141
+ button.appendChild(this.userCount);
142
+ // Dropdown chevron
143
+ const chevron = document.createElement('span');
144
+ chevron.className = `${CSS_PREFIX}__chevron`;
145
+ chevron.innerHTML =
146
+ `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 9l6 6 6-6"/></svg>`;
147
+ button.appendChild(chevron);
148
+ this.dom.appendChild(button);
149
+ // Create dropdown
150
+ this.dropdown = document.createElement('div');
151
+ this.dropdown.className = `${CSS_PREFIX}__dropdown`;
152
+ this.dropdown.style.display = 'none';
153
+ // Dropdown header
154
+ const header = document.createElement('div');
155
+ header.className = `${CSS_PREFIX}__header`;
156
+ header.textContent = 'Collaborators';
157
+ this.dropdown.appendChild(header);
158
+ // User list
159
+ this.userList = document.createElement('div');
160
+ this.userList.className = `${CSS_PREFIX}__users`;
161
+ this.dropdown.appendChild(this.userList);
162
+ this.dom.appendChild(this.dropdown);
163
+ // Event handlers
164
+ button.addEventListener('click', (e) => {
165
+ e.preventDefault();
166
+ e.stopPropagation();
167
+ this.toggleDropdown();
168
+ });
169
+ // Close on outside click
170
+ const closeHandler = (e) => {
171
+ if (this.isOpen && this.dom && !this.dom.contains(e.target)) {
172
+ this.closeDropdown();
173
+ }
174
+ };
175
+ document.addEventListener('click', closeHandler);
176
+ // Listen for awareness changes
177
+ const awarenessChangeHandler = () => {
178
+ this.updateUserList();
179
+ };
180
+ this.awareness.on('change', awarenessChangeHandler);
181
+ // Listen for provider status changes
182
+ if (this.provider && typeof this.provider.on === 'function') {
183
+ this.provider.on('status', (event) => {
184
+ this.setStatus(event.status);
185
+ });
186
+ }
187
+ // Initial update
188
+ this.updateUserList();
189
+ const update = (_state) => {
190
+ // Always visible
191
+ return true;
192
+ };
193
+ return { dom: this.dom, update };
194
+ }
195
+ setStatus(status) {
196
+ this.status = status;
197
+ if (this.statusDot) {
198
+ this.statusDot.className =
199
+ `${CSS_PREFIX}__dot ${CSS_PREFIX}__dot--${status}`;
200
+ }
201
+ if (this.dom) {
202
+ this.dom.setAttribute('data-status', status);
203
+ const button = this.dom.querySelector('button');
204
+ if (button) {
205
+ const statusLabels = {
206
+ connected: 'Connected',
207
+ connecting: 'Connecting...',
208
+ disconnected: 'Disconnected',
209
+ };
210
+ button.title = `${statusLabels[status]} - Click to see collaborators`;
211
+ }
212
+ }
213
+ }
214
+ updateUserList() {
215
+ if (!this.userList || !this.userCount)
216
+ return;
217
+ const users = getUsers(this.awareness);
218
+ const currentClientId = this.awareness.clientID;
219
+ // Update count (including self)
220
+ this.userCount.textContent = String(users.length);
221
+ // Clear and rebuild user list
222
+ this.userList.innerHTML = '';
223
+ if (users.length === 0) {
224
+ const emptyMessage = document.createElement('div');
225
+ emptyMessage.className = `${CSS_PREFIX}__empty`;
226
+ emptyMessage.textContent = 'No users connected';
227
+ this.userList.appendChild(emptyMessage);
228
+ return;
229
+ }
230
+ // Sort users: current user first, then alphabetically
231
+ const sortedUsers = [...users].sort((a, b) => {
232
+ if (a.clientId === currentClientId)
233
+ return -1;
234
+ if (b.clientId === currentClientId)
235
+ return 1;
236
+ return a.name.localeCompare(b.name);
237
+ });
238
+ sortedUsers.forEach((user) => {
239
+ const userItem = document.createElement('div');
240
+ userItem.className = `${CSS_PREFIX}__user`;
241
+ // Color indicator
242
+ const colorDot = document.createElement('span');
243
+ colorDot.className = `${CSS_PREFIX}__user-color`;
244
+ colorDot.style.backgroundColor = user.color;
245
+ userItem.appendChild(colorDot);
246
+ // User name
247
+ const userName = document.createElement('span');
248
+ userName.className = `${CSS_PREFIX}__user-name`;
249
+ userName.textContent = user.name;
250
+ userItem.appendChild(userName);
251
+ // "You" badge for current user
252
+ if (user.clientId === currentClientId) {
253
+ const youBadge = document.createElement('span');
254
+ youBadge.className = `${CSS_PREFIX}__you-badge`;
255
+ youBadge.textContent = '(you)';
256
+ userItem.appendChild(youBadge);
257
+ }
258
+ this.userList.appendChild(userItem);
259
+ });
260
+ }
261
+ toggleDropdown() {
262
+ if (this.isOpen) {
263
+ this.closeDropdown();
264
+ }
265
+ else {
266
+ this.openDropdown();
267
+ }
268
+ }
269
+ openDropdown() {
270
+ if (!this.dropdown || !this.dom)
271
+ return;
272
+ this.isOpen = true;
273
+ this.dropdown.style.display = 'block';
274
+ this.dom.setAttribute('aria-expanded', 'true');
275
+ this.dom.classList.add(`${CSS_PREFIX}--open`);
276
+ this.updateUserList(); // Refresh on open
277
+ }
278
+ closeDropdown() {
279
+ if (!this.dropdown || !this.dom)
280
+ return;
281
+ this.isOpen = false;
282
+ this.dropdown.style.display = 'none';
283
+ this.dom.setAttribute('aria-expanded', 'false');
284
+ this.dom.classList.remove(`${CSS_PREFIX}--open`);
285
+ }
286
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kerebron/extension-lsp",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "license": "MIT",
5
5
  "module": "./esm/extension-lsp/src/mod.js",
6
6
  "exports": {
@@ -12,6 +12,9 @@
12
12
  },
13
13
  "./LspWebSocketTransport": {
14
14
  "import": "./esm/extension-lsp/src/LspWebSocketTransport.js"
15
+ },
16
+ "./LspStatus": {
17
+ "import": "./esm/extension-lsp/src/LspStatus.js"
15
18
  }
16
19
  },
17
20
  "scripts": {},