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