straplight 1.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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -0
  3. package/assets/icon.png +0 -0
  4. package/assets/screenshot.png +0 -0
  5. package/dist/_chunks/Settings-BLDXIWWB.js +166 -0
  6. package/dist/_chunks/Settings-CncfOILf.mjs +166 -0
  7. package/dist/_chunks/StraplightOverlay-DLEpUqQM.mjs +506 -0
  8. package/dist/_chunks/StraplightOverlay-dGd1yRZj.js +506 -0
  9. package/dist/_chunks/en-B4KWt_jN.js +4 -0
  10. package/dist/_chunks/en-Byx4XI2L.mjs +4 -0
  11. package/dist/_chunks/index-B2B6CFFk.mjs +82 -0
  12. package/dist/_chunks/index-CuzgWWKQ.js +103 -0
  13. package/dist/admin/index.js +3 -0
  14. package/dist/admin/index.mjs +4 -0
  15. package/dist/admin/src/components/StraplightOverlay.d.ts +1 -0
  16. package/dist/admin/src/components/StraplightPortal.d.ts +6 -0
  17. package/dist/admin/src/hooks/useStraplight.d.ts +23 -0
  18. package/dist/admin/src/hooks/useStraplightSettings.d.ts +6 -0
  19. package/dist/admin/src/index.d.ts +11 -0
  20. package/dist/admin/src/pages/Settings.d.ts +2 -0
  21. package/dist/admin/src/pluginId.d.ts +1 -0
  22. package/dist/admin/src/utils/getTranslation.d.ts +2 -0
  23. package/dist/server/index.js +263 -0
  24. package/dist/server/index.mjs +264 -0
  25. package/dist/server/src/bootstrap.d.ts +5 -0
  26. package/dist/server/src/config/index.d.ts +5 -0
  27. package/dist/server/src/content-types/index.d.ts +2 -0
  28. package/dist/server/src/controllers/controller.d.ts +7 -0
  29. package/dist/server/src/controllers/index.d.ts +14 -0
  30. package/dist/server/src/controllers/settings.d.ts +8 -0
  31. package/dist/server/src/destroy.d.ts +5 -0
  32. package/dist/server/src/index.d.ts +68 -0
  33. package/dist/server/src/middlewares/index.d.ts +2 -0
  34. package/dist/server/src/policies/index.d.ts +2 -0
  35. package/dist/server/src/register.d.ts +5 -0
  36. package/dist/server/src/routes/admin/index.d.ts +12 -0
  37. package/dist/server/src/routes/content-api/index.d.ts +5 -0
  38. package/dist/server/src/routes/index.d.ts +18 -0
  39. package/dist/server/src/services/index.d.ts +15 -0
  40. package/dist/server/src/services/service.d.ts +7 -0
  41. package/dist/server/src/services/settings.d.ts +28 -0
  42. package/dist/server/src/utils/content-type-helpers.d.ts +4 -0
  43. package/package.json +85 -0
@@ -0,0 +1,506 @@
1
+ import { jsxs, Fragment, jsx } from "react/jsx-runtime";
2
+ import { useState, useEffect, useRef, useCallback } from "react";
3
+ import { getFetchClient } from "@strapi/strapi/admin";
4
+ import { P as PLUGIN_ID } from "./index-B2B6CFFk.mjs";
5
+ const DEFAULTS = {
6
+ debounceMs: 200,
7
+ minQueryLength: 1
8
+ };
9
+ function useStraplightSettings() {
10
+ const [settings, setSettings] = useState(DEFAULTS);
11
+ useEffect(() => {
12
+ const { get } = getFetchClient();
13
+ get(`/${PLUGIN_ID}/settings`).then(({ data }) => {
14
+ setSettings({
15
+ debounceMs: data.settings?.debounceMs ?? DEFAULTS.debounceMs,
16
+ minQueryLength: data.settings?.minQueryLength ?? DEFAULTS.minQueryLength
17
+ });
18
+ }).catch(() => {
19
+ });
20
+ }, []);
21
+ return settings;
22
+ }
23
+ function useStraplight() {
24
+ const settings = useStraplightSettings();
25
+ const [isOpen, setIsOpen] = useState(false);
26
+ const [query, setQuery] = useState("");
27
+ const [results, setResults] = useState([]);
28
+ const [loading, setLoading] = useState(false);
29
+ const [selectedIndex, setSelectedIndex] = useState(0);
30
+ const debounceRef = useRef(null);
31
+ const requestIdRef = useRef(0);
32
+ useEffect(() => {
33
+ const handler = (e) => {
34
+ if ((e.metaKey || e.ctrlKey) && e.key === "k") {
35
+ e.preventDefault();
36
+ setIsOpen((prev) => !prev);
37
+ }
38
+ };
39
+ document.addEventListener("keydown", handler);
40
+ return () => document.removeEventListener("keydown", handler);
41
+ }, []);
42
+ const close = useCallback(() => {
43
+ setIsOpen(false);
44
+ setQuery("");
45
+ setResults([]);
46
+ setSelectedIndex(0);
47
+ setLoading(false);
48
+ requestIdRef.current++;
49
+ }, []);
50
+ useEffect(() => {
51
+ if (!isOpen) return;
52
+ if (debounceRef.current) {
53
+ clearTimeout(debounceRef.current);
54
+ }
55
+ const trimmed = query.trim();
56
+ if (trimmed.length < settings.minQueryLength) {
57
+ setResults([]);
58
+ setSelectedIndex(0);
59
+ setLoading(false);
60
+ return;
61
+ }
62
+ debounceRef.current = setTimeout(async () => {
63
+ const id = ++requestIdRef.current;
64
+ setLoading(true);
65
+ try {
66
+ const { get } = getFetchClient();
67
+ const { data } = await get(`/${PLUGIN_ID}/search`, {
68
+ params: { q: trimmed }
69
+ });
70
+ if (id !== requestIdRef.current) return;
71
+ setResults(data.results || []);
72
+ setSelectedIndex(0);
73
+ } catch {
74
+ if (id !== requestIdRef.current) return;
75
+ setResults([]);
76
+ } finally {
77
+ if (id === requestIdRef.current) {
78
+ setLoading(false);
79
+ }
80
+ }
81
+ }, settings.debounceMs);
82
+ return () => {
83
+ if (debounceRef.current) clearTimeout(debounceRef.current);
84
+ };
85
+ }, [query, isOpen, settings.debounceMs, settings.minQueryLength]);
86
+ const navigateToResult = useCallback(
87
+ (result) => {
88
+ const path = `/content-manager/collection-types/${result.uid}/${result.documentId}`;
89
+ const nav = window.__straplight?.navigate;
90
+ if (nav) {
91
+ nav(path);
92
+ } else {
93
+ window.location.href = `/admin${path}`;
94
+ }
95
+ close();
96
+ },
97
+ [close]
98
+ );
99
+ const onKeyDown = useCallback(
100
+ (e) => {
101
+ if (e.key === "Escape") {
102
+ e.preventDefault();
103
+ close();
104
+ } else if (e.key === "ArrowDown") {
105
+ e.preventDefault();
106
+ setSelectedIndex((i) => i < results.length - 1 ? i + 1 : 0);
107
+ } else if (e.key === "ArrowUp") {
108
+ e.preventDefault();
109
+ setSelectedIndex((i) => i > 0 ? i - 1 : results.length - 1);
110
+ } else if (e.key === "Enter" && results.length > 0) {
111
+ e.preventDefault();
112
+ navigateToResult(results[selectedIndex]);
113
+ }
114
+ },
115
+ [results, selectedIndex, navigateToResult, close]
116
+ );
117
+ return {
118
+ isOpen,
119
+ query,
120
+ setQuery,
121
+ results,
122
+ loading,
123
+ selectedIndex,
124
+ close,
125
+ onKeyDown,
126
+ navigateToResult
127
+ };
128
+ }
129
+ const OVERLAY_STYLES = `
130
+ @keyframes straplight-fade-in {
131
+ from { opacity: 0; }
132
+ to { opacity: 1; }
133
+ }
134
+ @keyframes straplight-slide-in {
135
+ from { opacity: 0; transform: translateX(-50%) translateY(16px); }
136
+ to { opacity: 1; transform: translateX(-50%); }
137
+ }
138
+ @keyframes straplight-spin {
139
+ to { transform: rotate(360deg); }
140
+ }
141
+ `;
142
+ function resolveIsDark() {
143
+ const stored = localStorage.getItem("STRAPI_THEME") || "system";
144
+ if (stored === "dark") return true;
145
+ if (stored === "light") return false;
146
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
147
+ }
148
+ function useIsDarkMode() {
149
+ return resolveIsDark();
150
+ }
151
+ const themes = {
152
+ light: {
153
+ bg: "#ffffff",
154
+ bgHover: "#f6f6f9",
155
+ bgSelected: "#f0f0ff",
156
+ text: "#32324d",
157
+ textMuted: "#8e8ea9",
158
+ border: "#eaeaef",
159
+ inputText: "#32324d",
160
+ badge: "#f6f6f9",
161
+ kbd: "#f6f6f9",
162
+ kbdBorder: "#dcdce4",
163
+ kbdText: "#666687",
164
+ backdrop: "rgba(0, 0, 0, 0.4)",
165
+ shadow: "0 16px 70px rgba(0, 0, 0, 0.2)",
166
+ spinnerTrack: "#eaeaef",
167
+ spinnerAccent: "#4945ff"
168
+ },
169
+ dark: {
170
+ bg: "#212134",
171
+ bgHover: "#2a2a3e",
172
+ bgSelected: "#2e2e48",
173
+ text: "#eaeaef",
174
+ textMuted: "#a5a5ba",
175
+ border: "#3d3d57",
176
+ inputText: "#eaeaef",
177
+ badge: "#2e2e48",
178
+ kbd: "#2e2e48",
179
+ kbdBorder: "#3d3d57",
180
+ kbdText: "#a5a5ba",
181
+ backdrop: "rgba(0, 0, 0, 0.6)",
182
+ shadow: "0 16px 70px rgba(0, 0, 0, 0.5)",
183
+ spinnerTrack: "#3d3d57",
184
+ spinnerAccent: "#7b79ff"
185
+ }
186
+ };
187
+ function StraplightOverlay() {
188
+ const {
189
+ isOpen,
190
+ query,
191
+ setQuery,
192
+ results,
193
+ loading,
194
+ selectedIndex,
195
+ close,
196
+ onKeyDown,
197
+ navigateToResult
198
+ } = useStraplight();
199
+ const dark = useIsDarkMode();
200
+ const t = dark ? themes.dark : themes.light;
201
+ const inputRef = useRef(null);
202
+ const listRef = useRef(null);
203
+ useEffect(() => {
204
+ if (isOpen && inputRef.current) {
205
+ inputRef.current.focus();
206
+ }
207
+ }, [isOpen]);
208
+ useEffect(() => {
209
+ if (!listRef.current) return;
210
+ const selected = listRef.current.children[selectedIndex];
211
+ if (selected) {
212
+ selected.scrollIntoView({ block: "nearest" });
213
+ }
214
+ }, [selectedIndex]);
215
+ if (!isOpen) return null;
216
+ const isMac = typeof navigator !== "undefined" && /mac/i.test(navigator.platform || navigator.userAgent);
217
+ const modKey = isMac ? "⌘" : "Ctrl";
218
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
219
+ /* @__PURE__ */ jsx("style", { children: OVERLAY_STYLES }),
220
+ /* @__PURE__ */ jsx(
221
+ "div",
222
+ {
223
+ onClick: close,
224
+ style: {
225
+ position: "fixed",
226
+ inset: 0,
227
+ backgroundColor: t.backdrop,
228
+ backdropFilter: "blur(4px)",
229
+ zIndex: 99999,
230
+ animation: "straplight-fade-in 0.15s ease-out"
231
+ }
232
+ }
233
+ ),
234
+ /* @__PURE__ */ jsxs(
235
+ "div",
236
+ {
237
+ onKeyDown,
238
+ style: {
239
+ position: "fixed",
240
+ top: "calc(50% - 50px)",
241
+ left: "50%",
242
+ width: "580px",
243
+ maxWidth: "calc(100vw - 32px)",
244
+ maxHeight: "440px",
245
+ backgroundColor: t.bg,
246
+ borderRadius: "12px",
247
+ boxShadow: t.shadow,
248
+ zIndex: 1e5,
249
+ display: "flex",
250
+ flexDirection: "column",
251
+ overflow: "hidden",
252
+ animation: "straplight-slide-in 0.2s ease-out 0.05s both"
253
+ },
254
+ children: [
255
+ /* @__PURE__ */ jsxs(
256
+ "div",
257
+ {
258
+ style: {
259
+ display: "flex",
260
+ alignItems: "center",
261
+ padding: "14px 16px",
262
+ borderBottom: `1px solid ${t.border}`,
263
+ gap: "10px"
264
+ },
265
+ children: [
266
+ /* @__PURE__ */ jsxs(
267
+ "svg",
268
+ {
269
+ width: "20",
270
+ height: "20",
271
+ viewBox: "0 0 24 24",
272
+ fill: "none",
273
+ stroke: t.textMuted,
274
+ strokeWidth: "2",
275
+ strokeLinecap: "round",
276
+ strokeLinejoin: "round",
277
+ children: [
278
+ /* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "8" }),
279
+ /* @__PURE__ */ jsx("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" })
280
+ ]
281
+ }
282
+ ),
283
+ /* @__PURE__ */ jsx(
284
+ "input",
285
+ {
286
+ ref: inputRef,
287
+ type: "text",
288
+ value: query,
289
+ onChange: (e) => setQuery(e.target.value),
290
+ placeholder: "Search content...",
291
+ style: {
292
+ flex: 1,
293
+ border: "none",
294
+ outline: "none",
295
+ fontSize: "16px",
296
+ fontFamily: "inherit",
297
+ color: t.inputText,
298
+ backgroundColor: "transparent"
299
+ }
300
+ }
301
+ ),
302
+ loading && /* @__PURE__ */ jsx(
303
+ "div",
304
+ {
305
+ style: {
306
+ width: "18px",
307
+ height: "18px",
308
+ border: `2px solid ${t.spinnerTrack}`,
309
+ borderTopColor: t.spinnerAccent,
310
+ borderRadius: "50%",
311
+ animation: "straplight-spin 0.6s linear infinite"
312
+ }
313
+ }
314
+ )
315
+ ]
316
+ }
317
+ ),
318
+ /* @__PURE__ */ jsxs(
319
+ "div",
320
+ {
321
+ ref: listRef,
322
+ style: {
323
+ flex: 1,
324
+ overflowY: "auto",
325
+ padding: results.length > 0 ? "8px" : "0"
326
+ },
327
+ children: [
328
+ query.trim() && !loading && results.length === 0 && /* @__PURE__ */ jsx(
329
+ "div",
330
+ {
331
+ style: {
332
+ padding: "32px 16px",
333
+ textAlign: "center",
334
+ color: t.textMuted,
335
+ fontSize: "14px"
336
+ },
337
+ children: "No results found"
338
+ }
339
+ ),
340
+ results.map((result, index) => /* @__PURE__ */ jsx(
341
+ ResultItem,
342
+ {
343
+ result,
344
+ isSelected: index === selectedIndex,
345
+ onClick: () => navigateToResult(result),
346
+ t
347
+ },
348
+ `${result.uid}-${result.documentId}`
349
+ ))
350
+ ]
351
+ }
352
+ ),
353
+ /* @__PURE__ */ jsxs(
354
+ "div",
355
+ {
356
+ style: {
357
+ display: "flex",
358
+ alignItems: "center",
359
+ gap: "16px",
360
+ padding: "10px 16px",
361
+ borderTop: `1px solid ${t.border}`,
362
+ fontSize: "12px",
363
+ color: t.textMuted
364
+ },
365
+ children: [
366
+ /* @__PURE__ */ jsxs("span", { children: [
367
+ /* @__PURE__ */ jsx(Kbd, { t, children: "↑↓" }),
368
+ " navigate"
369
+ ] }),
370
+ /* @__PURE__ */ jsxs("span", { children: [
371
+ /* @__PURE__ */ jsx(Kbd, { t, children: "⏎" }),
372
+ " open"
373
+ ] }),
374
+ /* @__PURE__ */ jsxs("span", { children: [
375
+ /* @__PURE__ */ jsx(Kbd, { t, children: "esc" }),
376
+ " close"
377
+ ] }),
378
+ /* @__PURE__ */ jsxs("span", { style: { marginLeft: "auto" }, children: [
379
+ /* @__PURE__ */ jsxs(Kbd, { t, children: [
380
+ modKey,
381
+ "+K"
382
+ ] }),
383
+ " toggle"
384
+ ] })
385
+ ]
386
+ }
387
+ )
388
+ ]
389
+ }
390
+ )
391
+ ] });
392
+ }
393
+ function ResultItem({
394
+ result,
395
+ isSelected,
396
+ onClick,
397
+ t
398
+ }) {
399
+ return /* @__PURE__ */ jsxs(
400
+ "div",
401
+ {
402
+ onClick,
403
+ style: {
404
+ display: "flex",
405
+ alignItems: "center",
406
+ justifyContent: "space-between",
407
+ padding: "10px 12px",
408
+ borderRadius: "8px",
409
+ cursor: "pointer",
410
+ backgroundColor: isSelected ? t.bgSelected : "transparent",
411
+ transition: "background-color 0.1s"
412
+ },
413
+ onMouseEnter: (e) => {
414
+ if (!isSelected)
415
+ e.currentTarget.style.backgroundColor = t.bgHover;
416
+ },
417
+ onMouseLeave: (e) => {
418
+ e.currentTarget.style.backgroundColor = isSelected ? t.bgSelected : "transparent";
419
+ },
420
+ children: [
421
+ /* @__PURE__ */ jsxs(
422
+ "div",
423
+ {
424
+ style: {
425
+ flex: 1,
426
+ overflow: "hidden",
427
+ marginRight: "12px",
428
+ display: "flex",
429
+ alignItems: "baseline",
430
+ gap: "8px"
431
+ },
432
+ children: [
433
+ /* @__PURE__ */ jsx(
434
+ "span",
435
+ {
436
+ style: {
437
+ fontSize: "14px",
438
+ color: t.text,
439
+ fontWeight: isSelected ? 600 : 400,
440
+ overflow: "hidden",
441
+ textOverflow: "ellipsis",
442
+ whiteSpace: "nowrap",
443
+ flexShrink: 1,
444
+ minWidth: 0
445
+ },
446
+ children: result.label
447
+ }
448
+ ),
449
+ result.fields && result.fields.length > 0 && /* @__PURE__ */ jsx(
450
+ "span",
451
+ {
452
+ style: {
453
+ fontSize: "12px",
454
+ color: t.textMuted,
455
+ overflow: "hidden",
456
+ textOverflow: "ellipsis",
457
+ whiteSpace: "nowrap",
458
+ flexShrink: 2,
459
+ minWidth: 0
460
+ },
461
+ children: result.fields.map((f) => f.value).join(" · ")
462
+ }
463
+ )
464
+ ]
465
+ }
466
+ ),
467
+ /* @__PURE__ */ jsx(
468
+ "span",
469
+ {
470
+ style: {
471
+ fontSize: "11px",
472
+ color: t.textMuted,
473
+ backgroundColor: t.badge,
474
+ padding: "2px 8px",
475
+ borderRadius: "4px",
476
+ whiteSpace: "nowrap",
477
+ flexShrink: 0
478
+ },
479
+ children: result.contentType
480
+ }
481
+ )
482
+ ]
483
+ }
484
+ );
485
+ }
486
+ function Kbd({ children, t }) {
487
+ return /* @__PURE__ */ jsx(
488
+ "kbd",
489
+ {
490
+ style: {
491
+ display: "inline-block",
492
+ padding: "1px 5px",
493
+ fontSize: "11px",
494
+ fontFamily: "inherit",
495
+ color: t.kbdText,
496
+ backgroundColor: t.kbd,
497
+ border: `1px solid ${t.kbdBorder}`,
498
+ borderRadius: "4px"
499
+ },
500
+ children
501
+ }
502
+ );
503
+ }
504
+ export {
505
+ StraplightOverlay
506
+ };