@blockslides/extension-slash-menu-builder 0.1.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/LICENSE.md +36 -0
- package/dist/index.cjs +501 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +183 -0
- package/dist/index.d.ts +183 -0
- package/dist/index.js +460 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +178 -0
- package/dist/styles.css.map +1 -0
- package/dist/styles.d.cts +2 -0
- package/dist/styles.d.ts +2 -0
- package/package.json +54 -0
- package/src/filter.ts +73 -0
- package/src/index.ts +10 -0
- package/src/menu-renderer.ts +345 -0
- package/src/slash-menu-builder.ts +226 -0
- package/src/styles.css +300 -0
- package/src/types.ts +69 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 BlockSlides
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Acknowledgments
|
|
26
|
+
|
|
27
|
+
This project includes code and patterns adapted from [Tiptap](https://tiptap.dev),
|
|
28
|
+
an open-source headless editor framework for web artisans.
|
|
29
|
+
|
|
30
|
+
**Tiptap License:**
|
|
31
|
+
|
|
32
|
+
- Copyright (c) 2025, Tiptap GmbH
|
|
33
|
+
- Licensed under MIT License
|
|
34
|
+
- Original source: https://github.com/ueberdosis/tiptap
|
|
35
|
+
|
|
36
|
+
See `_tiptap/LICENSE.md` for the full Tiptap license.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
SlashMenuBuilder: () => SlashMenuBuilder,
|
|
34
|
+
SlashMenuRenderer: () => SlashMenuRenderer,
|
|
35
|
+
filterItems: () => filterItems,
|
|
36
|
+
flattenItems: () => flattenItems,
|
|
37
|
+
getSelectableItems: () => getSelectableItems
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(index_exports);
|
|
40
|
+
|
|
41
|
+
// src/slash-menu-builder.ts
|
|
42
|
+
var import_core = require("@blockslides/core");
|
|
43
|
+
var import_extension_suggestion = __toESM(require("@blockslides/extension-suggestion"), 1);
|
|
44
|
+
var import_state = require("@blockslides/pm/state");
|
|
45
|
+
|
|
46
|
+
// src/filter.ts
|
|
47
|
+
function filterItems(items, query, filterKeys = ["label", "description", "keywords"]) {
|
|
48
|
+
if (!query || query.trim() === "") {
|
|
49
|
+
return items;
|
|
50
|
+
}
|
|
51
|
+
const normalizedQuery = query.toLowerCase().trim();
|
|
52
|
+
return items.map((item) => {
|
|
53
|
+
if (item.group && item.items) {
|
|
54
|
+
const filteredChildren = filterItems(item.items, query, filterKeys);
|
|
55
|
+
if (filteredChildren.length > 0) {
|
|
56
|
+
return { ...item, items: filteredChildren };
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const matches = filterKeys.some((key) => {
|
|
61
|
+
if (key === "label" && item.label) {
|
|
62
|
+
return item.label.toLowerCase().includes(normalizedQuery);
|
|
63
|
+
}
|
|
64
|
+
if (key === "description" && item.description) {
|
|
65
|
+
return item.description.toLowerCase().includes(normalizedQuery);
|
|
66
|
+
}
|
|
67
|
+
if (key === "keywords" && item.keywords) {
|
|
68
|
+
return item.keywords.some((keyword) => keyword.toLowerCase().includes(normalizedQuery));
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
});
|
|
72
|
+
return matches ? item : null;
|
|
73
|
+
}).filter((item) => item !== null);
|
|
74
|
+
}
|
|
75
|
+
function flattenItems(items) {
|
|
76
|
+
const flattened = [];
|
|
77
|
+
for (const item of items) {
|
|
78
|
+
flattened.push(item);
|
|
79
|
+
if (item.group && item.items) {
|
|
80
|
+
flattened.push(...flattenItems(item.items));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return flattened;
|
|
84
|
+
}
|
|
85
|
+
function getSelectableItems(items) {
|
|
86
|
+
return flattenItems(items).filter((item) => !item.group);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/menu-renderer.ts
|
|
90
|
+
var SlashMenuRenderer = class {
|
|
91
|
+
constructor(items, onSelect, options = {}) {
|
|
92
|
+
this.items = items;
|
|
93
|
+
this.onSelect = onSelect;
|
|
94
|
+
this.options = options;
|
|
95
|
+
this.selectedIndex = 0;
|
|
96
|
+
this.selectableItems = [];
|
|
97
|
+
this.searchValue = "";
|
|
98
|
+
this.searchInput = null;
|
|
99
|
+
this.element = this.createElement();
|
|
100
|
+
this.selectableItems = this.getSelectableItems(items);
|
|
101
|
+
}
|
|
102
|
+
createElement() {
|
|
103
|
+
const menu = document.createElement("div");
|
|
104
|
+
menu.className = `slash-menu ${this.options.className || ""}`.trim();
|
|
105
|
+
if (this.options.maxHeight) {
|
|
106
|
+
menu.style.maxHeight = `${this.options.maxHeight}px`;
|
|
107
|
+
}
|
|
108
|
+
return menu;
|
|
109
|
+
}
|
|
110
|
+
getSelectableItems(items) {
|
|
111
|
+
const selectable = [];
|
|
112
|
+
for (const item of items) {
|
|
113
|
+
if (!item.group) {
|
|
114
|
+
selectable.push(item);
|
|
115
|
+
}
|
|
116
|
+
if (item.group && item.items) {
|
|
117
|
+
selectable.push(...this.getSelectableItems(item.items));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return selectable;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Renders the search bar
|
|
124
|
+
*/
|
|
125
|
+
renderSearchBar() {
|
|
126
|
+
const searchContainer = document.createElement("div");
|
|
127
|
+
searchContainer.className = "slash-menu-search";
|
|
128
|
+
this.searchInput = document.createElement("input");
|
|
129
|
+
this.searchInput.type = "text";
|
|
130
|
+
this.searchInput.className = "slash-menu-search-input";
|
|
131
|
+
this.searchInput.placeholder = this.options.searchPlaceholder || "Search commands...";
|
|
132
|
+
this.searchInput.value = this.searchValue;
|
|
133
|
+
this.searchInput.addEventListener("input", (e) => {
|
|
134
|
+
this.searchValue = e.target.value.toLowerCase();
|
|
135
|
+
this.filterAndRenderItems();
|
|
136
|
+
});
|
|
137
|
+
this.searchInput.addEventListener("mousedown", (e) => {
|
|
138
|
+
e.stopPropagation();
|
|
139
|
+
});
|
|
140
|
+
this.searchInput.addEventListener("keydown", (e) => {
|
|
141
|
+
if (e.key === "ArrowDown") {
|
|
142
|
+
e.preventDefault();
|
|
143
|
+
this.selectNext();
|
|
144
|
+
} else if (e.key === "ArrowUp") {
|
|
145
|
+
e.preventDefault();
|
|
146
|
+
this.selectPrevious();
|
|
147
|
+
} else if (e.key === "Enter") {
|
|
148
|
+
e.preventDefault();
|
|
149
|
+
this.selectCurrent();
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
searchContainer.appendChild(this.searchInput);
|
|
153
|
+
return searchContainer;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Filters items based on search value
|
|
157
|
+
*/
|
|
158
|
+
getFilteredItems() {
|
|
159
|
+
if (!this.searchValue) {
|
|
160
|
+
return this.items;
|
|
161
|
+
}
|
|
162
|
+
const filterRecursive = (items) => {
|
|
163
|
+
return items.filter((item) => {
|
|
164
|
+
var _a, _b;
|
|
165
|
+
if (item.group && item.items) {
|
|
166
|
+
const filteredChildren = filterRecursive(item.items);
|
|
167
|
+
return filteredChildren.length > 0;
|
|
168
|
+
}
|
|
169
|
+
const searchLower = this.searchValue.toLowerCase();
|
|
170
|
+
return item.label.toLowerCase().includes(searchLower) || ((_a = item.description) == null ? void 0 : _a.toLowerCase().includes(searchLower)) || ((_b = item.keywords) == null ? void 0 : _b.some((k) => k.toLowerCase().includes(searchLower)));
|
|
171
|
+
}).map((item) => {
|
|
172
|
+
if (item.group && item.items) {
|
|
173
|
+
return { ...item, items: filterRecursive(item.items) };
|
|
174
|
+
}
|
|
175
|
+
return item;
|
|
176
|
+
});
|
|
177
|
+
};
|
|
178
|
+
return filterRecursive(this.items);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Filters and re-renders items based on search
|
|
182
|
+
*/
|
|
183
|
+
filterAndRenderItems() {
|
|
184
|
+
const filteredItems = this.getFilteredItems();
|
|
185
|
+
this.selectableItems = this.getSelectableItems(filteredItems);
|
|
186
|
+
this.selectedIndex = 0;
|
|
187
|
+
const itemsContainer = this.element.querySelector(".slash-menu-items");
|
|
188
|
+
if (itemsContainer) {
|
|
189
|
+
itemsContainer.innerHTML = "";
|
|
190
|
+
if (this.selectableItems.length === 0) {
|
|
191
|
+
const empty = document.createElement("div");
|
|
192
|
+
empty.className = "slash-menu-empty";
|
|
193
|
+
empty.textContent = this.options.placeholder || "No commands found";
|
|
194
|
+
itemsContainer.appendChild(empty);
|
|
195
|
+
} else {
|
|
196
|
+
this.renderItems(filteredItems, itemsContainer);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Renders the menu items into the DOM
|
|
202
|
+
*/
|
|
203
|
+
render() {
|
|
204
|
+
this.element.innerHTML = "";
|
|
205
|
+
const searchBar = this.renderSearchBar();
|
|
206
|
+
this.element.appendChild(searchBar);
|
|
207
|
+
const listContainer = document.createElement("div");
|
|
208
|
+
listContainer.className = "slash-menu-items";
|
|
209
|
+
if (this.selectableItems.length === 0) {
|
|
210
|
+
const empty = document.createElement("div");
|
|
211
|
+
empty.className = "slash-menu-empty";
|
|
212
|
+
empty.textContent = this.options.placeholder || "No commands found";
|
|
213
|
+
listContainer.appendChild(empty);
|
|
214
|
+
} else {
|
|
215
|
+
this.renderItems(this.items, listContainer);
|
|
216
|
+
}
|
|
217
|
+
this.element.appendChild(listContainer);
|
|
218
|
+
setTimeout(() => {
|
|
219
|
+
var _a;
|
|
220
|
+
(_a = this.searchInput) == null ? void 0 : _a.focus();
|
|
221
|
+
}, 0);
|
|
222
|
+
}
|
|
223
|
+
renderItems(items, container, isNested = false) {
|
|
224
|
+
for (const item of items) {
|
|
225
|
+
if (item.group && item.items) {
|
|
226
|
+
this.renderGroup(item, container);
|
|
227
|
+
} else {
|
|
228
|
+
this.renderItem(item, container, isNested);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
renderGroup(group, container) {
|
|
233
|
+
const groupElement = document.createElement("div");
|
|
234
|
+
groupElement.className = "slash-menu-group";
|
|
235
|
+
const header = document.createElement("div");
|
|
236
|
+
header.className = "slash-menu-group-header";
|
|
237
|
+
header.textContent = group.label;
|
|
238
|
+
groupElement.appendChild(header);
|
|
239
|
+
const itemsContainer = document.createElement("div");
|
|
240
|
+
itemsContainer.className = "slash-menu-group-items";
|
|
241
|
+
if (group.items) {
|
|
242
|
+
this.renderItems(group.items, itemsContainer, true);
|
|
243
|
+
}
|
|
244
|
+
groupElement.appendChild(itemsContainer);
|
|
245
|
+
container.appendChild(groupElement);
|
|
246
|
+
}
|
|
247
|
+
renderItem(item, container, isNested) {
|
|
248
|
+
const itemElement = document.createElement("div");
|
|
249
|
+
const globalIndex = this.selectableItems.indexOf(item);
|
|
250
|
+
const isSelected = globalIndex === this.selectedIndex;
|
|
251
|
+
itemElement.className = `slash-menu-item ${isSelected ? "selected" : ""} ${isNested ? "nested" : ""}`.trim();
|
|
252
|
+
itemElement.dataset.key = item.key;
|
|
253
|
+
if (item.icon) {
|
|
254
|
+
const icon = document.createElement("div");
|
|
255
|
+
icon.className = "slash-menu-item-icon";
|
|
256
|
+
if (typeof item.icon === "string") {
|
|
257
|
+
icon.textContent = item.icon;
|
|
258
|
+
}
|
|
259
|
+
itemElement.appendChild(icon);
|
|
260
|
+
}
|
|
261
|
+
const content = document.createElement("div");
|
|
262
|
+
content.className = "slash-menu-item-content";
|
|
263
|
+
const label = document.createElement("div");
|
|
264
|
+
label.className = "slash-menu-item-label";
|
|
265
|
+
label.textContent = item.label;
|
|
266
|
+
content.appendChild(label);
|
|
267
|
+
if (item.description) {
|
|
268
|
+
const description = document.createElement("div");
|
|
269
|
+
description.className = "slash-menu-item-description";
|
|
270
|
+
description.textContent = item.description;
|
|
271
|
+
content.appendChild(description);
|
|
272
|
+
}
|
|
273
|
+
itemElement.appendChild(content);
|
|
274
|
+
itemElement.addEventListener("mousedown", (e) => {
|
|
275
|
+
e.preventDefault();
|
|
276
|
+
e.stopPropagation();
|
|
277
|
+
this.onSelect(item);
|
|
278
|
+
});
|
|
279
|
+
itemElement.addEventListener("mouseenter", () => {
|
|
280
|
+
this.selectedIndex = globalIndex;
|
|
281
|
+
this.updateSelection();
|
|
282
|
+
});
|
|
283
|
+
container.appendChild(itemElement);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Updates the menu with new filtered items
|
|
287
|
+
*/
|
|
288
|
+
updateItems(items) {
|
|
289
|
+
this.items = items;
|
|
290
|
+
this.selectableItems = this.getSelectableItems(items);
|
|
291
|
+
this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.selectableItems.length - 1));
|
|
292
|
+
this.render();
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Updates the selected class on items without re-rendering
|
|
296
|
+
*/
|
|
297
|
+
updateSelection() {
|
|
298
|
+
const allItems = this.element.querySelectorAll(".slash-menu-item");
|
|
299
|
+
allItems.forEach((el) => {
|
|
300
|
+
const itemKey = el.dataset.key;
|
|
301
|
+
const itemIndex = this.selectableItems.findIndex((item) => item.key === itemKey);
|
|
302
|
+
if (itemIndex === this.selectedIndex) {
|
|
303
|
+
el.classList.add("selected");
|
|
304
|
+
} else {
|
|
305
|
+
el.classList.remove("selected");
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Moves selection up
|
|
311
|
+
*/
|
|
312
|
+
selectPrevious() {
|
|
313
|
+
this.selectedIndex = Math.max(0, this.selectedIndex - 1);
|
|
314
|
+
this.updateSelection();
|
|
315
|
+
this.scrollToSelected();
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Moves selection down
|
|
319
|
+
*/
|
|
320
|
+
selectNext() {
|
|
321
|
+
this.selectedIndex = Math.min(this.selectableItems.length - 1, this.selectedIndex + 1);
|
|
322
|
+
this.updateSelection();
|
|
323
|
+
this.scrollToSelected();
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Executes the currently selected item
|
|
327
|
+
*/
|
|
328
|
+
selectCurrent() {
|
|
329
|
+
const selectedItem = this.selectableItems[this.selectedIndex];
|
|
330
|
+
if (selectedItem) {
|
|
331
|
+
this.onSelect(selectedItem);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Scrolls the selected item into view
|
|
336
|
+
*/
|
|
337
|
+
scrollToSelected() {
|
|
338
|
+
const selectedElement = this.element.querySelector(".slash-menu-item.selected");
|
|
339
|
+
if (selectedElement) {
|
|
340
|
+
selectedElement.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Gets the DOM element
|
|
345
|
+
*/
|
|
346
|
+
getElement() {
|
|
347
|
+
return this.element;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Cleans up the renderer
|
|
351
|
+
*/
|
|
352
|
+
destroy() {
|
|
353
|
+
this.element.remove();
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
// src/slash-menu-builder.ts
|
|
358
|
+
var SlashMenuBuilder = import_core.Extension.create({
|
|
359
|
+
name: "slashMenuBuilder",
|
|
360
|
+
addOptions() {
|
|
361
|
+
return {
|
|
362
|
+
items: [],
|
|
363
|
+
maxHeight: 400,
|
|
364
|
+
filterKeys: ["label", "description", "keywords"],
|
|
365
|
+
placeholder: "No commands found",
|
|
366
|
+
searchPlaceholder: "Search commands...",
|
|
367
|
+
className: ""
|
|
368
|
+
};
|
|
369
|
+
},
|
|
370
|
+
addStorage() {
|
|
371
|
+
return {
|
|
372
|
+
renderer: null
|
|
373
|
+
};
|
|
374
|
+
},
|
|
375
|
+
addProseMirrorPlugins() {
|
|
376
|
+
const editor = this.editor;
|
|
377
|
+
const pluginKey = new import_state.PluginKey("slashMenuBuilder");
|
|
378
|
+
const updatePopupPosition = (props, popup) => {
|
|
379
|
+
if (!props.clientRect) return;
|
|
380
|
+
const rect = props.clientRect();
|
|
381
|
+
if (!rect) return;
|
|
382
|
+
const menuHeight = popup.offsetHeight;
|
|
383
|
+
const spaceBelow = window.innerHeight - rect.bottom;
|
|
384
|
+
const spaceAbove = rect.top;
|
|
385
|
+
if (spaceBelow >= menuHeight || spaceBelow >= spaceAbove) {
|
|
386
|
+
popup.style.top = `${rect.bottom + window.scrollY + 8}px`;
|
|
387
|
+
popup.style.bottom = "auto";
|
|
388
|
+
} else {
|
|
389
|
+
popup.style.bottom = `${window.innerHeight - rect.top - window.scrollY + 8}px`;
|
|
390
|
+
popup.style.top = "auto";
|
|
391
|
+
}
|
|
392
|
+
popup.style.left = `${rect.left + window.scrollX}px`;
|
|
393
|
+
};
|
|
394
|
+
return [
|
|
395
|
+
(0, import_extension_suggestion.default)({
|
|
396
|
+
editor,
|
|
397
|
+
char: "/",
|
|
398
|
+
pluginKey,
|
|
399
|
+
// Allow suggestions to show (let Suggestion handle the defaults)
|
|
400
|
+
// Don't filter by uiEvent since it's not reliably set
|
|
401
|
+
items: ({ query }) => {
|
|
402
|
+
const filtered = filterItems(this.options.items, query, this.options.filterKeys);
|
|
403
|
+
return filtered;
|
|
404
|
+
},
|
|
405
|
+
render: () => {
|
|
406
|
+
let renderer = null;
|
|
407
|
+
let popup = null;
|
|
408
|
+
let currentProps = null;
|
|
409
|
+
return {
|
|
410
|
+
onStart: (props) => {
|
|
411
|
+
console.log("\u2705 Slash menu started!", { query: props.query });
|
|
412
|
+
currentProps = props;
|
|
413
|
+
const handleSelect = (item) => {
|
|
414
|
+
if (!currentProps) return;
|
|
415
|
+
if (item.command) {
|
|
416
|
+
try {
|
|
417
|
+
const context = {
|
|
418
|
+
editor: currentProps.editor,
|
|
419
|
+
range: currentProps.range,
|
|
420
|
+
query: currentProps.query
|
|
421
|
+
};
|
|
422
|
+
item.command(context);
|
|
423
|
+
} catch (error) {
|
|
424
|
+
console.error("\u274C Slash command failed:", item.key, error);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
(0, import_extension_suggestion.exitSuggestion)(currentProps.editor.view, pluginKey);
|
|
428
|
+
};
|
|
429
|
+
renderer = new SlashMenuRenderer(props.items, handleSelect, {
|
|
430
|
+
maxHeight: this.options.maxHeight,
|
|
431
|
+
className: this.options.className,
|
|
432
|
+
placeholder: this.options.placeholder,
|
|
433
|
+
searchPlaceholder: this.options.searchPlaceholder
|
|
434
|
+
});
|
|
435
|
+
renderer.render();
|
|
436
|
+
this.storage.renderer = renderer;
|
|
437
|
+
popup = document.createElement("div");
|
|
438
|
+
popup.style.position = "absolute";
|
|
439
|
+
popup.style.zIndex = "1000";
|
|
440
|
+
popup.appendChild(renderer.getElement());
|
|
441
|
+
document.body.appendChild(popup);
|
|
442
|
+
updatePopupPosition(props, popup);
|
|
443
|
+
},
|
|
444
|
+
onUpdate: (props) => {
|
|
445
|
+
console.log("\u{1F504} Slash menu updated!", { query: props.query, items: props.items.length });
|
|
446
|
+
currentProps = props;
|
|
447
|
+
if (renderer) {
|
|
448
|
+
renderer.updateItems(props.items);
|
|
449
|
+
}
|
|
450
|
+
if (popup) {
|
|
451
|
+
updatePopupPosition(props, popup);
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
onKeyDown: ({ event }) => {
|
|
455
|
+
if (!renderer) return false;
|
|
456
|
+
if (event.key === "ArrowUp") {
|
|
457
|
+
renderer.selectPrevious();
|
|
458
|
+
return true;
|
|
459
|
+
}
|
|
460
|
+
if (event.key === "ArrowDown") {
|
|
461
|
+
renderer.selectNext();
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
if (event.key === "Enter") {
|
|
465
|
+
renderer.selectCurrent();
|
|
466
|
+
return true;
|
|
467
|
+
}
|
|
468
|
+
if (event.key === "Tab") {
|
|
469
|
+
event.preventDefault();
|
|
470
|
+
renderer.selectCurrent();
|
|
471
|
+
return true;
|
|
472
|
+
}
|
|
473
|
+
return false;
|
|
474
|
+
},
|
|
475
|
+
onExit: () => {
|
|
476
|
+
console.log("\u274C Slash menu exited!");
|
|
477
|
+
if (renderer) {
|
|
478
|
+
renderer.destroy();
|
|
479
|
+
renderer = null;
|
|
480
|
+
}
|
|
481
|
+
if (popup) {
|
|
482
|
+
popup.remove();
|
|
483
|
+
popup = null;
|
|
484
|
+
}
|
|
485
|
+
this.storage.renderer = null;
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
})
|
|
490
|
+
];
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
494
|
+
0 && (module.exports = {
|
|
495
|
+
SlashMenuBuilder,
|
|
496
|
+
SlashMenuRenderer,
|
|
497
|
+
filterItems,
|
|
498
|
+
flattenItems,
|
|
499
|
+
getSelectableItems
|
|
500
|
+
});
|
|
501
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/slash-menu-builder.ts","../src/filter.ts","../src/menu-renderer.ts"],"sourcesContent":["export { SlashMenuBuilder } from './slash-menu-builder.js'\nexport { filterItems, flattenItems, getSelectableItems } from './filter.js'\nexport { SlashMenuRenderer } from './menu-renderer.js'\n\nexport type {\n SlashMenuItem,\n SlashMenuBuilderOptions,\n SlashCommandContext,\n MenuState,\n} from './types.js'\n","import { Extension } from '@blockslides/core'\nimport type { Editor } from '@blockslides/core'\nimport Suggestion, { exitSuggestion } from '@blockslides/extension-suggestion'\nimport type { SuggestionProps } from '@blockslides/extension-suggestion'\nimport { PluginKey } from '@blockslides/pm/state'\n\nimport { filterItems } from './filter.js'\nimport { SlashMenuRenderer } from './menu-renderer.js'\nimport type { SlashMenuBuilderOptions, SlashMenuItem, SlashCommandContext } from './types.js'\n\nexport interface SlashMenuBuilderStorage {\n renderer: SlashMenuRenderer | null\n}\n\n/**\n * SlashMenuBuilder Extension\n * \n * A framework for building custom slash command menus.\n * Built on top of the Suggestion utility.\n * \n * @example\n * ```ts\n * SlashMenuBuilder({\n * items: [\n * {\n * key: 'heading1',\n * label: 'Heading 1',\n * description: 'Used for a top-level heading',\n * icon: 'H1',\n * command: ({ commands }) => commands.replaceWithHeading(1)\n * }\n * ]\n * })\n * ```\n */\nexport const SlashMenuBuilder = Extension.create<SlashMenuBuilderOptions, SlashMenuBuilderStorage>({\n name: 'slashMenuBuilder',\n\n addOptions() {\n return {\n items: [],\n maxHeight: 400,\n filterKeys: ['label', 'description', 'keywords'],\n placeholder: 'No commands found',\n searchPlaceholder: 'Search commands...',\n className: '',\n }\n },\n\n addStorage() {\n return {\n renderer: null,\n }\n },\n\n addProseMirrorPlugins() {\n const editor = this.editor as Editor\n const pluginKey = new PluginKey('slashMenuBuilder')\n\n // Helper function to update popup position\n const updatePopupPosition = (props: SuggestionProps<SlashMenuItem>, popup: HTMLElement) => {\n if (!props.clientRect) return\n\n const rect = props.clientRect()\n if (!rect) return\n\n const menuHeight = popup.offsetHeight\n const spaceBelow = window.innerHeight - rect.bottom\n const spaceAbove = rect.top\n\n // Position below if there's space, otherwise above\n if (spaceBelow >= menuHeight || spaceBelow >= spaceAbove) {\n popup.style.top = `${rect.bottom + window.scrollY + 8}px`\n popup.style.bottom = 'auto'\n } else {\n popup.style.bottom = `${window.innerHeight - rect.top - window.scrollY + 8}px`\n popup.style.top = 'auto'\n }\n\n popup.style.left = `${rect.left + window.scrollX}px`\n }\n\n return [\n Suggestion<SlashMenuItem>({\n editor,\n char: '/',\n pluginKey,\n\n // Allow suggestions to show (let Suggestion handle the defaults)\n // Don't filter by uiEvent since it's not reliably set\n\n items: ({ query }) => {\n // Filter items based on the query\n const filtered = filterItems(this.options.items, query, this.options.filterKeys)\n return filtered\n },\n\n render: () => {\n let renderer: SlashMenuRenderer | null = null\n let popup: HTMLElement | null = null\n let currentProps: SuggestionProps<SlashMenuItem> | null = null\n\n return {\n onStart: (props: SuggestionProps<SlashMenuItem>) => {\n console.log('✅ Slash menu started!', { query: props.query })\n\n // Store the current props\n currentProps = props\n\n // Create the command handler\n const handleSelect = (item: SlashMenuItem) => {\n\n // Use the latest props, not the captured ones\n if (!currentProps) return\n\n // Execute the command if provided\n if (item.command) {\n try {\n const context: SlashCommandContext = {\n editor: currentProps.editor,\n range: currentProps.range,\n query: currentProps.query,\n }\n\n item.command(context)\n } catch (error) {\n console.error('❌ Slash command failed:', item.key, error)\n }\n }\n\n // Always close the menu, even if command failed\n exitSuggestion(currentProps.editor.view, pluginKey)\n }\n\n // Create renderer\n renderer = new SlashMenuRenderer(props.items, handleSelect, {\n maxHeight: this.options.maxHeight,\n className: this.options.className,\n placeholder: this.options.placeholder,\n searchPlaceholder: this.options.searchPlaceholder,\n })\n\n renderer.render()\n\n // Store in extension storage\n this.storage.renderer = renderer\n\n // Create popup container\n popup = document.createElement('div')\n popup.style.position = 'absolute'\n popup.style.zIndex = '1000'\n popup.appendChild(renderer.getElement())\n document.body.appendChild(popup)\n\n // Position the menu\n updatePopupPosition(props, popup)\n },\n\n onUpdate: (props: SuggestionProps<SlashMenuItem>) => {\n console.log('🔄 Slash menu updated!', { query: props.query, items: props.items.length })\n\n // Update to the latest props\n currentProps = props\n\n if (renderer) {\n renderer.updateItems(props.items)\n }\n\n // Update position\n if (popup) {\n updatePopupPosition(props, popup)\n }\n },\n\n onKeyDown: ({ event }) => {\n if (!renderer) return false\n\n // Arrow Up\n if (event.key === 'ArrowUp') {\n renderer.selectPrevious()\n return true\n }\n\n // Arrow Down\n if (event.key === 'ArrowDown') {\n renderer.selectNext()\n return true\n }\n\n // Enter\n if (event.key === 'Enter') {\n renderer.selectCurrent()\n return true\n }\n\n // Tab (same as Enter)\n if (event.key === 'Tab') {\n event.preventDefault()\n renderer.selectCurrent()\n return true\n }\n\n return false\n },\n\n onExit: () => {\n console.log('❌ Slash menu exited!')\n\n if (renderer) {\n renderer.destroy()\n renderer = null\n }\n\n if (popup) {\n popup.remove()\n popup = null\n }\n\n this.storage.renderer = null\n },\n }\n },\n }),\n ]\n },\n})\n","import type { SlashMenuItem } from './types.js'\n\n/**\n * Filters menu items based on a search query\n * Searches through label, description, and keywords\n */\nexport function filterItems(\n items: SlashMenuItem[],\n query: string,\n filterKeys: Array<'label' | 'description' | 'keywords'> = ['label', 'description', 'keywords'],\n): SlashMenuItem[] {\n // If no query, return all items\n if (!query || query.trim() === '') {\n return items\n }\n\n const normalizedQuery = query.toLowerCase().trim()\n\n // Recursively filter items and their nested items\n return items\n .map(item => {\n // For group items, filter their children\n if (item.group && item.items) {\n const filteredChildren = filterItems(item.items, query, filterKeys)\n // Keep the group if any children match\n if (filteredChildren.length > 0) {\n return { ...item, items: filteredChildren }\n }\n return null\n }\n\n // Check if this item matches the query\n const matches = filterKeys.some(key => {\n if (key === 'label' && item.label) {\n return item.label.toLowerCase().includes(normalizedQuery)\n }\n if (key === 'description' && item.description) {\n return item.description.toLowerCase().includes(normalizedQuery)\n }\n if (key === 'keywords' && item.keywords) {\n return item.keywords.some(keyword => keyword.toLowerCase().includes(normalizedQuery))\n }\n return false\n })\n\n return matches ? item : null\n })\n .filter((item): item is SlashMenuItem => item !== null)\n}\n\n/**\n * Flattens nested menu items into a single array (for keyboard navigation)\n * Expands groups but keeps track of hierarchy\n */\nexport function flattenItems(items: SlashMenuItem[]): SlashMenuItem[] {\n const flattened: SlashMenuItem[] = []\n\n for (const item of items) {\n flattened.push(item)\n if (item.group && item.items) {\n flattened.push(...flattenItems(item.items))\n }\n }\n\n return flattened\n}\n\n/**\n * Gets all selectable items (non-group items) from the menu\n */\nexport function getSelectableItems(items: SlashMenuItem[]): SlashMenuItem[] {\n return flattenItems(items).filter(item => !item.group)\n}\n","import type { SlashMenuItem } from './types.js'\n\n/**\n * Renders the slash menu UI\n * Creates a Notion-style command menu with keyboard navigation\n */\nexport class SlashMenuRenderer {\n private element: HTMLElement\n private selectedIndex: number = 0\n private selectableItems: SlashMenuItem[] = []\n private searchValue: string = ''\n private searchInput: HTMLInputElement | null = null\n\n constructor(\n private items: SlashMenuItem[],\n private onSelect: (item: SlashMenuItem) => void,\n private options: {\n maxHeight?: number\n className?: string\n placeholder?: string\n searchPlaceholder?: string\n } = {},\n ) {\n this.element = this.createElement()\n this.selectableItems = this.getSelectableItems(items)\n }\n\n private createElement(): HTMLElement {\n const menu = document.createElement('div')\n menu.className = `slash-menu ${this.options.className || ''}`.trim()\n \n if (this.options.maxHeight) {\n menu.style.maxHeight = `${this.options.maxHeight}px`\n }\n\n return menu\n }\n\n private getSelectableItems(items: SlashMenuItem[]): SlashMenuItem[] {\n const selectable: SlashMenuItem[] = []\n \n for (const item of items) {\n if (!item.group) {\n selectable.push(item)\n }\n if (item.group && item.items) {\n selectable.push(...this.getSelectableItems(item.items))\n }\n }\n \n return selectable\n }\n\n /**\n * Renders the search bar\n */\n private renderSearchBar(): HTMLElement {\n const searchContainer = document.createElement('div')\n searchContainer.className = 'slash-menu-search'\n\n this.searchInput = document.createElement('input')\n this.searchInput.type = 'text'\n this.searchInput.className = 'slash-menu-search-input'\n this.searchInput.placeholder = this.options.searchPlaceholder || 'Search commands...'\n this.searchInput.value = this.searchValue\n\n this.searchInput.addEventListener('input', (e) => {\n this.searchValue = (e.target as HTMLInputElement).value.toLowerCase()\n this.filterAndRenderItems()\n })\n\n // Prevent the input from closing the menu or interfering with editor\n this.searchInput.addEventListener('mousedown', (e) => {\n e.stopPropagation()\n })\n\n this.searchInput.addEventListener('keydown', (e) => {\n if (e.key === 'ArrowDown') {\n e.preventDefault()\n this.selectNext()\n } else if (e.key === 'ArrowUp') {\n e.preventDefault()\n this.selectPrevious()\n } else if (e.key === 'Enter') {\n e.preventDefault()\n this.selectCurrent()\n }\n })\n\n searchContainer.appendChild(this.searchInput)\n return searchContainer\n }\n\n /**\n * Filters items based on search value\n */\n private getFilteredItems(): SlashMenuItem[] {\n if (!this.searchValue) {\n return this.items\n }\n\n const filterRecursive = (items: SlashMenuItem[]): SlashMenuItem[] => {\n return items.filter(item => {\n if (item.group && item.items) {\n const filteredChildren = filterRecursive(item.items)\n return filteredChildren.length > 0\n }\n \n const searchLower = this.searchValue.toLowerCase()\n return (\n item.label.toLowerCase().includes(searchLower) ||\n item.description?.toLowerCase().includes(searchLower) ||\n item.keywords?.some(k => k.toLowerCase().includes(searchLower))\n )\n }).map(item => {\n if (item.group && item.items) {\n return { ...item, items: filterRecursive(item.items) }\n }\n return item\n })\n }\n\n return filterRecursive(this.items)\n }\n\n /**\n * Filters and re-renders items based on search\n */\n private filterAndRenderItems() {\n const filteredItems = this.getFilteredItems()\n this.selectableItems = this.getSelectableItems(filteredItems)\n this.selectedIndex = 0\n\n // Clear and re-render items container\n const itemsContainer = this.element.querySelector('.slash-menu-items')\n if (itemsContainer) {\n itemsContainer.innerHTML = ''\n \n if (this.selectableItems.length === 0) {\n const empty = document.createElement('div')\n empty.className = 'slash-menu-empty'\n empty.textContent = this.options.placeholder || 'No commands found'\n itemsContainer.appendChild(empty)\n } else {\n this.renderItems(filteredItems, itemsContainer as HTMLElement)\n }\n }\n }\n\n /**\n * Renders the menu items into the DOM\n */\n render() {\n this.element.innerHTML = ''\n\n // Add search bar\n const searchBar = this.renderSearchBar()\n this.element.appendChild(searchBar)\n\n // Add items container\n const listContainer = document.createElement('div')\n listContainer.className = 'slash-menu-items'\n\n if (this.selectableItems.length === 0) {\n const empty = document.createElement('div')\n empty.className = 'slash-menu-empty'\n empty.textContent = this.options.placeholder || 'No commands found'\n listContainer.appendChild(empty)\n } else {\n this.renderItems(this.items, listContainer)\n }\n\n this.element.appendChild(listContainer)\n\n // Focus search input after render\n setTimeout(() => {\n this.searchInput?.focus()\n }, 0)\n }\n\n private renderItems(items: SlashMenuItem[], container: HTMLElement, isNested = false) {\n for (const item of items) {\n if (item.group && item.items) {\n this.renderGroup(item, container)\n } else {\n this.renderItem(item, container, isNested)\n }\n }\n }\n\n private renderGroup(group: SlashMenuItem, container: HTMLElement) {\n const groupElement = document.createElement('div')\n groupElement.className = 'slash-menu-group'\n\n // Group header\n const header = document.createElement('div')\n header.className = 'slash-menu-group-header'\n header.textContent = group.label\n groupElement.appendChild(header)\n\n // Group items\n const itemsContainer = document.createElement('div')\n itemsContainer.className = 'slash-menu-group-items'\n if (group.items) {\n this.renderItems(group.items, itemsContainer, true)\n }\n groupElement.appendChild(itemsContainer)\n\n container.appendChild(groupElement)\n }\n\n private renderItem(item: SlashMenuItem, container: HTMLElement, isNested: boolean) {\n const itemElement = document.createElement('div')\n const globalIndex = this.selectableItems.indexOf(item)\n const isSelected = globalIndex === this.selectedIndex\n\n itemElement.className = `slash-menu-item ${isSelected ? 'selected' : ''} ${isNested ? 'nested' : ''}`.trim()\n itemElement.dataset.key = item.key\n\n // Icon\n if (item.icon) {\n const icon = document.createElement('div')\n icon.className = 'slash-menu-item-icon'\n \n // If icon is a string, treat it as text\n if (typeof item.icon === 'string') {\n icon.textContent = item.icon\n }\n \n itemElement.appendChild(icon)\n }\n\n // Content (label + description)\n const content = document.createElement('div')\n content.className = 'slash-menu-item-content'\n\n const label = document.createElement('div')\n label.className = 'slash-menu-item-label'\n label.textContent = item.label\n content.appendChild(label)\n\n if (item.description) {\n const description = document.createElement('div')\n description.className = 'slash-menu-item-description'\n description.textContent = item.description\n content.appendChild(description)\n }\n\n itemElement.appendChild(content)\n\n // Use mousedown instead of click for immediate feedback and to prevent\n // the editor from handling the event (moving cursor, selecting text, etc.)\n itemElement.addEventListener('mousedown', (e) => {\n e.preventDefault() // Prevent default mousedown behavior (text selection, cursor movement)\n e.stopPropagation() // Stop event from bubbling to ProseMirror\n this.onSelect(item)\n })\n\n // Hover handler - update selection without re-rendering\n itemElement.addEventListener('mouseenter', () => {\n this.selectedIndex = globalIndex\n this.updateSelection()\n })\n\n container.appendChild(itemElement)\n }\n\n /**\n * Updates the menu with new filtered items\n */\n updateItems(items: SlashMenuItem[]) {\n this.items = items\n this.selectableItems = this.getSelectableItems(items)\n this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.selectableItems.length - 1))\n this.render()\n }\n\n /**\n * Updates the selected class on items without re-rendering\n */\n private updateSelection() {\n const allItems = this.element.querySelectorAll('.slash-menu-item')\n allItems.forEach((el) => {\n const itemKey = (el as HTMLElement).dataset.key\n const itemIndex = this.selectableItems.findIndex(item => item.key === itemKey)\n if (itemIndex === this.selectedIndex) {\n el.classList.add('selected')\n } else {\n el.classList.remove('selected')\n }\n })\n }\n\n /**\n * Moves selection up\n */\n selectPrevious() {\n this.selectedIndex = Math.max(0, this.selectedIndex - 1)\n this.updateSelection()\n this.scrollToSelected()\n }\n\n /**\n * Moves selection down\n */\n selectNext() {\n this.selectedIndex = Math.min(this.selectableItems.length - 1, this.selectedIndex + 1)\n this.updateSelection()\n this.scrollToSelected()\n }\n\n /**\n * Executes the currently selected item\n */\n selectCurrent() {\n const selectedItem = this.selectableItems[this.selectedIndex]\n if (selectedItem) {\n this.onSelect(selectedItem)\n }\n }\n\n /**\n * Scrolls the selected item into view\n */\n private scrollToSelected() {\n const selectedElement = this.element.querySelector('.slash-menu-item.selected')\n if (selectedElement) {\n selectedElement.scrollIntoView({ block: 'nearest', behavior: 'smooth' })\n }\n }\n\n /**\n * Gets the DOM element\n */\n getElement(): HTMLElement {\n return this.element\n }\n\n /**\n * Cleans up the renderer\n */\n destroy() {\n this.element.remove()\n }\n}\n"],"mappings":"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","names":["Suggestion"]}
|