@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.
- package/README.md +157 -47
- package/assets/lsp-status.css +60 -0
- package/esm/extension-lsp/src/LspStatus.d.ts +35 -0
- package/esm/extension-lsp/src/LspStatus.d.ts.map +1 -0
- package/esm/extension-lsp/src/LspStatus.js +128 -0
- package/esm/extension-menu/src/CustomMenuPlugin.d.ts +65 -0
- package/esm/extension-menu/src/CustomMenuPlugin.d.ts.map +1 -0
- package/esm/extension-menu/src/CustomMenuPlugin.js +1186 -0
- package/esm/extension-menu/src/ExtensionCustomMenu.d.ts +14 -0
- package/esm/extension-menu/src/ExtensionCustomMenu.d.ts.map +1 -0
- package/esm/extension-menu/src/ExtensionCustomMenu.js +57 -0
- package/esm/extension-menu/src/buildMenu.d.ts +5 -0
- package/esm/extension-menu/src/buildMenu.d.ts.map +1 -0
- package/esm/extension-menu/src/buildMenu.js +331 -0
- package/esm/extension-menu/src/icons.d.ts +15 -0
- package/esm/extension-menu/src/icons.d.ts.map +1 -0
- package/esm/extension-menu/src/icons.js +123 -0
- package/esm/extension-menu/src/menu.d.ts +81 -0
- package/esm/extension-menu/src/menu.d.ts.map +1 -0
- package/esm/extension-menu/src/menu.js +350 -0
- package/esm/extension-menu/src/mod.d.ts +3 -0
- package/esm/extension-menu/src/mod.d.ts.map +1 -0
- package/esm/extension-menu/src/mod.js +2 -0
- package/esm/extension-menu/src/prompt.d.ts +36 -0
- package/esm/extension-menu/src/prompt.d.ts.map +1 -0
- package/esm/extension-menu/src/prompt.js +158 -0
- package/esm/extension-yjs/src/CollaborationStatus.d.ts +59 -0
- package/esm/extension-yjs/src/CollaborationStatus.d.ts.map +1 -0
- package/esm/extension-yjs/src/CollaborationStatus.js +286 -0
- 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
|
+
"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": {},
|