@vicacha-devs/strapi-plugin-notes 0.0.0-stage → 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 (66) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +20 -2
  3. package/dist/admin/NotesHomePage-CIWSOu3n.js +457 -0
  4. package/dist/admin/NotesHomePage-Da5Eu2o9.cjs +475 -0
  5. package/dist/admin/ar-NJ-XJhAU.cjs +64 -0
  6. package/dist/admin/ar-uSBvoP4-.js +64 -0
  7. package/dist/admin/de-DlFY4yCr.js +64 -0
  8. package/dist/admin/de-d-M42GQs.cjs +64 -0
  9. package/dist/admin/en-C8MGI5Fp.cjs +64 -0
  10. package/dist/admin/en-DQUtJt3K.js +64 -0
  11. package/dist/admin/es-BZY_kUPd.cjs +64 -0
  12. package/dist/admin/es-DcTVGahO.js +64 -0
  13. package/dist/admin/fr-D6Ism7Y3.cjs +64 -0
  14. package/dist/admin/fr-QW7vMjBs.js +64 -0
  15. package/dist/admin/index-BD0jLH1y.cjs +1458 -0
  16. package/dist/admin/index-ClQtejgN.js +1442 -0
  17. package/dist/admin/index.cjs +4 -0
  18. package/dist/admin/index.js +4 -0
  19. package/dist/admin/index.mjs +4 -0
  20. package/dist/admin/it-DyBVYzjD.cjs +64 -0
  21. package/dist/admin/it-F24NTVne.js +64 -0
  22. package/dist/admin/pt-B4OG4ZiY.js +64 -0
  23. package/dist/admin/pt-BOHJueLP.cjs +64 -0
  24. package/dist/admin/ru-DGiT4YZI.js +64 -0
  25. package/dist/admin/ru-DhigZM8k.cjs +64 -0
  26. package/dist/admin/src/components/DeleteNoteDialog.d.ts +10 -0
  27. package/dist/admin/src/components/Initializer.d.ts +5 -0
  28. package/dist/admin/src/components/NoteListItem.d.ts +10 -0
  29. package/dist/admin/src/components/NoteModal.d.ts +18 -0
  30. package/dist/admin/src/components/NotesHubFilterBar.d.ts +14 -0
  31. package/dist/admin/src/components/NotesSidePanel.d.ts +13 -0
  32. package/dist/admin/src/components/PluginIcon.d.ts +2 -0
  33. package/dist/admin/src/hooks/useNotes.d.ts +12 -0
  34. package/dist/admin/src/index.d.ts +21 -0
  35. package/dist/admin/src/pages/NotesHomePage.d.ts +3 -0
  36. package/dist/admin/src/pluginId.d.ts +4 -0
  37. package/dist/admin/src/utils/getTrad.d.ts +2 -0
  38. package/dist/admin/src/utils/index.d.ts +1 -0
  39. package/dist/admin/tr-CNFZeutu.cjs +64 -0
  40. package/dist/admin/tr-CwzORNP2.js +64 -0
  41. package/dist/admin/zh-BriU4KiK.cjs +64 -0
  42. package/dist/admin/zh-C1EjzGrj.js +64 -0
  43. package/dist/package.json +3 -0
  44. package/dist/server/index.cjs +1212 -0
  45. package/dist/server/index.js +1212 -0
  46. package/dist/server/index.mjs +1212 -0
  47. package/dist/server/src/bootstrap.d.ts +5 -0
  48. package/dist/server/src/config/index.d.ts +8 -0
  49. package/dist/server/src/content-types/index.d.ts +70 -0
  50. package/dist/server/src/content-types/note/schema.json.d.ts +68 -0
  51. package/dist/server/src/controllers/index.d.ts +2 -0
  52. package/dist/server/src/controllers/notesController.d.ts +27 -0
  53. package/dist/server/src/destroy.d.ts +5 -0
  54. package/dist/server/src/index.d.ts +2 -0
  55. package/dist/server/src/register.d.ts +5 -0
  56. package/dist/server/src/routes/admin.d.ts +17 -0
  57. package/dist/server/src/routes/content-api.d.ts +12 -0
  58. package/dist/server/src/routes/index.d.ts +30 -0
  59. package/dist/server/src/services/index.d.ts +2 -0
  60. package/dist/server/src/services/notesQueryHelpers.d.ts +11 -0
  61. package/dist/server/src/services/notesService.d.ts +25 -0
  62. package/dist/shared/index.cjs +525 -0
  63. package/dist/shared/index.d.ts +1 -0
  64. package/dist/shared/index.js +525 -0
  65. package/dist/shared/index.mjs +525 -0
  66. package/package.json +102 -3
@@ -0,0 +1,1442 @@
1
+ import * as React from "react";
2
+ import { useRef, useEffect, useState, useCallback } from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { Dialog, Button, Box, Flex, Badge, Typography, IconButton, Modal, Field, TextInput, SingleSelect, SingleSelectOption, Textarea, Checkbox, Grid, Divider, Loader } from "@strapi/design-system";
5
+ import { WarningCircle, Trash, Pin, Pencil, Check, Plus, File } from "@strapi/icons";
6
+ import { useIntl } from "react-intl";
7
+ import { useFetchClient, useNotification } from "@strapi/strapi/admin";
8
+ const __variableDynamicImportRuntimeHelper = (glob, path, segs) => {
9
+ const v = glob[path];
10
+ if (v) {
11
+ return typeof v === "function" ? v() : Promise.resolve(v);
12
+ }
13
+ return new Promise((_, reject) => {
14
+ (typeof queueMicrotask === "function" ? queueMicrotask : setTimeout)(
15
+ reject.bind(
16
+ null,
17
+ new Error(
18
+ "Unknown variable dynamic import: " + path + (path.split("/").length !== segs ? ". Note that variables only represent file names one level deep." : "")
19
+ )
20
+ )
21
+ );
22
+ });
23
+ };
24
+ function prefixPluginTranslations(trad, pluginId, options) {
25
+ if (!pluginId) {
26
+ throw new TypeError("pluginId can't be empty");
27
+ }
28
+ const result = {
29
+ ...options?.baseTranslations
30
+ };
31
+ for (const [key, value] of Object.entries(trad)) {
32
+ if (options?.keepUnprefixed) {
33
+ result[key] = value;
34
+ }
35
+ const fullKey = key.startsWith(`${pluginId}.`) ? key : `${pluginId}.${key}`;
36
+ result[fullKey] = value;
37
+ }
38
+ return result;
39
+ }
40
+ function createRegisterTrads(options) {
41
+ const { pluginId, defaultLocales = ["en", "es", "ar", "tr"], supportedLocales, keepUnprefixed = true, loadTranslation, translations, getBaseTranslations, onLoaded } = options;
42
+ return async function registerTrads({ locales = [] } = {}) {
43
+ const allLocales = Array.from(/* @__PURE__ */ new Set([...locales, ...defaultLocales]));
44
+ return Promise.all(allLocales.map(async (locale) => {
45
+ if (supportedLocales && !supportedLocales.includes(locale)) {
46
+ return { data: {}, locale };
47
+ }
48
+ try {
49
+ let rawData = translations?.[locale];
50
+ if (!rawData && loadTranslation) {
51
+ const loaded = await loadTranslation(locale);
52
+ rawData = loaded && "default" in loaded && loaded.default ? loaded.default : loaded;
53
+ }
54
+ if (!rawData) {
55
+ return { data: {}, locale };
56
+ }
57
+ const prefixed = prefixPluginTranslations(rawData, pluginId, {
58
+ keepUnprefixed,
59
+ baseTranslations: getBaseTranslations ? getBaseTranslations(locale) : void 0
60
+ });
61
+ onLoaded?.(locale, prefixed);
62
+ return { data: prefixed, locale };
63
+ } catch {
64
+ return { data: {}, locale };
65
+ }
66
+ }));
67
+ };
68
+ }
69
+ const PLUGIN_ID = "notes";
70
+ const NOTE_CATEGORIES = ["general", "todo", "blocker", "idea"];
71
+ const MAX_NOTE_TITLE_LENGTH = 255;
72
+ const MAX_NOTE_CONTENT_LENGTH = 2e4;
73
+ const MAX_ENTITY_SLUG_LENGTH = 160;
74
+ const MAX_ENTITY_ID_LENGTH = 128;
75
+ const MAX_LOCALE_LENGTH = 32;
76
+ const NOTE_CATEGORY_META = {
77
+ general: {
78
+ labelKey: "category.general",
79
+ defaultLabel: "General",
80
+ badgeBackground: "neutral150",
81
+ badgeTextColor: "neutral800"
82
+ },
83
+ todo: {
84
+ labelKey: "category.todo",
85
+ defaultLabel: "Todo",
86
+ badgeBackground: "primary100",
87
+ badgeTextColor: "primary700"
88
+ },
89
+ blocker: {
90
+ labelKey: "category.blocker",
91
+ defaultLabel: "Blocker",
92
+ badgeBackground: "danger100",
93
+ badgeTextColor: "danger700"
94
+ },
95
+ idea: {
96
+ labelKey: "category.idea",
97
+ defaultLabel: "Idea",
98
+ badgeBackground: "success100",
99
+ badgeTextColor: "success700"
100
+ }
101
+ };
102
+ const Initializer = ({ setPlugin }) => {
103
+ const ref = useRef(setPlugin);
104
+ useEffect(() => {
105
+ ref.current(PLUGIN_ID);
106
+ }, []);
107
+ return null;
108
+ };
109
+ const ENTITY_SLUG_PATTERN = /^[a-zA-Z0-9_:.-]{1,160}$/;
110
+ const SAFE_ID_PATTERN = /^[a-zA-Z0-9_-]{1,128}$/;
111
+ const LOCALE_PATTERN = /^[a-zA-Z]{2,3}(?:-[a-zA-Z0-9]{2,8}){0,4}$/;
112
+ const FORBIDDEN_IDENTIFIER_TOKENS = /* @__PURE__ */ new Set([
113
+ "__proto__",
114
+ "constructor",
115
+ "prototype"
116
+ ]);
117
+ function isForbiddenIdentifier(value) {
118
+ const lower = value.toLowerCase();
119
+ if (FORBIDDEN_IDENTIFIER_TOKENS.has(lower)) return true;
120
+ if (value === "." || value === ".." || value.includes("..") || value.startsWith(".") || value.endsWith(".")) {
121
+ return true;
122
+ }
123
+ return lower.split(/[:./]+/).some((segment) => FORBIDDEN_IDENTIFIER_TOKENS.has(segment));
124
+ }
125
+ function toPositiveSafeInt(input) {
126
+ if (typeof input !== "number" || !Number.isFinite(input)) return null;
127
+ const truncated = Math.trunc(input);
128
+ return Number.isSafeInteger(truncated) && truncated >= 1 ? truncated : null;
129
+ }
130
+ function isValidNoteCategory(value) {
131
+ return typeof value === "string" && NOTE_CATEGORIES.includes(value);
132
+ }
133
+ function normalizeNoteCategory(value) {
134
+ if (typeof value === "string") {
135
+ const normalized = value.trim().toLowerCase();
136
+ if (isValidNoteCategory(normalized)) return normalized;
137
+ }
138
+ return "general";
139
+ }
140
+ function sanitizePlainText(input, maxLength) {
141
+ if (typeof input !== "string") return "";
142
+ const cleaned = input.replace(/\0/g, "").replace(/\r\n/g, "\n").trim();
143
+ return cleaned.length > maxLength ? cleaned.slice(0, maxLength) : cleaned;
144
+ }
145
+ function sanitizeEntitySlug(input) {
146
+ if (typeof input !== "string" || input.includes("\0")) return "";
147
+ const cleaned = input.trim();
148
+ if (!cleaned || cleaned.length > MAX_ENTITY_SLUG_LENGTH || isForbiddenIdentifier(cleaned) || !ENTITY_SLUG_PATTERN.test(cleaned)) {
149
+ return "";
150
+ }
151
+ return cleaned;
152
+ }
153
+ function sanitizeEntityDocumentId(input) {
154
+ const numeric = toPositiveSafeInt(input);
155
+ if (numeric !== null) return String(numeric);
156
+ if (typeof input !== "string" || input.includes("\0")) return "";
157
+ const cleaned = input.trim();
158
+ if (!cleaned || cleaned.length > MAX_ENTITY_ID_LENGTH || isForbiddenIdentifier(cleaned) || !SAFE_ID_PATTERN.test(cleaned)) {
159
+ return "";
160
+ }
161
+ return cleaned;
162
+ }
163
+ function parseStringEntityId(input) {
164
+ if (input.includes("\0")) return null;
165
+ const trimmed = input.trim();
166
+ if (!trimmed || trimmed.length > MAX_ENTITY_ID_LENGTH || isForbiddenIdentifier(trimmed)) {
167
+ return null;
168
+ }
169
+ if (/^\d+$/.test(trimmed)) {
170
+ const parsed = Number.parseInt(trimmed, 10);
171
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
172
+ }
173
+ return SAFE_ID_PATTERN.test(trimmed) ? trimmed : null;
174
+ }
175
+ function sanitizeEntityId(input) {
176
+ const numeric = toPositiveSafeInt(input);
177
+ if (numeric !== null) {
178
+ return numeric;
179
+ }
180
+ if (typeof input === "string") {
181
+ return parseStringEntityId(input);
182
+ }
183
+ return null;
184
+ }
185
+ function isWildcardOrInvalidLocale(cleaned) {
186
+ if (!cleaned || cleaned === "all" || cleaned === "*") {
187
+ return true;
188
+ }
189
+ return cleaned.length > MAX_LOCALE_LENGTH || isForbiddenIdentifier(cleaned);
190
+ }
191
+ function sanitizeEntityLocale(input) {
192
+ if (typeof input !== "string" || input.includes("\0")) {
193
+ return null;
194
+ }
195
+ const cleaned = input.trim();
196
+ if (isWildcardOrInvalidLocale(cleaned)) {
197
+ return null;
198
+ }
199
+ return LOCALE_PATTERN.test(cleaned) ? cleaned : null;
200
+ }
201
+ function extractPrimitiveAuthorId(rawUser) {
202
+ const numeric = toPositiveSafeInt(rawUser);
203
+ if (numeric !== null) {
204
+ return numeric;
205
+ }
206
+ if (typeof rawUser === "string" && rawUser.trim()) {
207
+ return sanitizeEntityDocumentId(rawUser) || null;
208
+ }
209
+ return null;
210
+ }
211
+ function sanitizePrimitiveAuthor(rawUser, fallback) {
212
+ const primitiveId = extractPrimitiveAuthorId(rawUser);
213
+ if (!fallback && primitiveId === null) {
214
+ return null;
215
+ }
216
+ return {
217
+ id: primitiveId,
218
+ firstname: "",
219
+ lastname: "",
220
+ username: "",
221
+ displayName: fallback || `#${String(primitiveId)}`
222
+ };
223
+ }
224
+ function asPlainObject(value, fallback) {
225
+ return value && typeof value === "object" && !Array.isArray(value) ? value : fallback;
226
+ }
227
+ function unwrapAuthorRecord(rawUser) {
228
+ const nestedData = asPlainObject(rawUser.data, rawUser);
229
+ const attrs = asPlainObject(nestedData.attributes, nestedData);
230
+ const id = extractPrimitiveAuthorId(nestedData.id ?? rawUser.id);
231
+ return { attrs, id };
232
+ }
233
+ function sanitizeNoteAuthor(rawUser, fallbackDisplayName) {
234
+ const fallback = sanitizePlainText(fallbackDisplayName, 120);
235
+ if (!rawUser || typeof rawUser !== "object" || Array.isArray(rawUser)) {
236
+ return sanitizePrimitiveAuthor(rawUser, fallback);
237
+ }
238
+ const { attrs, id } = unwrapAuthorRecord(rawUser);
239
+ const firstname = sanitizePlainText(attrs.firstname, 80);
240
+ const lastname = sanitizePlainText(attrs.lastname, 80);
241
+ const username = sanitizePlainText(attrs.username, 80);
242
+ const explicitDisplayName = sanitizePlainText(attrs.displayName, 120);
243
+ const fullName = [firstname, lastname].filter(Boolean).join(" ").trim();
244
+ const displayName = fullName || username || explicitDisplayName || fallback || "";
245
+ if (!displayName && id === null) {
246
+ return null;
247
+ }
248
+ return {
249
+ id,
250
+ firstname,
251
+ lastname,
252
+ username,
253
+ displayName: displayName || `#${String(id)}`
254
+ };
255
+ }
256
+ function formatNoteAuthorDisplay(author, emptyFallback = "-") {
257
+ if (!author) {
258
+ return emptyFallback;
259
+ }
260
+ const fullName = [author.firstname, author.lastname].filter(Boolean).join(" ").trim();
261
+ if (fullName) {
262
+ return fullName;
263
+ }
264
+ if (author.username && author.username.trim()) {
265
+ return author.username.trim();
266
+ }
267
+ if (author.displayName && author.displayName.trim()) {
268
+ return author.displayName.trim();
269
+ }
270
+ return emptyFallback;
271
+ }
272
+ function formatNoteTimestamp(isoString, locale = "en", emptyFallback = "-") {
273
+ if (!isoString || typeof isoString !== "string") {
274
+ return emptyFallback;
275
+ }
276
+ const date = new Date(isoString);
277
+ if (Number.isNaN(date.getTime())) {
278
+ return emptyFallback;
279
+ }
280
+ try {
281
+ return new Intl.DateTimeFormat(locale || "en", {
282
+ dateStyle: "medium",
283
+ timeStyle: "short"
284
+ }).format(date);
285
+ } catch {
286
+ return date.toISOString();
287
+ }
288
+ }
289
+ function extractRecordId(root, attrs) {
290
+ if (typeof root.id === "number" && Number.isFinite(root.id) || typeof root.id === "string") {
291
+ return root.id;
292
+ }
293
+ if (typeof attrs.id === "number" && Number.isFinite(attrs.id) || typeof attrs.id === "string") {
294
+ return attrs.id;
295
+ }
296
+ return "";
297
+ }
298
+ function sanitizeOptionalIsoString(value) {
299
+ return typeof value === "string" && value.trim() ? value.trim() : null;
300
+ }
301
+ function unwrapRawNoteAttributes(raw) {
302
+ const root = asPlainObject(raw, {});
303
+ const rawAttrs = asPlainObject(root.attributes, {});
304
+ const attrs = { ...root, ...rawAttrs };
305
+ return { root, attrs };
306
+ }
307
+ function normalizeNoteRecord(raw) {
308
+ const { root, attrs } = unwrapRawNoteAttributes(raw);
309
+ const id = extractRecordId(root, attrs);
310
+ const documentId = sanitizeEntityDocumentId(root.documentId ?? attrs.documentId) || String(id || "");
311
+ const entityId = sanitizeEntityId(attrs.entityId);
312
+ const entityDocumentId = sanitizeEntityDocumentId(attrs.entityDocumentId) || (entityId !== null ? String(entityId) : "");
313
+ const createdBy = sanitizeNoteAuthor(
314
+ attrs.createdBy,
315
+ attrs.createdByName ?? attrs.authorName
316
+ );
317
+ const updatedBy = sanitizeNoteAuthor(attrs.updatedBy, attrs.updatedByName) ?? createdBy;
318
+ const createdAt = sanitizeOptionalIsoString(attrs.createdAt);
319
+ const updatedAt = sanitizeOptionalIsoString(attrs.updatedAt) ?? createdAt;
320
+ const normalizedAttributes = {
321
+ title: sanitizePlainText(attrs.title, MAX_NOTE_TITLE_LENGTH),
322
+ content: sanitizePlainText(attrs.content, MAX_NOTE_CONTENT_LENGTH),
323
+ category: normalizeNoteCategory(attrs.category),
324
+ isPinned: Boolean(attrs.isPinned),
325
+ entitySlug: sanitizeEntitySlug(attrs.entitySlug),
326
+ entityId,
327
+ entityDocumentId,
328
+ entityLocale: sanitizeEntityLocale(attrs.entityLocale),
329
+ createdAt,
330
+ updatedAt,
331
+ createdBy,
332
+ updatedBy
333
+ };
334
+ return {
335
+ id,
336
+ documentId,
337
+ ...normalizedAttributes,
338
+ attributes: normalizedAttributes
339
+ };
340
+ }
341
+ function sortNotesPinnedFirst(notes, direction = "DESC") {
342
+ return [...notes].sort((a, b) => {
343
+ if (a.isPinned !== b.isPinned) {
344
+ return a.isPinned ? -1 : 1;
345
+ }
346
+ const timeA = a.createdAt ? Date.parse(a.createdAt) : 0;
347
+ const timeB = b.createdAt ? Date.parse(b.createdAt) : 0;
348
+ if (timeA !== timeB && !Number.isNaN(timeA) && !Number.isNaN(timeB)) {
349
+ return direction === "ASC" ? timeA - timeB : timeB - timeA;
350
+ }
351
+ return String(a.id).localeCompare(String(b.id));
352
+ });
353
+ }
354
+ function matchesEntityIdentifier(note, docIdFilter, entityIdFilter) {
355
+ if (!docIdFilter && entityIdFilter === null) {
356
+ return true;
357
+ }
358
+ const noteIdStr = note.entityId !== null ? String(note.entityId) : "";
359
+ if (docIdFilter) {
360
+ if (note.entityDocumentId === docIdFilter || noteIdStr === docIdFilter) {
361
+ return true;
362
+ }
363
+ }
364
+ if (entityIdFilter !== null) {
365
+ const filterStr = String(entityIdFilter);
366
+ if (noteIdStr === filterStr || note.entityDocumentId === filterStr) {
367
+ return true;
368
+ }
369
+ }
370
+ return false;
371
+ }
372
+ function matchesLocaleFilter(note, localeFilter, includeAllLocales) {
373
+ if (!localeFilter || includeAllLocales) {
374
+ return true;
375
+ }
376
+ return !note.entityLocale || note.entityLocale === localeFilter;
377
+ }
378
+ function matchesSearchFilter(note, searchFilter) {
379
+ if (!searchFilter) {
380
+ return true;
381
+ }
382
+ const haystack = [
383
+ note.title,
384
+ note.content,
385
+ note.entitySlug,
386
+ note.createdBy?.displayName ?? "",
387
+ note.updatedBy?.displayName ?? ""
388
+ ].join(" ").toLowerCase();
389
+ return haystack.includes(searchFilter);
390
+ }
391
+ function matchesAllNoteFilters(note, filters) {
392
+ if (filters.slugFilter && note.entitySlug !== filters.slugFilter) {
393
+ return false;
394
+ }
395
+ if (!matchesEntityIdentifier(note, filters.docIdFilter, filters.entityIdFilter)) {
396
+ return false;
397
+ }
398
+ if (!matchesLocaleFilter(note, filters.localeFilter, filters.includeAllLocales)) {
399
+ return false;
400
+ }
401
+ if (filters.categoryFilter && note.category !== filters.categoryFilter) {
402
+ return false;
403
+ }
404
+ if (typeof filters.isPinned === "boolean" && note.isPinned !== filters.isPinned) {
405
+ return false;
406
+ }
407
+ return matchesSearchFilter(note, filters.searchFilter);
408
+ }
409
+ function filterNotesList(notes, params = {}) {
410
+ const filters = {
411
+ slugFilter: params.entitySlug ? sanitizeEntitySlug(params.entitySlug) : "",
412
+ docIdFilter: params.entityDocumentId ? sanitizeEntityDocumentId(params.entityDocumentId) : "",
413
+ entityIdFilter: params.entityId !== void 0 && params.entityId !== null ? sanitizeEntityId(params.entityId) : null,
414
+ localeFilter: sanitizeEntityLocale(params.entityLocale),
415
+ includeAllLocales: params.includeAllLocales,
416
+ categoryFilter: params.category && params.category !== "all" ? normalizeNoteCategory(params.category) : null,
417
+ isPinned: params.isPinned,
418
+ searchFilter: sanitizePlainText(params.search, 160).toLowerCase()
419
+ };
420
+ const filtered = notes.filter((note) => matchesAllNoteFilters(note, filters));
421
+ const sorted = sortNotesPinnedFirst(filtered, params.sort ?? "DESC");
422
+ const offset = typeof params.offset === "number" && Number.isFinite(params.offset) && params.offset > 0 ? Math.trunc(params.offset) : 0;
423
+ const limit = typeof params.limit === "number" && Number.isFinite(params.limit) && params.limit > 0 ? Math.trunc(params.limit) : void 0;
424
+ if (offset > 0 || limit !== void 0) {
425
+ return sorted.slice(
426
+ offset,
427
+ limit !== void 0 ? offset + limit : void 0
428
+ );
429
+ }
430
+ return sorted;
431
+ }
432
+ function computeNotesSummaryStats(notes) {
433
+ const byCategory = {
434
+ general: 0,
435
+ todo: 0,
436
+ blocker: 0,
437
+ idea: 0
438
+ };
439
+ let pinned = 0;
440
+ const slugSet = /* @__PURE__ */ new Set();
441
+ for (const note of notes) {
442
+ if (note.isPinned) {
443
+ pinned += 1;
444
+ }
445
+ const cat = normalizeNoteCategory(note.category);
446
+ byCategory[cat] += 1;
447
+ if (note.entitySlug) {
448
+ slugSet.add(note.entitySlug);
449
+ }
450
+ }
451
+ return {
452
+ total: notes.length,
453
+ pinned,
454
+ byCategory,
455
+ entitySlugs: Array.from(slugSet).sort()
456
+ };
457
+ }
458
+ function buildContentManagerNoteLink(note) {
459
+ const slug = sanitizeEntitySlug(note.entitySlug);
460
+ const targetId = sanitizeEntityDocumentId(note.entityDocumentId) || (note.entityId !== null && note.entityId !== void 0 ? sanitizeEntityDocumentId(String(note.entityId)) : "");
461
+ const isSingleType = note.collectionType === "single-types" || note.collectionType === "singleType" || targetId === "single";
462
+ if (!slug || !isSingleType && !targetId) {
463
+ return null;
464
+ }
465
+ const basePath = isSingleType ? `/content-manager/single-types/${encodeURIComponent(slug)}` : `/content-manager/collection-types/${encodeURIComponent(
466
+ slug
467
+ )}/${encodeURIComponent(targetId)}`;
468
+ const locale = sanitizeEntityLocale(note.entityLocale);
469
+ if (locale) {
470
+ return `${basePath}?plugins[i18n][locale]=${encodeURIComponent(locale)}`;
471
+ }
472
+ return basePath;
473
+ }
474
+ const getTrad = (id) => `${PLUGIN_ID}.${id}`;
475
+ function formatEntityIdParam(entityId) {
476
+ if (entityId === void 0 || entityId === null || entityId === "") {
477
+ return "";
478
+ }
479
+ return String(entityId);
480
+ }
481
+ function formatOptionalIntParam(value) {
482
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? String(Math.trunc(value)) : void 0;
483
+ }
484
+ function buildNotesQueryString(params = {}) {
485
+ const entries = [
486
+ ["entitySlug", params.entitySlug],
487
+ ["entityDocumentId", params.entityDocumentId],
488
+ ["entityId", formatEntityIdParam(params.entityId)],
489
+ ["entityLocale", params.entityLocale ?? void 0],
490
+ ["includeAllLocales", params.includeAllLocales ? "true" : void 0],
491
+ [
492
+ "category",
493
+ params.category && params.category !== "all" ? params.category : void 0
494
+ ],
495
+ [
496
+ "isPinned",
497
+ typeof params.isPinned === "boolean" ? String(params.isPinned) : void 0
498
+ ],
499
+ ["search", params.search],
500
+ ["sort", params.sort],
501
+ ["limit", formatOptionalIntParam(params.limit)],
502
+ ["offset", formatOptionalIntParam(params.offset)]
503
+ ];
504
+ const searchParams = new URLSearchParams();
505
+ for (const [key, value] of entries) {
506
+ if (value) {
507
+ searchParams.set(key, value);
508
+ }
509
+ }
510
+ const qs = searchParams.toString();
511
+ return qs ? `?${qs}` : "";
512
+ }
513
+ function matchesNoteIdentifier(note, targetId) {
514
+ const cleanTarget = String(targetId);
515
+ return String(note.id) === cleanTarget || String(note.documentId) === cleanTarget;
516
+ }
517
+ function useNotes(initialParams = {}) {
518
+ const { get, post, put, del } = useFetchClient();
519
+ const { toggleNotification } = useNotification();
520
+ const { formatMessage } = useIntl();
521
+ const [notes, setNotes] = useState([]);
522
+ const [summary, setSummary] = useState(
523
+ () => computeNotesSummaryStats([])
524
+ );
525
+ const [isLoading, setIsLoading] = useState(false);
526
+ const [isMutating, setIsMutating] = useState(false);
527
+ const requestIdRef = useRef(0);
528
+ const pendingMutationsRef = useRef(0);
529
+ const notesRef = useRef([]);
530
+ const beginMutation = useCallback(() => {
531
+ pendingMutationsRef.current += 1;
532
+ setIsMutating(true);
533
+ }, []);
534
+ const endMutation = useCallback(() => {
535
+ pendingMutationsRef.current = Math.max(0, pendingMutationsRef.current - 1);
536
+ if (pendingMutationsRef.current === 0) {
537
+ setIsMutating(false);
538
+ }
539
+ }, []);
540
+ const t = useCallback(
541
+ (id, defaultMessage) => formatMessage({ id: getTrad(id), defaultMessage }),
542
+ [formatMessage]
543
+ );
544
+ const applyLocalNotesState = useCallback(
545
+ (updater) => {
546
+ const next = sortNotesPinnedFirst(
547
+ updater(notesRef.current),
548
+ initialParams.sort ?? "DESC"
549
+ );
550
+ notesRef.current = next;
551
+ setNotes(next);
552
+ setSummary(computeNotesSummaryStats(next));
553
+ },
554
+ [initialParams.sort]
555
+ );
556
+ const fetchNotes = useCallback(
557
+ async (overrideParams) => {
558
+ const requestId = ++requestIdRef.current;
559
+ const merged = { ...initialParams, ...overrideParams };
560
+ setIsLoading(true);
561
+ try {
562
+ const qs = buildNotesQueryString(merged);
563
+ const res = await get(`/${PLUGIN_ID}/notes${qs}`);
564
+ const rawItems = Array.isArray(res?.data?.data) ? res.data.data : [];
565
+ const normalized = rawItems.map((item) => normalizeNoteRecord(item));
566
+ if (requestId === requestIdRef.current) {
567
+ notesRef.current = normalized;
568
+ setNotes(normalized);
569
+ setSummary(
570
+ res?.data?.meta?.summary ?? computeNotesSummaryStats(normalized)
571
+ );
572
+ }
573
+ return normalized;
574
+ } catch {
575
+ if (requestId === requestIdRef.current) {
576
+ toggleNotification({
577
+ type: "warning",
578
+ message: t("notes.notification.loadError", "Unable to load notes.")
579
+ });
580
+ }
581
+ return [];
582
+ } finally {
583
+ if (requestId === requestIdRef.current) {
584
+ setIsLoading(false);
585
+ }
586
+ }
587
+ },
588
+ [
589
+ get,
590
+ initialParams.category,
591
+ initialParams.entityDocumentId,
592
+ initialParams.entityId,
593
+ initialParams.entityLocale,
594
+ initialParams.entitySlug,
595
+ initialParams.includeAllLocales,
596
+ initialParams.isPinned,
597
+ initialParams.limit,
598
+ initialParams.offset,
599
+ initialParams.search,
600
+ initialParams.sort,
601
+ t,
602
+ toggleNotification
603
+ ]
604
+ );
605
+ useEffect(() => {
606
+ void fetchNotes();
607
+ return () => {
608
+ requestIdRef.current += 1;
609
+ };
610
+ }, [fetchNotes]);
611
+ const createNote = useCallback(
612
+ async (payload) => {
613
+ beginMutation();
614
+ try {
615
+ const res = await post(`/${PLUGIN_ID}/notes`, {
616
+ data: payload
617
+ });
618
+ const created = res?.data?.data ? normalizeNoteRecord(res.data.data) : null;
619
+ if (created) {
620
+ applyLocalNotesState((prev) => [created, ...prev]);
621
+ }
622
+ toggleNotification({
623
+ type: "success",
624
+ message: t(
625
+ "notes.notification.created",
626
+ "Note has been successfully created."
627
+ )
628
+ });
629
+ await fetchNotes();
630
+ return created;
631
+ } catch {
632
+ toggleNotification({
633
+ type: "warning",
634
+ message: t(
635
+ "notes.notification.createError",
636
+ "Failed to create note."
637
+ )
638
+ });
639
+ return null;
640
+ } finally {
641
+ endMutation();
642
+ }
643
+ },
644
+ [
645
+ applyLocalNotesState,
646
+ beginMutation,
647
+ endMutation,
648
+ fetchNotes,
649
+ post,
650
+ t,
651
+ toggleNotification
652
+ ]
653
+ );
654
+ const updateNote = useCallback(
655
+ async (id, body) => {
656
+ beginMutation();
657
+ try {
658
+ const res = await put(
659
+ `/${PLUGIN_ID}/notes/${encodeURIComponent(String(id))}`,
660
+ { data: body }
661
+ );
662
+ const updated = res?.data?.data ? normalizeNoteRecord(res.data.data) : null;
663
+ if (updated) {
664
+ applyLocalNotesState(
665
+ (prev) => prev.map(
666
+ (item) => matchesNoteIdentifier(item, id) ? updated : item
667
+ )
668
+ );
669
+ }
670
+ toggleNotification({
671
+ type: "success",
672
+ message: t(
673
+ "notes.notification.updated",
674
+ "Note has been successfully updated."
675
+ )
676
+ });
677
+ await fetchNotes();
678
+ return updated;
679
+ } catch {
680
+ toggleNotification({
681
+ type: "warning",
682
+ message: t(
683
+ "notes.notification.updateError",
684
+ "Failed to update note."
685
+ )
686
+ });
687
+ return null;
688
+ } finally {
689
+ endMutation();
690
+ }
691
+ },
692
+ [
693
+ applyLocalNotesState,
694
+ beginMutation,
695
+ endMutation,
696
+ fetchNotes,
697
+ put,
698
+ t,
699
+ toggleNotification
700
+ ]
701
+ );
702
+ const deleteNote = useCallback(
703
+ async (id) => {
704
+ beginMutation();
705
+ try {
706
+ await del(`/${PLUGIN_ID}/notes/${encodeURIComponent(String(id))}`);
707
+ applyLocalNotesState(
708
+ (prev) => prev.filter((item) => !matchesNoteIdentifier(item, id))
709
+ );
710
+ toggleNotification({
711
+ type: "success",
712
+ message: t(
713
+ "notes.notification.deleted",
714
+ "Note has been successfully deleted."
715
+ )
716
+ });
717
+ await fetchNotes();
718
+ return true;
719
+ } catch {
720
+ toggleNotification({
721
+ type: "warning",
722
+ message: t(
723
+ "notes.notification.deleteError",
724
+ "Failed to delete note."
725
+ )
726
+ });
727
+ return false;
728
+ } finally {
729
+ endMutation();
730
+ }
731
+ },
732
+ [
733
+ applyLocalNotesState,
734
+ beginMutation,
735
+ del,
736
+ endMutation,
737
+ fetchNotes,
738
+ t,
739
+ toggleNotification
740
+ ]
741
+ );
742
+ return {
743
+ notes,
744
+ summary,
745
+ isLoading,
746
+ isMutating,
747
+ refetch: fetchNotes,
748
+ createNote,
749
+ updateNote,
750
+ deleteNote
751
+ };
752
+ }
753
+ const DeleteNoteDialog = ({
754
+ note,
755
+ isSubmitting = false,
756
+ onCancel,
757
+ onConfirm
758
+ }) => {
759
+ const { formatMessage } = useIntl();
760
+ if (!note) {
761
+ return null;
762
+ }
763
+ const t = (id, defaultMessage, values) => formatMessage({ id: getTrad(id), defaultMessage }, values);
764
+ return /* @__PURE__ */ jsx(
765
+ Dialog.Root,
766
+ {
767
+ open: Boolean(note),
768
+ onOpenChange: (open) => !open && onCancel(),
769
+ children: /* @__PURE__ */ jsxs(Dialog.Content, { children: [
770
+ /* @__PURE__ */ jsx(Dialog.Header, { children: t("note.delete.dialog.title", "Confirmation") }),
771
+ /* @__PURE__ */ jsx(Dialog.Body, { icon: /* @__PURE__ */ jsx(WarningCircle, {}), children: t(
772
+ "note.delete.dialog.description",
773
+ 'Are you sure you want to delete the note "{title}"?',
774
+ { title: note.title }
775
+ ) }),
776
+ /* @__PURE__ */ jsxs(Dialog.Footer, { children: [
777
+ /* @__PURE__ */ jsx(Dialog.Cancel, { children: /* @__PURE__ */ jsx(Button, { fullWidth: true, variant: "tertiary", onClick: onCancel, children: t("note.modal.actions.cancel", "Cancel") }) }),
778
+ /* @__PURE__ */ jsx(Dialog.Action, { children: /* @__PURE__ */ jsx(
779
+ Button,
780
+ {
781
+ fullWidth: true,
782
+ variant: "danger-light",
783
+ startIcon: /* @__PURE__ */ jsx(Trash, {}),
784
+ loading: isSubmitting,
785
+ disabled: isSubmitting,
786
+ onClick: () => onConfirm(note),
787
+ children: t("note.delete.dialog.confirm", "Confirm")
788
+ }
789
+ ) })
790
+ ] })
791
+ ] })
792
+ }
793
+ );
794
+ };
795
+ const NoteListItem = ({
796
+ note,
797
+ onEdit,
798
+ onRequestDelete,
799
+ onTogglePin
800
+ }) => {
801
+ const { formatMessage, locale } = useIntl();
802
+ const t = (id, defaultMessage) => formatMessage({ id: getTrad(id), defaultMessage });
803
+ const categoryMeta = NOTE_CATEGORY_META[note.category] ?? NOTE_CATEGORY_META.general;
804
+ const authorLabel = formatNoteAuthorDisplay(
805
+ note.updatedBy ?? note.createdBy,
806
+ ""
807
+ );
808
+ const timestampLabel = formatNoteTimestamp(
809
+ note.updatedAt ?? note.createdAt,
810
+ locale,
811
+ ""
812
+ );
813
+ return /* @__PURE__ */ jsx(
814
+ Box,
815
+ {
816
+ hasRadius: true,
817
+ padding: 3,
818
+ background: "neutral0",
819
+ borderStyle: "solid",
820
+ borderWidth: "1px",
821
+ borderColor: note.isPinned ? "primary600" : "neutral150",
822
+ shadow: "tableShadow",
823
+ tag: "article",
824
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 1, children: [
825
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, justifyContent: "space-between", alignItems: "center", children: [
826
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", style: { minWidth: 0, flex: 1 }, children: [
827
+ /* @__PURE__ */ jsx(
828
+ Badge,
829
+ {
830
+ backgroundColor: categoryMeta.badgeBackground,
831
+ textColor: categoryMeta.badgeTextColor,
832
+ children: t(categoryMeta.labelKey, categoryMeta.defaultLabel)
833
+ }
834
+ ),
835
+ note.entityLocale && /* @__PURE__ */ jsx(Badge, { backgroundColor: "neutral100", textColor: "neutral700", children: note.entityLocale.toUpperCase() }),
836
+ /* @__PURE__ */ jsx(
837
+ Typography,
838
+ {
839
+ variant: "pi",
840
+ textColor: "neutral800",
841
+ fontWeight: "bold",
842
+ ellipsis: true,
843
+ role: "button",
844
+ tabIndex: 0,
845
+ style: { cursor: "pointer" },
846
+ onClick: () => onEdit(note),
847
+ onKeyDown: (e) => {
848
+ if (e.key === "Enter" || e.key === " ") {
849
+ e.preventDefault();
850
+ onEdit(note);
851
+ }
852
+ },
853
+ children: note.title
854
+ }
855
+ )
856
+ ] }),
857
+ /* @__PURE__ */ jsxs(Flex, { gap: 1, alignItems: "center", children: [
858
+ /* @__PURE__ */ jsx(
859
+ IconButton,
860
+ {
861
+ withTooltip: false,
862
+ variant: note.isPinned ? "secondary" : "ghost",
863
+ onClick: () => onTogglePin(note),
864
+ label: note.isPinned ? t("note.actions.unpin", "Unpin note") : t("note.actions.pin", "Pin note"),
865
+ children: /* @__PURE__ */ jsx(Pin, {})
866
+ }
867
+ ),
868
+ /* @__PURE__ */ jsx(
869
+ IconButton,
870
+ {
871
+ withTooltip: false,
872
+ variant: "ghost",
873
+ onClick: () => onEdit(note),
874
+ label: t("note.actions.edit", "Edit"),
875
+ children: /* @__PURE__ */ jsx(Pencil, {})
876
+ }
877
+ ),
878
+ /* @__PURE__ */ jsx(
879
+ IconButton,
880
+ {
881
+ withTooltip: false,
882
+ variant: "ghost",
883
+ onClick: () => onRequestDelete(note),
884
+ label: t("note.actions.delete", "Delete"),
885
+ children: /* @__PURE__ */ jsx(Trash, {})
886
+ }
887
+ )
888
+ ] })
889
+ ] }),
890
+ (authorLabel || timestampLabel) && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", ellipsis: true, children: [authorLabel, timestampLabel].filter(Boolean).join(" • ") })
891
+ ] })
892
+ }
893
+ );
894
+ };
895
+ function resolveNormalizedExisting(note) {
896
+ if (!note) return null;
897
+ const hasId = Boolean(note.id || note.documentId);
898
+ return hasId ? normalizeNoteRecord(note) : null;
899
+ }
900
+ function resolveEntityDocumentId(existingDocId, entity) {
901
+ if (existingDocId) return existingDocId;
902
+ if (entity.documentId) return entity.documentId;
903
+ if (entity.id !== null && entity.id !== void 0) return String(entity.id);
904
+ return "";
905
+ }
906
+ function buildNoteSavePayload(params) {
907
+ const {
908
+ trimmedTitle,
909
+ content,
910
+ category,
911
+ isPinned,
912
+ scopeToLocale,
913
+ effectiveLocale,
914
+ normalizedExisting,
915
+ entity
916
+ } = params;
917
+ return {
918
+ title: trimmedTitle,
919
+ content: content.trim(),
920
+ category: normalizeNoteCategory(category),
921
+ isPinned,
922
+ entitySlug: normalizedExisting?.entitySlug || entity.slug,
923
+ entityId: normalizedExisting?.entityId ?? entity.id ?? null,
924
+ entityDocumentId: resolveEntityDocumentId(
925
+ normalizedExisting?.entityDocumentId,
926
+ entity
927
+ ),
928
+ entityLocale: scopeToLocale ? effectiveLocale : null
929
+ };
930
+ }
931
+ const NoteMetadataBox = ({
932
+ note,
933
+ uiLocale,
934
+ t
935
+ }) => /* @__PURE__ */ jsx(
936
+ Box,
937
+ {
938
+ padding: 4,
939
+ background: "neutral100",
940
+ hasRadius: true,
941
+ "data-testid": "note-modal-meta-box",
942
+ children: /* @__PURE__ */ jsxs(Grid.Root, { gap: 4, children: [
943
+ /* @__PURE__ */ jsx(Grid.Item, { col: 6, s: 12, direction: "column", alignItems: "flex-start", children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
944
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: t("note.modal.meta.createdAt", "Created At") }),
945
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral800", children: formatNoteTimestamp(note.createdAt, uiLocale) })
946
+ ] }) }),
947
+ /* @__PURE__ */ jsx(Grid.Item, { col: 6, s: 12, direction: "column", alignItems: "flex-start", children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
948
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: t("note.modal.meta.createdBy", "Created By") }),
949
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral800", children: formatNoteAuthorDisplay(note.createdBy) })
950
+ ] }) }),
951
+ /* @__PURE__ */ jsx(Grid.Item, { col: 6, s: 12, direction: "column", alignItems: "flex-start", children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
952
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: t("note.modal.meta.updatedAt", "Updated At") }),
953
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral800", children: formatNoteTimestamp(note.updatedAt, uiLocale) })
954
+ ] }) }),
955
+ /* @__PURE__ */ jsx(Grid.Item, { col: 6, s: 12, direction: "column", alignItems: "flex-start", children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
956
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: t("note.modal.meta.updatedBy", "Updated By") }),
957
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral800", children: formatNoteAuthorDisplay(note.updatedBy) })
958
+ ] }) })
959
+ ] })
960
+ }
961
+ );
962
+ const NoteModal = ({
963
+ entity,
964
+ note,
965
+ isOpen,
966
+ isSubmitting = false,
967
+ onClose,
968
+ onSubmit
969
+ }) => {
970
+ const { formatMessage, locale: uiLocale } = useIntl();
971
+ const normalizedExisting = React.useMemo(
972
+ () => resolveNormalizedExisting(note),
973
+ [note]
974
+ );
975
+ const isExistingNote = Boolean(normalizedExisting);
976
+ const [title, setTitle] = React.useState("");
977
+ const [content, setContent] = React.useState("");
978
+ const [category, setCategory] = React.useState("general");
979
+ const [isPinned, setIsPinned] = React.useState(false);
980
+ const [scopeToLocale, setScopeToLocale] = React.useState(false);
981
+ const [titleError, setTitleError] = React.useState("");
982
+ const lastInitKeyRef = React.useRef(null);
983
+ const noteIdentityKey = normalizedExisting ? String(normalizedExisting.id || normalizedExisting.documentId) : "new";
984
+ React.useEffect(() => {
985
+ if (!isOpen) {
986
+ lastInitKeyRef.current = null;
987
+ return;
988
+ }
989
+ if (lastInitKeyRef.current === noteIdentityKey) {
990
+ return;
991
+ }
992
+ lastInitKeyRef.current = noteIdentityKey;
993
+ setTitle(normalizedExisting?.title ?? "");
994
+ setContent(normalizedExisting?.content ?? "");
995
+ setCategory(normalizedExisting?.category ?? "general");
996
+ setIsPinned(Boolean(normalizedExisting?.isPinned));
997
+ setScopeToLocale(Boolean(normalizedExisting?.entityLocale));
998
+ setTitleError("");
999
+ }, [isOpen, noteIdentityKey, normalizedExisting]);
1000
+ const t = (id, defaultMessage, values) => formatMessage({ id: getTrad(id), defaultMessage }, values);
1001
+ const effectiveLocale = normalizedExisting?.entityLocale || sanitizeEntityLocale(entity.locale) || null;
1002
+ const handleSave = async () => {
1003
+ if (isSubmitting) {
1004
+ return;
1005
+ }
1006
+ const trimmedTitle = title.trim();
1007
+ if (!trimmedTitle) {
1008
+ setTitleError(
1009
+ t("note.modal.validation.titleRequired", "Title is required.")
1010
+ );
1011
+ return;
1012
+ }
1013
+ setTitleError("");
1014
+ const payload = buildNoteSavePayload({
1015
+ trimmedTitle,
1016
+ content,
1017
+ category,
1018
+ isPinned,
1019
+ scopeToLocale,
1020
+ effectiveLocale,
1021
+ normalizedExisting,
1022
+ entity
1023
+ });
1024
+ const targetId = normalizedExisting?.id || normalizedExisting?.documentId;
1025
+ await onSubmit(payload, targetId);
1026
+ };
1027
+ if (!isOpen) {
1028
+ return null;
1029
+ }
1030
+ return /* @__PURE__ */ jsx(
1031
+ Modal.Root,
1032
+ {
1033
+ open: isOpen,
1034
+ onOpenChange: (open) => !open && onClose(),
1035
+ children: /* @__PURE__ */ jsxs(Modal.Content, { children: [
1036
+ /* @__PURE__ */ jsx(Modal.Header, { closeLabel: t("note.modal.actions.close", "Close modal"), children: /* @__PURE__ */ jsx(Modal.Title, { children: isExistingNote ? t("note.modal.existing.title", "Edit a note") : t("note.modal.new.title", "Add a note") }) }),
1037
+ /* @__PURE__ */ jsx(Modal.Body, { children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
1038
+ normalizedExisting && /* @__PURE__ */ jsx(
1039
+ NoteMetadataBox,
1040
+ {
1041
+ note: normalizedExisting,
1042
+ uiLocale,
1043
+ t
1044
+ }
1045
+ ),
1046
+ /* @__PURE__ */ jsxs(Field.Root, { name: "title", required: true, error: titleError || void 0, children: [
1047
+ /* @__PURE__ */ jsx(Field.Label, { children: t("note.modal.fields.title", "Title") }),
1048
+ /* @__PURE__ */ jsx(
1049
+ TextInput,
1050
+ {
1051
+ name: "title",
1052
+ value: title,
1053
+ maxLength: MAX_NOTE_TITLE_LENGTH,
1054
+ onChange: (e) => {
1055
+ setTitle(e.target.value);
1056
+ if (titleError) setTitleError("");
1057
+ },
1058
+ placeholder: t(
1059
+ "note.modal.fields.title.placeholder",
1060
+ "Short summary of this note..."
1061
+ )
1062
+ }
1063
+ ),
1064
+ titleError && /* @__PURE__ */ jsx(Field.Error, {})
1065
+ ] }),
1066
+ /* @__PURE__ */ jsxs(Field.Root, { name: "category", children: [
1067
+ /* @__PURE__ */ jsx(Field.Label, { children: t("note.modal.fields.category", "Category") }),
1068
+ /* @__PURE__ */ jsx(
1069
+ SingleSelect,
1070
+ {
1071
+ value: category,
1072
+ onChange: (val) => setCategory(normalizeNoteCategory(val)),
1073
+ children: NOTE_CATEGORIES.map((cat) => /* @__PURE__ */ jsx(SingleSelectOption, { value: cat, children: t(
1074
+ NOTE_CATEGORY_META[cat].labelKey,
1075
+ NOTE_CATEGORY_META[cat].defaultLabel
1076
+ ) }, cat))
1077
+ }
1078
+ )
1079
+ ] }),
1080
+ /* @__PURE__ */ jsxs(Field.Root, { name: "content", children: [
1081
+ /* @__PURE__ */ jsx(Field.Label, { children: t("note.modal.fields.content", "Content") }),
1082
+ /* @__PURE__ */ jsx(
1083
+ Textarea,
1084
+ {
1085
+ name: "content",
1086
+ value: content,
1087
+ maxLength: MAX_NOTE_CONTENT_LENGTH,
1088
+ onChange: (e) => setContent(e.target.value),
1089
+ placeholder: t(
1090
+ "note.modal.fields.content.placeholder",
1091
+ "Write detailed editorial context, review feedback, or follow-up steps..."
1092
+ )
1093
+ }
1094
+ )
1095
+ ] }),
1096
+ /* @__PURE__ */ jsxs(Flex, { gap: 4, wrap: "wrap", paddingTop: 1, children: [
1097
+ /* @__PURE__ */ jsx(
1098
+ Checkbox,
1099
+ {
1100
+ checked: isPinned,
1101
+ onCheckedChange: (checked) => setIsPinned(Boolean(checked)),
1102
+ children: t("note.modal.fields.isPinned", "Pin note to top")
1103
+ }
1104
+ ),
1105
+ effectiveLocale && /* @__PURE__ */ jsx(
1106
+ Checkbox,
1107
+ {
1108
+ checked: scopeToLocale,
1109
+ onCheckedChange: (checked) => setScopeToLocale(Boolean(checked)),
1110
+ children: t(
1111
+ "note.modal.fields.scopeToLocale",
1112
+ "Scope only to locale ({locale})",
1113
+ {
1114
+ locale: effectiveLocale
1115
+ }
1116
+ )
1117
+ }
1118
+ )
1119
+ ] })
1120
+ ] }) }),
1121
+ /* @__PURE__ */ jsxs(Modal.Footer, { children: [
1122
+ /* @__PURE__ */ jsx(Modal.Close, { children: /* @__PURE__ */ jsx(Button, { onClick: onClose, variant: "tertiary", children: t("note.modal.actions.cancel", "Cancel") }) }),
1123
+ /* @__PURE__ */ jsx(
1124
+ Button,
1125
+ {
1126
+ onClick: handleSave,
1127
+ startIcon: /* @__PURE__ */ jsx(Check, {}),
1128
+ loading: isSubmitting,
1129
+ disabled: isSubmitting,
1130
+ children: t("note.modal.actions.save", "Save")
1131
+ }
1132
+ )
1133
+ ] })
1134
+ ] })
1135
+ }
1136
+ );
1137
+ };
1138
+ function detectDocumentLocale(props) {
1139
+ const fromDoc = sanitizeEntityLocale(props.document?.locale);
1140
+ if (fromDoc) {
1141
+ return fromDoc;
1142
+ }
1143
+ if (typeof window !== "undefined") {
1144
+ const fromUrl = new URLSearchParams(window.location.search).get(
1145
+ "plugins[i18n][locale]"
1146
+ );
1147
+ return sanitizeEntityLocale(fromUrl);
1148
+ }
1149
+ return null;
1150
+ }
1151
+ function interpolateTemplateValues(template, values) {
1152
+ if (!values) return template;
1153
+ return Object.entries(values).reduce(
1154
+ (acc, [k, v]) => acc.split(`{${k}}`).join(String(v)),
1155
+ template
1156
+ );
1157
+ }
1158
+ function formatNoteMessageWithLocale(id, defaultMessage, formatMessage, uiLocale, entityLocale, values) {
1159
+ const fullKey = getTrad(id);
1160
+ const fallbackEntityLoc = entityLocale || (typeof window !== "undefined" ? new URLSearchParams(window.location.search).get("plugins[i18n][locale]") : null);
1161
+ const globalMessages = globalThis.__CM_I18N_MESSAGES__;
1162
+ const override = (uiLocale ? globalMessages?.[uiLocale]?.[fullKey] : void 0) ?? (fallbackEntityLoc ? globalMessages?.[fallbackEntityLoc]?.[fullKey] : void 0);
1163
+ if (override) {
1164
+ return interpolateTemplateValues(override, values);
1165
+ }
1166
+ return formatMessage({ id: fullKey, defaultMessage }, values);
1167
+ }
1168
+ const NotesSidePanelContent = ({
1169
+ entity
1170
+ }) => {
1171
+ const { formatMessage, locale: uiLocale } = useIntl();
1172
+ const [searchQuery, setSearchQuery] = React.useState("");
1173
+ const [categoryFilter, setCategoryFilter] = React.useState("all");
1174
+ const [isModalOpen, setIsModalOpen] = React.useState(false);
1175
+ const [activeNote, setActiveNote] = React.useState(null);
1176
+ const [noteToDelete, setNoteToDelete] = React.useState(
1177
+ null
1178
+ );
1179
+ const { notes, isLoading, isMutating, createNote, updateNote, deleteNote } = useNotes({
1180
+ entitySlug: entity.slug,
1181
+ entityDocumentId: entity.documentId,
1182
+ entityId: entity.id,
1183
+ entityLocale: entity.locale
1184
+ });
1185
+ const t = (id, defaultMessage, values) => formatNoteMessageWithLocale(
1186
+ id,
1187
+ defaultMessage,
1188
+ formatMessage,
1189
+ uiLocale,
1190
+ entity.locale,
1191
+ values
1192
+ );
1193
+ const visibleNotes = React.useMemo(
1194
+ () => filterNotesList(notes, {
1195
+ category: categoryFilter,
1196
+ search: searchQuery,
1197
+ sort: "DESC"
1198
+ }),
1199
+ [notes, categoryFilter, searchQuery]
1200
+ );
1201
+ const showFilterControls = notes.length > 1 || Boolean(searchQuery.trim()) || categoryFilter !== "all";
1202
+ const handleUpsert = async (payload, existingId) => {
1203
+ const saved = existingId !== void 0 && existingId !== "" ? await updateNote(existingId, payload) : await createNote(payload);
1204
+ if (saved) {
1205
+ setIsModalOpen(false);
1206
+ setActiveNote(null);
1207
+ }
1208
+ };
1209
+ const handleTogglePin = async (note) => {
1210
+ const targetId = note.id || note.documentId;
1211
+ if (!targetId) return;
1212
+ await updateNote(targetId, { isPinned: !note.isPinned });
1213
+ };
1214
+ const handleConfirmDelete = async (note) => {
1215
+ const targetId = note.id || note.documentId;
1216
+ if (!targetId) return;
1217
+ const deleted = await deleteNote(targetId);
1218
+ if (deleted) {
1219
+ setNoteToDelete(null);
1220
+ }
1221
+ };
1222
+ return /* @__PURE__ */ jsx(Box, { paddingTop: 2, paddingBottom: 2, children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 3, children: [
1223
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
1224
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: t("notes.sidePanel.count", "{count} note(s)", {
1225
+ count: notes.length
1226
+ }) }),
1227
+ entity.locale && /* @__PURE__ */ jsx(Badge, { backgroundColor: "neutral150", textColor: "neutral800", children: entity.locale.toUpperCase() })
1228
+ ] }),
1229
+ showFilterControls && /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
1230
+ /* @__PURE__ */ jsx(
1231
+ TextInput,
1232
+ {
1233
+ name: "noteSearch",
1234
+ size: "S",
1235
+ value: searchQuery,
1236
+ onChange: (e) => setSearchQuery(e.target.value),
1237
+ placeholder: t(
1238
+ "notes.sidePanel.search.placeholder",
1239
+ "Filter notes..."
1240
+ )
1241
+ }
1242
+ ),
1243
+ /* @__PURE__ */ jsxs(
1244
+ SingleSelect,
1245
+ {
1246
+ size: "S",
1247
+ value: categoryFilter,
1248
+ onChange: (val) => setCategoryFilter(
1249
+ String(val) || "all"
1250
+ ),
1251
+ children: [
1252
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "all", children: t("category.all", "All categories") }),
1253
+ NOTE_CATEGORIES.map((cat) => /* @__PURE__ */ jsx(SingleSelectOption, { value: cat, children: t(
1254
+ NOTE_CATEGORY_META[cat].labelKey,
1255
+ NOTE_CATEGORY_META[cat].defaultLabel
1256
+ ) }, cat))
1257
+ ]
1258
+ }
1259
+ )
1260
+ ] }),
1261
+ /* @__PURE__ */ jsx(Divider, {}),
1262
+ isLoading ? /* @__PURE__ */ jsx(Flex, { justifyContent: "center", paddingTop: 3, paddingBottom: 3, children: /* @__PURE__ */ jsx(Loader, { small: true, children: t("notes.loading", "Loading Content ...") }) }) : visibleNotes.length === 0 ? /* @__PURE__ */ jsx(Box, { padding: 3, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: notes.length === 0 ? t(
1263
+ "notes.sidePanel.empty",
1264
+ "No editorial notes attached to this entry yet."
1265
+ ) : t(
1266
+ "notes.sidePanel.noMatches",
1267
+ "No notes match the current filter."
1268
+ ) }) }) : /* @__PURE__ */ jsx(
1269
+ Flex,
1270
+ {
1271
+ direction: "column",
1272
+ alignItems: "stretch",
1273
+ gap: 2,
1274
+ style: {
1275
+ maxHeight: "240px",
1276
+ overflowY: "auto",
1277
+ overflowX: "hidden"
1278
+ },
1279
+ children: visibleNotes.map((item) => /* @__PURE__ */ jsx(
1280
+ NoteListItem,
1281
+ {
1282
+ note: item,
1283
+ onEdit: (selected) => {
1284
+ setActiveNote(selected);
1285
+ setIsModalOpen(true);
1286
+ },
1287
+ onRequestDelete: (selected) => setNoteToDelete(selected),
1288
+ onTogglePin: handleTogglePin
1289
+ },
1290
+ String(item.id || item.documentId)
1291
+ ))
1292
+ }
1293
+ ),
1294
+ /* @__PURE__ */ jsx(
1295
+ Button,
1296
+ {
1297
+ fullWidth: true,
1298
+ variant: "secondary",
1299
+ startIcon: /* @__PURE__ */ jsx(Plus, {}),
1300
+ disabled: isLoading || isMutating,
1301
+ onClick: () => {
1302
+ setActiveNote(null);
1303
+ setIsModalOpen(true);
1304
+ },
1305
+ children: t("note.modal.new.title", "Add a note")
1306
+ }
1307
+ ),
1308
+ /* @__PURE__ */ jsx(
1309
+ NoteModal,
1310
+ {
1311
+ entity,
1312
+ note: activeNote,
1313
+ isOpen: isModalOpen,
1314
+ isSubmitting: isMutating,
1315
+ onClose: () => {
1316
+ setIsModalOpen(false);
1317
+ setActiveNote(null);
1318
+ },
1319
+ onSubmit: handleUpsert
1320
+ }
1321
+ ),
1322
+ /* @__PURE__ */ jsx(
1323
+ DeleteNoteDialog,
1324
+ {
1325
+ note: noteToDelete,
1326
+ isSubmitting: isMutating,
1327
+ onCancel: () => setNoteToDelete(null),
1328
+ onConfirm: handleConfirmDelete
1329
+ }
1330
+ )
1331
+ ] }) });
1332
+ };
1333
+ function resolveSidePanelDocumentId(props) {
1334
+ if (props.documentId) return props.documentId;
1335
+ const docId = props.document?.documentId;
1336
+ return typeof docId === "string" ? docId : "";
1337
+ }
1338
+ function resolveSidePanelNumericId(props) {
1339
+ const id = props.document?.id;
1340
+ if (typeof id === "number" || typeof id === "string") {
1341
+ return id;
1342
+ }
1343
+ return null;
1344
+ }
1345
+ function resolveSidePanelEntity(props) {
1346
+ const slug = props.model ?? "";
1347
+ const documentId = resolveSidePanelDocumentId(props);
1348
+ const numericId = resolveSidePanelNumericId(props);
1349
+ const isSingleType = props.collectionType === "single-types" || props.collectionType === "singleType";
1350
+ const hasIdentifier = Boolean(
1351
+ documentId || numericId !== null || isSingleType
1352
+ );
1353
+ if (!slug || !hasIdentifier) {
1354
+ return null;
1355
+ }
1356
+ return {
1357
+ id: numericId,
1358
+ documentId: documentId || (numericId !== null ? String(numericId) : "single"),
1359
+ slug,
1360
+ locale: detectDocumentLocale(props)
1361
+ };
1362
+ }
1363
+ const NotesSidePanel = (props) => {
1364
+ const { formatMessage, locale: uiLocale } = useIntl();
1365
+ const entity = resolveSidePanelEntity(props);
1366
+ if (!entity) {
1367
+ return null;
1368
+ }
1369
+ return {
1370
+ title: formatNoteMessageWithLocale(
1371
+ "plugin.name",
1372
+ "Notes",
1373
+ formatMessage,
1374
+ uiLocale,
1375
+ entity.locale
1376
+ ),
1377
+ content: /* @__PURE__ */ jsx(NotesSidePanelContent, { entity })
1378
+ };
1379
+ };
1380
+ const PluginIcon = () => /* @__PURE__ */ jsx(File, {});
1381
+ const index = {
1382
+ register(app) {
1383
+ app.addMenuLink({
1384
+ to: `plugins/${PLUGIN_ID}`,
1385
+ icon: PluginIcon,
1386
+ intlLabel: {
1387
+ id: `${PLUGIN_ID}.plugin.name`,
1388
+ defaultMessage: "Notes"
1389
+ },
1390
+ Component: () => import("./NotesHomePage-CIWSOu3n.js").then((mod) => ({
1391
+ default: mod.NotesHomePage
1392
+ })),
1393
+ permissions: [
1394
+ {
1395
+ action: `plugin::${PLUGIN_ID}.read`,
1396
+ subject: null
1397
+ }
1398
+ ]
1399
+ });
1400
+ app.registerPlugin({
1401
+ id: PLUGIN_ID,
1402
+ initializer: Initializer,
1403
+ isReady: false,
1404
+ name: PLUGIN_ID
1405
+ });
1406
+ },
1407
+ bootstrap(app) {
1408
+ const cmPlugin = app.getPlugin("content-manager");
1409
+ if (cmPlugin?.apis && typeof cmPlugin.apis.addEditViewSidePanel === "function") {
1410
+ cmPlugin.apis.addEditViewSidePanel([NotesSidePanel]);
1411
+ }
1412
+ },
1413
+ registerTrads: createRegisterTrads({
1414
+ pluginId: PLUGIN_ID,
1415
+ defaultLocales: [
1416
+ "en",
1417
+ "es",
1418
+ "ar",
1419
+ "tr",
1420
+ "fr",
1421
+ "de",
1422
+ "it",
1423
+ "pt",
1424
+ "ru",
1425
+ "zh"
1426
+ ],
1427
+ loadTranslation: (locale) => __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/ar.json": () => import("./ar-uSBvoP4-.js"), "./translations/de.json": () => import("./de-DlFY4yCr.js"), "./translations/en.json": () => import("./en-DQUtJt3K.js"), "./translations/es.json": () => import("./es-DcTVGahO.js"), "./translations/fr.json": () => import("./fr-QW7vMjBs.js"), "./translations/it.json": () => import("./it-F24NTVne.js"), "./translations/pt.json": () => import("./pt-B4OG4ZiY.js"), "./translations/ru.json": () => import("./ru-DGiT4YZI.js"), "./translations/tr.json": () => import("./tr-CwzORNP2.js"), "./translations/zh.json": () => import("./zh-C1EjzGrj.js") }), `./translations/${locale}.json`, 3)
1428
+ })
1429
+ };
1430
+ export {
1431
+ DeleteNoteDialog as D,
1432
+ NOTE_CATEGORIES as N,
1433
+ NOTE_CATEGORY_META as a,
1434
+ NoteModal as b,
1435
+ buildContentManagerNoteLink as c,
1436
+ formatNoteAuthorDisplay as d,
1437
+ formatNoteTimestamp as e,
1438
+ filterNotesList as f,
1439
+ getTrad as g,
1440
+ index as i,
1441
+ useNotes as u
1442
+ };