@kerebron/editor-kits 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.
@@ -1,8 +1,5 @@
1
1
  import "../../_dnt.polyfills.js";
2
2
  import { Extension } from '../../editor/src/mod.js';
3
- import { ExtensionBasicEditor } from '../../extension-basic-editor/src/ExtensionBasicEditor.js';
4
- import { ExtensionMarkdown } from '../../extension-markdown/src/ExtensionMarkdown.js';
5
- import { ExtensionCustomMenu } from '../../extension-menu/src/mod.js';
6
3
  /**
7
4
  * StarterKit is a collection of commonly used extensions that provide
8
5
  * basic functionality like paragraphs, headings, bold, italic, and more.
@@ -26,7 +23,7 @@ import { ExtensionCustomMenu } from '../../extension-menu/src/mod.js';
26
23
  */
27
24
  export declare class StarterKit extends Extension {
28
25
  name: string;
29
- requires: (ExtensionBasicEditor | ExtensionMarkdown | ExtensionCustomMenu)[];
26
+ requires: any[];
30
27
  }
31
28
  declare const _default: StarterKit;
32
29
  export default _default;
@@ -1 +1 @@
1
- {"version":3,"file":"StarterKit.d.ts","sourceRoot":"","sources":["../../../src/editor-kits/src/StarterKit.ts"],"names":[],"mappings":"AAAA,OAAO,yBAAyB,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,0DAA0D,CAAC;AAChG,OAAO,EAAE,iBAAiB,EAAE,MAAM,mDAAmD,CAAC;AACtF,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAEtE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,UAAW,SAAQ,SAAS;IAC9B,IAAI,SAAiB;IAC9B,QAAQ,qEAIN;CACH;;AAGD,wBAAgC"}
1
+ {"version":3,"file":"StarterKit.d.ts","sourceRoot":"","sources":["../../../src/editor-kits/src/StarterKit.ts"],"names":[],"mappings":"AAAA,OAAO,yBAAyB,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAKpD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,UAAW,SAAQ,SAAS;IAC9B,IAAI,SAAiB;IAC9B,QAAQ,QAIN;CACH;;AAGD,wBAAgC"}
@@ -0,0 +1,34 @@
1
+ import { EditorState } from 'prosemirror-state';
2
+ import { EditorView } from 'prosemirror-view';
3
+ import type { MenuElement } from '../../extension-menu/src/mod.js';
4
+ import type { ExtensionLsp } from './ExtensionLsp.js';
5
+ export type LspConnectionStatus = 'connected' | 'connecting' | 'disconnected';
6
+ export interface LspStatusOptions {
7
+ /** The LSP extension instance to monitor */
8
+ lspExtension: ExtensionLsp;
9
+ /** Optional label to show (e.g., 'LSP' or language name) */
10
+ label?: string;
11
+ }
12
+ /**
13
+ * A menu element that displays LSP connection status.
14
+ * Shows a status indicator dot with an optional label.
15
+ */
16
+ export declare class LspStatusElement implements MenuElement {
17
+ private lspExtension;
18
+ private label;
19
+ private dom;
20
+ private statusDot;
21
+ private labelEl;
22
+ private status;
23
+ private statusCheckInterval;
24
+ constructor(options: LspStatusOptions);
25
+ render(_view: EditorView): {
26
+ dom: HTMLElement;
27
+ update: (state: EditorState) => boolean;
28
+ };
29
+ private getClient;
30
+ private updateStatusFromClient;
31
+ private setStatus;
32
+ destroy(): void;
33
+ }
34
+ //# sourceMappingURL=LspStatus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LspStatus.d.ts","sourceRoot":"","sources":["../../../src/extension-lsp/src/LspStatus.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAItD,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG,YAAY,GAAG,cAAc,CAAC;AAE9E,MAAM,WAAW,gBAAgB;IAC/B,4CAA4C;IAC5C,YAAY,EAAE,YAAY,CAAC;IAC3B,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,qBAAa,gBAAiB,YAAW,WAAW;IAClD,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,GAAG,CAA4B;IACvC,OAAO,CAAC,SAAS,CAA4B;IAC7C,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,mBAAmB,CAA+C;gBAE9D,OAAO,EAAE,gBAAgB;IAKrC,MAAM,CACJ,KAAK,EAAE,UAAU,GAChB;QAAE,GAAG,EAAE,WAAW,CAAC;QAAC,MAAM,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,CAAA;KAAE;IAyChE,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,sBAAsB;IAY9B,OAAO,CAAC,SAAS;IAoBjB,OAAO;CAMR"}
@@ -0,0 +1,127 @@
1
+ const CSS_PREFIX = 'kb-lsp-status';
2
+ /**
3
+ * A menu element that displays LSP connection status.
4
+ * Shows a status indicator dot with an optional label.
5
+ */
6
+ export class LspStatusElement {
7
+ constructor(options) {
8
+ Object.defineProperty(this, "lspExtension", {
9
+ enumerable: true,
10
+ configurable: true,
11
+ writable: true,
12
+ value: void 0
13
+ });
14
+ Object.defineProperty(this, "label", {
15
+ enumerable: true,
16
+ configurable: true,
17
+ writable: true,
18
+ value: void 0
19
+ });
20
+ Object.defineProperty(this, "dom", {
21
+ enumerable: true,
22
+ configurable: true,
23
+ writable: true,
24
+ value: null
25
+ });
26
+ Object.defineProperty(this, "statusDot", {
27
+ enumerable: true,
28
+ configurable: true,
29
+ writable: true,
30
+ value: null
31
+ });
32
+ Object.defineProperty(this, "labelEl", {
33
+ enumerable: true,
34
+ configurable: true,
35
+ writable: true,
36
+ value: null
37
+ });
38
+ Object.defineProperty(this, "status", {
39
+ enumerable: true,
40
+ configurable: true,
41
+ writable: true,
42
+ value: 'disconnected'
43
+ });
44
+ Object.defineProperty(this, "statusCheckInterval", {
45
+ enumerable: true,
46
+ configurable: true,
47
+ writable: true,
48
+ value: null
49
+ });
50
+ this.lspExtension = options.lspExtension;
51
+ this.label = options.label ?? 'LSP';
52
+ }
53
+ render(_view) {
54
+ // Create main container
55
+ this.dom = document.createElement('div');
56
+ this.dom.className = CSS_PREFIX;
57
+ this.dom.setAttribute('role', 'status');
58
+ this.dom.setAttribute('aria-live', 'polite');
59
+ // Create status indicator
60
+ const indicator = document.createElement('div');
61
+ indicator.className = `${CSS_PREFIX}__indicator`;
62
+ // Status dot
63
+ this.statusDot = document.createElement('span');
64
+ this.statusDot.className =
65
+ `${CSS_PREFIX}__dot ${CSS_PREFIX}__dot--disconnected`;
66
+ indicator.appendChild(this.statusDot);
67
+ // Label
68
+ this.labelEl = document.createElement('span');
69
+ this.labelEl.className = `${CSS_PREFIX}__label`;
70
+ this.labelEl.textContent = this.label;
71
+ indicator.appendChild(this.labelEl);
72
+ this.dom.appendChild(indicator);
73
+ // Set initial status based on client state
74
+ this.updateStatusFromClient();
75
+ // Poll for status changes since the client is created lazily
76
+ this.statusCheckInterval = setInterval(() => {
77
+ this.updateStatusFromClient();
78
+ }, 1000);
79
+ const update = (_state) => {
80
+ // Always visible
81
+ return true;
82
+ };
83
+ return { dom: this.dom, update };
84
+ }
85
+ getClient() {
86
+ const mainLang = this.lspExtension.mainLang || 'markdown';
87
+ return this.lspExtension.getClient(mainLang);
88
+ }
89
+ updateStatusFromClient() {
90
+ const client = this.getClient();
91
+ // Check if the client has server capabilities (meaning it's connected and initialized)
92
+ if (client?.serverCapabilities) {
93
+ this.setStatus('connected');
94
+ }
95
+ else if (client?.active) {
96
+ this.setStatus('connecting');
97
+ }
98
+ else {
99
+ this.setStatus('disconnected');
100
+ }
101
+ }
102
+ setStatus(status) {
103
+ if (this.status === status)
104
+ return; // No change
105
+ this.status = status;
106
+ if (this.statusDot) {
107
+ this.statusDot.className =
108
+ `${CSS_PREFIX}__dot ${CSS_PREFIX}__dot--${status}`;
109
+ }
110
+ if (this.dom) {
111
+ this.dom.setAttribute('data-status', status);
112
+ const statusLabels = {
113
+ connected: 'LSP Connected',
114
+ connecting: 'LSP Connecting...',
115
+ disconnected: 'LSP Disconnected',
116
+ };
117
+ this.dom.title = statusLabels[status];
118
+ this.dom.setAttribute('aria-label', statusLabels[status]);
119
+ }
120
+ }
121
+ destroy() {
122
+ if (this.statusCheckInterval) {
123
+ clearInterval(this.statusCheckInterval);
124
+ this.statusCheckInterval = null;
125
+ }
126
+ }
127
+ }
@@ -1,7 +1,6 @@
1
1
  import { EditorState, Plugin } from 'prosemirror-state';
2
2
  import { EditorView } from 'prosemirror-view';
3
3
  import type { CoreEditor } from '../../editor/src/mod.js';
4
- import { CustomMenuOptions } from './ExtensionCustomMenu.js';
5
4
  import type { MenuElement } from './menu.js';
6
5
  interface ToolItem {
7
6
  id: string;
@@ -19,6 +18,7 @@ export declare class CustomMenuView {
19
18
  pinnedDropdownMenu: HTMLElement | null;
20
19
  modal: HTMLElement | null;
21
20
  tools: ToolItem[];
21
+ trailingElements: MenuElement[];
22
22
  root: Document | ShadowRoot;
23
23
  resizeHandle: HTMLElement;
24
24
  editorContainer: HTMLElement;
@@ -26,7 +26,7 @@ export declare class CustomMenuView {
26
26
  private closePinnedDropdownHandler;
27
27
  private submenuStack;
28
28
  private pinnedDropdownStack;
29
- constructor(editorView: EditorView, editor: CoreEditor, content: readonly (readonly MenuElement[])[]);
29
+ constructor(editorView: EditorView, editor: CoreEditor, content: readonly (readonly MenuElement[])[], trailingElements?: readonly MenuElement[]);
30
30
  private initializeTools;
31
31
  /**
32
32
  * Generate a stable ID from a label by converting it to a kebab-case slug.
@@ -54,8 +54,12 @@ export declare class CustomMenuView {
54
54
  update(view: EditorView, prevState: EditorState): void;
55
55
  destroy(): void;
56
56
  }
57
+ export interface CustomMenuPluginOptions {
58
+ content: readonly (readonly MenuElement[])[];
59
+ trailingElements?: readonly MenuElement[];
60
+ }
57
61
  export declare class CustomMenuPlugin extends Plugin {
58
- constructor(editor: CoreEditor, options: CustomMenuOptions);
62
+ constructor(editor: CoreEditor, options: CustomMenuPluginOptions);
59
63
  }
60
64
  export {};
61
65
  //# sourceMappingURL=CustomMenuPlugin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CustomMenuPlugin.d.ts","sourceRoot":"","sources":["../../../src/extension-menu/src/CustomMenuPlugin.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAE1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAuB7C,UAAU,QAAQ;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,WAAW,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,qBAAa,cAAc;IAkBvB,QAAQ,CAAC,UAAU,EAAE,UAAU;IAC/B,QAAQ,CAAC,MAAM,EAAE,UAAU;IAC3B,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC,SAAS,WAAW,EAAE,CAAC,EAAE;IAnBvD,OAAO,EAAE,WAAW,CAAC;IACrB,OAAO,EAAE,WAAW,CAAC;IACrB,YAAY,EAAE,WAAW,CAAC;IAC1B,kBAAkB,EAAE,WAAW,GAAG,IAAI,CAAQ;IAC9C,KAAK,EAAE,WAAW,GAAG,IAAI,CAAQ;IACjC,KAAK,EAAE,QAAQ,EAAE,CAAM;IACvB,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC5B,YAAY,EAAE,WAAW,CAAC;IAC1B,eAAe,EAAE,WAAW,CAAC;IAC7B,OAAO,CAAC,oBAAoB,CAA0C;IACtE,OAAO,CAAC,0BAA0B,CAA0C;IAC5E,OAAO,CAAC,YAAY,CAAmD;IACvE,OAAO,CAAC,mBAAmB,CAEpB;gBAGI,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,SAAS,CAAC,SAAS,WAAW,EAAE,CAAC,EAAE;IAgEvD,OAAO,CAAC,eAAe;IAgCvB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAyBxB;;OAEG;IACH,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,YAAY;IAyBpB,OAAO,CAAC,eAAe;IAsCvB,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,WAAW;IAqCnB,OAAO,CAAC,MAAM;IAQd,OAAO,CAAC,kBAAkB;IA0C1B,OAAO,CAAC,oBAAoB;IAsB5B,OAAO,CAAC,yBAAyB;IAuCjC,OAAO,CAAC,oBAAoB;IA8J5B,OAAO,CAAC,kBAAkB;IA6R1B,OAAO,CAAC,MAAM;IAyLd,OAAO,CAAC,eAAe;IAyHvB,OAAO,CAAC,gBAAgB;IAoBxB,OAAO,CAAC,WAAW;IAgDnB,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW;IAQ/C,OAAO;CA8BR;AAED,qBAAa,gBAAiB,SAAQ,MAAM;gBAC9B,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB;CAO3D"}
1
+ {"version":3,"file":"CustomMenuPlugin.d.ts","sourceRoot":"","sources":["../../../src/extension-menu/src/CustomMenuPlugin.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAI1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAuB7C,UAAU,QAAQ;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,WAAW,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,qBAAa,cAAc;IAmBvB,QAAQ,CAAC,UAAU,EAAE,UAAU;IAC/B,QAAQ,CAAC,MAAM,EAAE,UAAU;IAC3B,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC,SAAS,WAAW,EAAE,CAAC,EAAE;IApBvD,OAAO,EAAE,WAAW,CAAC;IACrB,OAAO,EAAE,WAAW,CAAC;IACrB,YAAY,EAAE,WAAW,CAAC;IAC1B,kBAAkB,EAAE,WAAW,GAAG,IAAI,CAAQ;IAC9C,KAAK,EAAE,WAAW,GAAG,IAAI,CAAQ;IACjC,KAAK,EAAE,QAAQ,EAAE,CAAM;IACvB,gBAAgB,EAAE,WAAW,EAAE,CAAM;IACrC,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC5B,YAAY,EAAE,WAAW,CAAC;IAC1B,eAAe,EAAE,WAAW,CAAC;IAC7B,OAAO,CAAC,oBAAoB,CAA0C;IACtE,OAAO,CAAC,0BAA0B,CAA0C;IAC5E,OAAO,CAAC,YAAY,CAAmD;IACvE,OAAO,CAAC,mBAAmB,CAEpB;gBAGI,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,SAAS,CAAC,SAAS,WAAW,EAAE,CAAC,EAAE,EACrD,gBAAgB,CAAC,EAAE,SAAS,WAAW,EAAE;IAmE3C,OAAO,CAAC,eAAe;IAgCvB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAyBxB;;OAEG;IACH,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,YAAY;IAyBpB,OAAO,CAAC,eAAe;IAsCvB,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,WAAW;IAqCnB,OAAO,CAAC,MAAM;IAQd,OAAO,CAAC,kBAAkB;IA0C1B,OAAO,CAAC,oBAAoB;IAsB5B,OAAO,CAAC,yBAAyB;IAuCjC,OAAO,CAAC,oBAAoB;IA8L5B,OAAO,CAAC,kBAAkB;IA6R1B,OAAO,CAAC,MAAM;IAuMd,OAAO,CAAC,eAAe;IAyHvB,OAAO,CAAC,gBAAgB;IAoBxB,OAAO,CAAC,WAAW;IAgDnB,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW;IAQ/C,OAAO;CA8BR;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,SAAS,CAAC,SAAS,WAAW,EAAE,CAAC,EAAE,CAAC;IAC7C,gBAAgB,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;CAC3C;AAED,qBAAa,gBAAiB,SAAQ,MAAM;gBAC9B,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,uBAAuB;CAYjE"}
@@ -20,7 +20,7 @@ function debug(...args) {
20
20
  }
21
21
  }
22
22
  export class CustomMenuView {
23
- constructor(editorView, editor, content) {
23
+ constructor(editorView, editor, content, trailingElements) {
24
24
  Object.defineProperty(this, "editorView", {
25
25
  enumerable: true,
26
26
  configurable: true,
@@ -75,6 +75,12 @@ export class CustomMenuView {
75
75
  writable: true,
76
76
  value: []
77
77
  });
78
+ Object.defineProperty(this, "trailingElements", {
79
+ enumerable: true,
80
+ configurable: true,
81
+ writable: true,
82
+ value: []
83
+ });
78
84
  Object.defineProperty(this, "root", {
79
85
  enumerable: true,
80
86
  configurable: true,
@@ -120,6 +126,8 @@ export class CustomMenuView {
120
126
  debug('CustomMenuView constructor called');
121
127
  debug('Content groups:', content.length);
122
128
  this.root = editorView.root;
129
+ // Store trailing elements
130
+ this.trailingElements = trailingElements ? [...trailingElements] : [];
123
131
  // Create wrapper
124
132
  this.wrapper = document.createElement('div');
125
133
  this.wrapper.classList.add(CSS_PREFIX + '__wrapper');
@@ -434,6 +442,37 @@ export class CustomMenuView {
434
442
  // Create dropdown menu
435
443
  this.pinnedDropdownMenu = document.createElement('div');
436
444
  this.pinnedDropdownMenu.classList.add(CSS_PREFIX + '__pinned-dropdown');
445
+ // Inherit theme CSS variables from editor container
446
+ // This ensures dropdowns match the editor's theme even when appended to body
447
+ const editorContainer = this.editorView.dom.closest('.kb-component') ||
448
+ this.editorView.dom.parentElement;
449
+ if (editorContainer) {
450
+ const computedStyle = getComputedStyle(editorContainer);
451
+ const themeVars = [
452
+ // New unified naming
453
+ '--kb-menu-dropdown-bg',
454
+ '--kb-menu-dropdown-border',
455
+ '--kb-menu-dropdown-text',
456
+ // Legacy naming (for backwards compatibility)
457
+ '--kb-menu-dropdown-dark-bg',
458
+ '--kb-menu-dropdown-dark-border',
459
+ '--kb-menu-dropdown-dark-text',
460
+ // General theme colors
461
+ '--kb-color-surface',
462
+ '--kb-color-surface-elevated',
463
+ '--kb-color-surface-hover',
464
+ '--kb-color-border',
465
+ '--kb-color-border-strong',
466
+ '--kb-color-text',
467
+ '--kb-color-text-muted',
468
+ ];
469
+ themeVars.forEach((varName) => {
470
+ const value = computedStyle.getPropertyValue(varName).trim();
471
+ if (value) {
472
+ this.pinnedDropdownMenu.style.setProperty(varName, value);
473
+ }
474
+ });
475
+ }
437
476
  // Position it below the trigger element
438
477
  const rect = triggerElement.getBoundingClientRect();
439
478
  this.pinnedDropdownMenu.style.position = 'absolute';
@@ -933,10 +972,27 @@ export class CustomMenuView {
933
972
  });
934
973
  this.toolbar.appendChild(overflowToggle);
935
974
  }
975
+ // Render trailing elements (always visible, not pinnable)
976
+ if (this.trailingElements.length > 0) {
977
+ // Add separator before trailing elements
978
+ const separator = document.createElement('div');
979
+ separator.classList.add(CSS_PREFIX + '__separator');
980
+ separator.classList.add(CSS_PREFIX + '__separator--trailing');
981
+ this.toolbar.appendChild(separator);
982
+ // Create a trailing group container
983
+ const trailingGroup = document.createElement('div');
984
+ trailingGroup.classList.add(CSS_PREFIX + '__trailing-group');
985
+ this.trailingElements.forEach((element) => {
986
+ const { dom, update } = element.render(this.editorView);
987
+ const wrapper = document.createElement('span');
988
+ wrapper.classList.add(CSS_PREFIX + '__trailing-item');
989
+ wrapper.appendChild(dom);
990
+ trailingGroup.appendChild(wrapper);
991
+ });
992
+ this.toolbar.appendChild(trailingGroup);
993
+ }
936
994
  // Render overflow menu content
937
995
  this.renderOverflowMenu();
938
- console.log('[kb-custom-menu] render() complete. Toolbar children:', this.toolbar.children.length);
939
- console.log('[kb-custom-menu] Toolbar HTML:', this.toolbar.innerHTML.substring(0, 200));
940
996
  }
941
997
  openManageModal() {
942
998
  // Close overflow menu
@@ -1123,7 +1179,7 @@ export class CustomMenuPlugin extends Plugin {
1123
1179
  constructor(editor, options) {
1124
1180
  super({
1125
1181
  view(editorView) {
1126
- return new CustomMenuView(editorView, editor, options.content);
1182
+ return new CustomMenuView(editorView, editor, options.content, options.trailingElements);
1127
1183
  },
1128
1184
  });
1129
1185
  }
@@ -2,9 +2,12 @@ import { Plugin } from 'prosemirror-state';
2
2
  import { Extension } from '../../editor/src/mod.js';
3
3
  import { type MenuElement } from './menu.js';
4
4
  export interface CustomMenuOptions {
5
- content: readonly (readonly MenuElement[])[];
5
+ content?: readonly (readonly MenuElement[])[];
6
+ trailingElements?: readonly MenuElement[];
7
+ autoAddCollaborationStatus?: boolean;
8
+ autoAddLspStatus?: boolean;
6
9
  }
7
- export declare class ExtensionCustomMenu extends Extension {
10
+ export declare class ExtensionCustomMenu extends Extension<CustomMenuOptions> {
8
11
  name: string;
9
12
  getProseMirrorPlugins(): Plugin[];
10
13
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ExtensionCustomMenu.d.ts","sourceRoot":"","sources":["../../../src/extension-menu/src/ExtensionCustomMenu.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE3C,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAI7C,MAAM,WAAW,iBAAiB;IAEhC,OAAO,EAAE,SAAS,CAAC,SAAS,WAAW,EAAE,CAAC,EAAE,CAAC;CAC9C;AAGD,qBAAa,mBAAoB,SAAQ,SAAS;IAChD,IAAI,SAAgB;IAEX,qBAAqB,IAAI,MAAM,EAAE;CAS3C"}
1
+ {"version":3,"file":"ExtensionCustomMenu.d.ts","sourceRoot":"","sources":["../../../src/extension-menu/src/ExtensionCustomMenu.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE3C,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAM7C,MAAM,WAAW,iBAAiB;IAEhC,OAAO,CAAC,EAAE,SAAS,CAAC,SAAS,WAAW,EAAE,CAAC,EAAE,CAAC;IAG9C,gBAAgB,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IAG1C,0BAA0B,CAAC,EAAE,OAAO,CAAC;IAGrC,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAGD,qBAAa,mBAAoB,SAAQ,SAAS,CAAC,iBAAiB,CAAC;IACnE,IAAI,SAAgB;IAEX,qBAAqB,IAAI,MAAM,EAAE;CA2C3C"}
@@ -1,6 +1,8 @@
1
1
  import { Extension } from '../../editor/src/mod.js';
2
2
  import { buildMenu } from './buildMenu.js';
3
3
  import { CustomMenuPlugin } from './CustomMenuPlugin.js';
4
+ import { CollaborationStatusElement } from '../../extension-yjs/src/CollaborationStatus.js';
5
+ import { LspStatusElement } from '../../extension-lsp/src/LspStatus.js';
4
6
  /// Extension for a customizable menu with pinned items
5
7
  export class ExtensionCustomMenu extends Extension {
6
8
  constructor() {
@@ -13,10 +15,42 @@ export class ExtensionCustomMenu extends Extension {
13
15
  });
14
16
  }
15
17
  getProseMirrorPlugins() {
16
- const content = buildMenu(this.editor, this.editor.schema);
18
+ const content = this.config?.content ??
19
+ buildMenu(this.editor, this.editor.schema);
20
+ const trailingElements = this.config?.trailingElements
21
+ ? [...this.config.trailingElements]
22
+ : [];
23
+ // Auto-add collaboration status if YJS extension is present
24
+ const autoAdd = this.config?.autoAddCollaborationStatus ?? true;
25
+ if (autoAdd) {
26
+ const yjsExtension = this.editor.getExtension('yjs');
27
+ if (yjsExtension) {
28
+ const yjsConfig = yjsExtension.config;
29
+ if (yjsConfig?.provider?.awareness) {
30
+ const collabStatus = new CollaborationStatusElement({
31
+ awareness: yjsConfig.provider.awareness,
32
+ provider: yjsConfig.provider,
33
+ });
34
+ trailingElements.push(collabStatus);
35
+ }
36
+ }
37
+ }
38
+ // Auto-add LSP status if LSP extension is present
39
+ const autoAddLsp = this.config?.autoAddLspStatus ?? true;
40
+ if (autoAddLsp) {
41
+ const lspExtension = this.editor.getExtension('lsp');
42
+ if (lspExtension) {
43
+ const lspStatus = new LspStatusElement({
44
+ lspExtension: lspExtension,
45
+ label: 'LSP',
46
+ });
47
+ trailingElements.push(lspStatus);
48
+ }
49
+ }
17
50
  return [
18
51
  new CustomMenuPlugin(this.editor, {
19
52
  content,
53
+ trailingElements,
20
54
  }),
21
55
  ];
22
56
  }
@@ -0,0 +1,59 @@
1
+ import { EditorState, Plugin, PluginKey } from 'prosemirror-state';
2
+ import { EditorView } from 'prosemirror-view';
3
+ import type * as awarenessProtocol from 'y-protocols/awareness';
4
+ import type { MenuElement } from '../../extension-menu/src/mod.js';
5
+ export interface CollaborationUser {
6
+ clientId: number;
7
+ name: string;
8
+ color: string;
9
+ colorLight?: string;
10
+ }
11
+ export interface CollaborationStatusOptions {
12
+ /** The Yjs awareness instance */
13
+ awareness: awarenessProtocol.Awareness;
14
+ /** Called when connection status changes */
15
+ onStatusChange?: (status: 'connected' | 'connecting' | 'disconnected') => void;
16
+ }
17
+ /**
18
+ * Plugin key for accessing collaboration status state
19
+ */
20
+ export declare const collaborationStatusPluginKey: PluginKey<CollaborationStatusState>;
21
+ export interface CollaborationStatusState {
22
+ status: 'connected' | 'connecting' | 'disconnected';
23
+ users: CollaborationUser[];
24
+ }
25
+ /**
26
+ * Creates a ProseMirror plugin that tracks collaboration status and user presence.
27
+ * This plugin maintains state that can be accessed by other components.
28
+ */
29
+ export declare function collaborationStatusPlugin(awareness: awarenessProtocol.Awareness, options?: {
30
+ onStatusChange?: (status: 'connected' | 'connecting' | 'disconnected') => void;
31
+ }): Plugin;
32
+ /**
33
+ * A menu element that displays collaboration status, user count, and a dropdown of users.
34
+ */
35
+ export declare class CollaborationStatusElement implements MenuElement {
36
+ private awareness;
37
+ private provider;
38
+ private dom;
39
+ private statusDot;
40
+ private userCount;
41
+ private dropdown;
42
+ private userList;
43
+ private isOpen;
44
+ private status;
45
+ constructor(options: {
46
+ awareness: awarenessProtocol.Awareness;
47
+ provider: any;
48
+ });
49
+ render(view: EditorView): {
50
+ dom: HTMLElement;
51
+ update: (state: EditorState) => boolean;
52
+ };
53
+ private setStatus;
54
+ private updateUserList;
55
+ private toggleDropdown;
56
+ private openDropdown;
57
+ private closeDropdown;
58
+ }
59
+ //# sourceMappingURL=CollaborationStatus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CollaborationStatus.d.ts","sourceRoot":"","sources":["../../../src/extension-yjs/src/CollaborationStatus.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,KAAK,iBAAiB,MAAM,uBAAuB,CAAC;AAEhE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAInE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,0BAA0B;IACzC,iCAAiC;IACjC,SAAS,EAAE,iBAAiB,CAAC,SAAS,CAAC;IACvC,4CAA4C;IAC5C,cAAc,CAAC,EAAE,CACf,MAAM,EAAE,WAAW,GAAG,YAAY,GAAG,cAAc,KAChD,IAAI,CAAC;CACX;AAED;;GAEG;AACH,eAAO,MAAM,4BAA4B,qCAEhB,CAAC;AAE1B,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,WAAW,GAAG,YAAY,GAAG,cAAc,CAAC;IACpD,KAAK,EAAE,iBAAiB,EAAE,CAAC;CAC5B;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,iBAAiB,CAAC,SAAS,EACtC,OAAO,GAAE;IACP,cAAc,CAAC,EAAE,CACf,MAAM,EAAE,WAAW,GAAG,YAAY,GAAG,cAAc,KAChD,IAAI,CAAC;CACN,GACL,MAAM,CAsCR;AAiBD;;GAEG;AACH,qBAAa,0BAA2B,YAAW,WAAW;IAC5D,OAAO,CAAC,SAAS,CAA8B;IAC/C,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,GAAG,CAA4B;IACvC,OAAO,CAAC,SAAS,CAA4B;IAC7C,OAAO,CAAC,SAAS,CAA4B;IAC7C,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAA6D;gBAGzE,OAAO,EAAE;QAAE,SAAS,EAAE,iBAAiB,CAAC,SAAS,CAAC;QAAC,QAAQ,EAAE,GAAG,CAAA;KAAE;IAMpE,MAAM,CACJ,IAAI,EAAE,UAAU,GACf;QAAE,GAAG,EAAE,WAAW,CAAC;QAAC,MAAM,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,CAAA;KAAE;IA+FhE,OAAO,CAAC,SAAS;IAoBjB,OAAO,CAAC,cAAc;IAuDtB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,aAAa;CAOtB"}
@@ -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/editor-kits",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "license": "MIT",
5
5
  "module": "./esm/editor-kits/src/mod.js",
6
6
  "exports": {