@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.
- package/LICENSE +22 -0
- package/README.md +20 -2
- package/dist/admin/NotesHomePage-CIWSOu3n.js +457 -0
- package/dist/admin/NotesHomePage-Da5Eu2o9.cjs +475 -0
- package/dist/admin/ar-NJ-XJhAU.cjs +64 -0
- package/dist/admin/ar-uSBvoP4-.js +64 -0
- package/dist/admin/de-DlFY4yCr.js +64 -0
- package/dist/admin/de-d-M42GQs.cjs +64 -0
- package/dist/admin/en-C8MGI5Fp.cjs +64 -0
- package/dist/admin/en-DQUtJt3K.js +64 -0
- package/dist/admin/es-BZY_kUPd.cjs +64 -0
- package/dist/admin/es-DcTVGahO.js +64 -0
- package/dist/admin/fr-D6Ism7Y3.cjs +64 -0
- package/dist/admin/fr-QW7vMjBs.js +64 -0
- package/dist/admin/index-BD0jLH1y.cjs +1458 -0
- package/dist/admin/index-ClQtejgN.js +1442 -0
- package/dist/admin/index.cjs +4 -0
- package/dist/admin/index.js +4 -0
- package/dist/admin/index.mjs +4 -0
- package/dist/admin/it-DyBVYzjD.cjs +64 -0
- package/dist/admin/it-F24NTVne.js +64 -0
- package/dist/admin/pt-B4OG4ZiY.js +64 -0
- package/dist/admin/pt-BOHJueLP.cjs +64 -0
- package/dist/admin/ru-DGiT4YZI.js +64 -0
- package/dist/admin/ru-DhigZM8k.cjs +64 -0
- package/dist/admin/src/components/DeleteNoteDialog.d.ts +10 -0
- package/dist/admin/src/components/Initializer.d.ts +5 -0
- package/dist/admin/src/components/NoteListItem.d.ts +10 -0
- package/dist/admin/src/components/NoteModal.d.ts +18 -0
- package/dist/admin/src/components/NotesHubFilterBar.d.ts +14 -0
- package/dist/admin/src/components/NotesSidePanel.d.ts +13 -0
- package/dist/admin/src/components/PluginIcon.d.ts +2 -0
- package/dist/admin/src/hooks/useNotes.d.ts +12 -0
- package/dist/admin/src/index.d.ts +21 -0
- package/dist/admin/src/pages/NotesHomePage.d.ts +3 -0
- package/dist/admin/src/pluginId.d.ts +4 -0
- package/dist/admin/src/utils/getTrad.d.ts +2 -0
- package/dist/admin/src/utils/index.d.ts +1 -0
- package/dist/admin/tr-CNFZeutu.cjs +64 -0
- package/dist/admin/tr-CwzORNP2.js +64 -0
- package/dist/admin/zh-BriU4KiK.cjs +64 -0
- package/dist/admin/zh-C1EjzGrj.js +64 -0
- package/dist/package.json +3 -0
- package/dist/server/index.cjs +1212 -0
- package/dist/server/index.js +1212 -0
- package/dist/server/index.mjs +1212 -0
- package/dist/server/src/bootstrap.d.ts +5 -0
- package/dist/server/src/config/index.d.ts +8 -0
- package/dist/server/src/content-types/index.d.ts +70 -0
- package/dist/server/src/content-types/note/schema.json.d.ts +68 -0
- package/dist/server/src/controllers/index.d.ts +2 -0
- package/dist/server/src/controllers/notesController.d.ts +27 -0
- package/dist/server/src/destroy.d.ts +5 -0
- package/dist/server/src/index.d.ts +2 -0
- package/dist/server/src/register.d.ts +5 -0
- package/dist/server/src/routes/admin.d.ts +17 -0
- package/dist/server/src/routes/content-api.d.ts +12 -0
- package/dist/server/src/routes/index.d.ts +30 -0
- package/dist/server/src/services/index.d.ts +2 -0
- package/dist/server/src/services/notesQueryHelpers.d.ts +11 -0
- package/dist/server/src/services/notesService.d.ts +25 -0
- package/dist/shared/index.cjs +525 -0
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.js +525 -0
- package/dist/shared/index.mjs +525 -0
- 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
|
+
};
|