@takuhon/ui 0.11.0 → 0.13.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.
@@ -178,10 +178,185 @@ function CheckboxField({ label, checked, onChange }) {
178
178
  ] });
179
179
  }
180
180
 
181
- // src/admin/primitives/LocaleTabs.tsx
181
+ // src/admin/primitives/ImageField.tsx
182
182
  import { useId as useId3, useRef, useState } from "react";
183
- import styles6 from "../LocaleTabs.module-IEEC6Q27.module.css";
183
+
184
+ // src/admin/admin-labels.ts
185
+ var EN = {
186
+ "app.title": "takuhon admin",
187
+ "toolbar.label": "Editor actions",
188
+ "mode.label": "Editing mode",
189
+ "mode.form": "Form",
190
+ "mode.advanced": "Advanced (JSON)",
191
+ "action.save": "Save",
192
+ "action.reload": "Reload",
193
+ "action.export": "Export",
194
+ "action.import": "Import",
195
+ "action.add": "Add",
196
+ "action.remove": "Remove",
197
+ "action.moveUp": "Move up",
198
+ "action.moveDown": "Move down",
199
+ "status.saving": "Saving\u2026",
200
+ "status.saved": "Saved.",
201
+ "status.loading": "Loading\u2026",
202
+ "status.conflict": "The profile changed on the server since it was loaded. Reload, then reapply the edits.",
203
+ "status.invalid": "Some fields need attention before saving.",
204
+ "status.fixSummary": "Please fix the following:",
205
+ "status.error": "Something went wrong. Please try again.",
206
+ "status.imported": "Imported. Review the fields, then save to apply.",
207
+ "status.importInvalid": "The imported file is not a valid takuhon document.",
208
+ "status.uploading": "Uploading\u2026",
209
+ "status.uploadError": "Upload failed. Please try again.",
210
+ "section.profile": "Profile",
211
+ "section.about": "About",
212
+ "section.links": "Links",
213
+ "section.careers": "Experience",
214
+ "section.projects": "Projects",
215
+ "section.skills": "Skills",
216
+ "section.settings": "Settings",
217
+ "field.displayName": "Display name",
218
+ "field.tagline": "Tagline",
219
+ "field.bio": "Bio",
220
+ "field.avatarUrl": "Avatar URL",
221
+ "field.avatarUpload": "Upload image",
222
+ "field.avatarAlt": "Avatar alternative text",
223
+ "field.location.country": "Country",
224
+ "field.location.region": "Region",
225
+ "field.location.locality": "Locality",
226
+ "field.location.display": "Location (display)",
227
+ "field.link.type": "Type",
228
+ "field.link.url": "URL",
229
+ "field.link.label": "Label",
230
+ "field.link.iconUrl": "Icon URL",
231
+ "field.link.featured": "Featured",
232
+ "field.career.organization": "Organization",
233
+ "field.career.role": "Role",
234
+ "field.career.description": "Description",
235
+ "field.career.startDate": "Start",
236
+ "field.career.endDate": "End",
237
+ "field.career.isCurrent": "Current position",
238
+ "field.career.url": "URL",
239
+ "field.project.title": "Title",
240
+ "field.project.description": "Description",
241
+ "field.project.url": "URL",
242
+ "field.project.tags": "Tags",
243
+ "field.project.highlighted": "Highlighted",
244
+ "field.project.startDate": "Start",
245
+ "field.project.endDate": "End",
246
+ "field.skill.label": "Label",
247
+ "field.skill.category": "Category",
248
+ "field.settings.defaultLocale": "Default locale",
249
+ "field.settings.fallbackLocale": "Fallback locale",
250
+ "field.settings.availableLocales": "Available locales",
251
+ "field.settings.theme": "Theme",
252
+ "field.settings.showPoweredBy": 'Show the "Powered by takuhon" footer',
253
+ "field.settings.enableJsonLd": "Emit Schema.org JSON-LD",
254
+ "field.settings.enableApi": "Expose the public read API",
255
+ "field.settings.enableAnalytics": "Enable first-party analytics",
256
+ "item.link": "Link",
257
+ "item.career": "Position",
258
+ "item.project": "Project",
259
+ "item.skill": "Skill",
260
+ "empty.links": "No links yet.",
261
+ "empty.careers": "No positions yet.",
262
+ "empty.projects": "No projects yet.",
263
+ "empty.skills": "No skills yet.",
264
+ "hint.avatarNoUpload": "Paste an image URL. Uploading image files is not available yet.",
265
+ "hint.avatarUpload": "Paste an image URL, or upload a JPEG, PNG, WebP, or GIF.",
266
+ "hint.month": "Format: YYYY-MM (e.g. 2024-03).",
267
+ "hint.country": "ISO 3166-1 alpha-2 code, e.g. US.",
268
+ "hint.tags": "Comma-separated.",
269
+ "hint.locales": "Comma-separated BCP-47 tags, e.g. en, ja. Drives the language tabs above.",
270
+ "advanced.hint": "Edit the entire document as JSON. Edits apply only while the JSON is valid.",
271
+ "advanced.invalid": "The JSON is not a valid takuhon document:",
272
+ "option.none": "(none)"
273
+ };
274
+ var DICTIONARIES = {
275
+ en: EN
276
+ };
277
+ function getAdminLabel(key, locale = "en") {
278
+ const exact = DICTIONARIES[locale]?.[key];
279
+ if (exact !== void 0) return exact;
280
+ const base = locale.split("-")[0];
281
+ const baseMatch = base ? DICTIONARIES[base]?.[key] : void 0;
282
+ return baseMatch ?? EN[key];
283
+ }
284
+
285
+ // src/admin/primitives/ImageField.tsx
286
+ import styles6 from "../ImageField.module-CC3IBMDB.module.css";
287
+ import controls from "../controls.module-CMB7V22N.module.css";
184
288
  import { Fragment, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
289
+ var ACCEPT = "image/jpeg,image/png,image/webp,image/gif";
290
+ function ImageField({
291
+ label,
292
+ value,
293
+ onChange,
294
+ errors,
295
+ hint,
296
+ uploadAsset
297
+ }) {
298
+ const fileId = useId3();
299
+ const statusId = useId3();
300
+ const fileRef = useRef(null);
301
+ const [busy, setBusy] = useState(false);
302
+ const [uploadError, setUploadError] = useState(null);
303
+ const handleFile = (event) => {
304
+ const file = event.target.files?.[0];
305
+ if (!file || uploadAsset === void 0) return;
306
+ setBusy(true);
307
+ setUploadError(null);
308
+ uploadAsset(file).then((result) => {
309
+ if (result.status === "uploaded") {
310
+ onChange(result.url);
311
+ } else {
312
+ setUploadError(result.message ?? getAdminLabel("status.uploadError"));
313
+ }
314
+ }).catch(() => {
315
+ setUploadError(getAdminLabel("status.uploadError"));
316
+ }).finally(() => {
317
+ setBusy(false);
318
+ if (fileRef.current) fileRef.current.value = "";
319
+ });
320
+ };
321
+ return /* @__PURE__ */ jsx6(Field, { label, errors, hint, children: ({ controlId, describedBy, invalid }) => /* @__PURE__ */ jsxs3(Fragment, { children: [
322
+ /* @__PURE__ */ jsx6(
323
+ "input",
324
+ {
325
+ id: controlId,
326
+ className: controls.control,
327
+ type: "url",
328
+ value,
329
+ "aria-invalid": invalid || void 0,
330
+ "aria-describedby": describedBy,
331
+ onChange: (event) => {
332
+ onChange(event.target.value);
333
+ }
334
+ }
335
+ ),
336
+ uploadAsset !== void 0 ? /* @__PURE__ */ jsxs3("div", { className: styles6.uploadRow, children: [
337
+ /* @__PURE__ */ jsx6("label", { className: styles6.uploadLabel, htmlFor: fileId, children: getAdminLabel("field.avatarUpload") }),
338
+ /* @__PURE__ */ jsx6(
339
+ "input",
340
+ {
341
+ id: fileId,
342
+ ref: fileRef,
343
+ type: "file",
344
+ accept: ACCEPT,
345
+ disabled: busy,
346
+ "aria-describedby": statusId,
347
+ onChange: handleFile
348
+ }
349
+ ),
350
+ /* @__PURE__ */ jsx6("span", { className: styles6.uploadStatus, id: statusId, role: "status", "aria-live": "polite", children: busy ? getAdminLabel("status.uploading") : "" }),
351
+ uploadError !== null ? /* @__PURE__ */ jsx6("span", { className: styles6.uploadError, role: "alert", children: uploadError }) : null
352
+ ] }) : null
353
+ ] }) });
354
+ }
355
+
356
+ // src/admin/primitives/LocaleTabs.tsx
357
+ import { useId as useId4, useRef as useRef2, useState as useState2 } from "react";
358
+ import styles7 from "../LocaleTabs.module-IEEC6Q27.module.css";
359
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
185
360
  function LocaleTabs({
186
361
  label,
187
362
  value,
@@ -194,17 +369,17 @@ function LocaleTabs({
194
369
  pointer,
195
370
  formatLocale
196
371
  }) {
197
- const baseId = useId3();
198
- const hintId = useId3();
199
- const errorId = useId3();
200
- const [active, setActive] = useState(() => locales[0] ?? "");
201
- const tabRefs = useRef({});
372
+ const baseId = useId4();
373
+ const hintId = useId4();
374
+ const errorId = useId4();
375
+ const [active, setActive] = useState2(() => locales[0] ?? "");
376
+ const tabRefs = useRef2({});
202
377
  const format = formatLocale ?? ((locale) => locale);
203
378
  const labelId = `${baseId}-label`;
204
379
  if (locales.length === 0) {
205
- return /* @__PURE__ */ jsxs3("div", { className: styles6.group, role: "group", "aria-labelledby": labelId, children: [
206
- /* @__PURE__ */ jsx6("span", { className: styles6.groupLabel, id: labelId, children: label }),
207
- /* @__PURE__ */ jsx6("p", { className: styles6.hint, children: "No locales are configured yet; add one under Settings." })
380
+ return /* @__PURE__ */ jsxs4("div", { className: styles7.group, role: "group", "aria-labelledby": labelId, children: [
381
+ /* @__PURE__ */ jsx7("span", { className: styles7.groupLabel, id: labelId, children: label }),
382
+ /* @__PURE__ */ jsx7("p", { className: styles7.hint, children: "No locales are configured yet; add one under Settings." })
208
383
  ] });
209
384
  }
210
385
  const activeLocale = locales.includes(active) ? active : locales[0];
@@ -249,16 +424,16 @@ function LocaleTabs({
249
424
  }
250
425
  };
251
426
  const controlName = `${label} (${format(activeLocale)})`;
252
- return /* @__PURE__ */ jsxs3("div", { className: styles6.group, role: "group", "aria-labelledby": labelId, children: [
253
- /* @__PURE__ */ jsxs3("span", { className: styles6.groupLabel, id: labelId, children: [
427
+ return /* @__PURE__ */ jsxs4("div", { className: styles7.group, role: "group", "aria-labelledby": labelId, children: [
428
+ /* @__PURE__ */ jsxs4("span", { className: styles7.groupLabel, id: labelId, children: [
254
429
  label,
255
- required ? /* @__PURE__ */ jsx6("span", { "aria-hidden": "true", children: " *" }) : null
430
+ required ? /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", children: " *" }) : null
256
431
  ] }),
257
- hint ? /* @__PURE__ */ jsx6("p", { className: styles6.hint, id: hintId, children: hint }) : null,
258
- /* @__PURE__ */ jsx6("div", { className: styles6.tablist, role: "tablist", "aria-label": label, children: locales.map((locale) => {
432
+ hint ? /* @__PURE__ */ jsx7("p", { className: styles7.hint, id: hintId, children: hint }) : null,
433
+ /* @__PURE__ */ jsx7("div", { className: styles7.tablist, role: "tablist", "aria-label": label, children: locales.map((locale) => {
259
434
  const selected = locale === activeLocale;
260
435
  const tabHasErrors = pointer ? hasErrorsUnder(errors, `${pointer}/${locale}`) : false;
261
- return /* @__PURE__ */ jsxs3(
436
+ return /* @__PURE__ */ jsxs4(
262
437
  "button",
263
438
  {
264
439
  ref: (element) => {
@@ -270,37 +445,37 @@ function LocaleTabs({
270
445
  "aria-selected": selected,
271
446
  "aria-controls": `${baseId}-panel`,
272
447
  tabIndex: selected ? 0 : -1,
273
- className: `${styles6.tab} ${selected ? styles6.tabActive : ""}`,
448
+ className: `${styles7.tab} ${selected ? styles7.tabActive : ""}`,
274
449
  onClick: () => {
275
450
  setActive(locale);
276
451
  },
277
452
  onKeyDown: onTabKeyDown,
278
453
  children: [
279
454
  format(locale),
280
- tabHasErrors ? /* @__PURE__ */ jsxs3(Fragment, { children: [
281
- /* @__PURE__ */ jsxs3("span", { className: styles6.tabError, "aria-hidden": "true", children: [
455
+ tabHasErrors ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
456
+ /* @__PURE__ */ jsxs4("span", { className: styles7.tabError, "aria-hidden": "true", children: [
282
457
  " ",
283
458
  "\u25CF"
284
459
  ] }),
285
- /* @__PURE__ */ jsx6("span", { className: styles6.srOnly, children: " (has errors)" })
460
+ /* @__PURE__ */ jsx7("span", { className: styles7.srOnly, children: " (has errors)" })
286
461
  ] }) : null
287
462
  ]
288
463
  },
289
464
  locale
290
465
  );
291
466
  }) }),
292
- /* @__PURE__ */ jsxs3(
467
+ /* @__PURE__ */ jsxs4(
293
468
  "div",
294
469
  {
295
470
  role: "tabpanel",
296
471
  id: `${baseId}-panel`,
297
472
  "aria-labelledby": `${baseId}-tab-${activeLocale}`,
298
- className: styles6.panel,
473
+ className: styles7.panel,
299
474
  children: [
300
- multiline ? /* @__PURE__ */ jsx6(
475
+ multiline ? /* @__PURE__ */ jsx7(
301
476
  "textarea",
302
477
  {
303
- className: `${styles6.control} ${styles6.textarea}`,
478
+ className: `${styles7.control} ${styles7.textarea}`,
304
479
  value: text,
305
480
  rows: 4,
306
481
  "aria-label": controlName,
@@ -311,10 +486,10 @@ function LocaleTabs({
311
486
  setText(event.target.value);
312
487
  }
313
488
  }
314
- ) : /* @__PURE__ */ jsx6(
489
+ ) : /* @__PURE__ */ jsx7(
315
490
  "input",
316
491
  {
317
- className: styles6.control,
492
+ className: styles7.control,
318
493
  type: "text",
319
494
  value: text,
320
495
  "aria-label": controlName,
@@ -326,7 +501,7 @@ function LocaleTabs({
326
501
  }
327
502
  }
328
503
  ),
329
- hasErrors ? /* @__PURE__ */ jsx6("ul", { className: styles6.errors, id: errorId, children: shownErrors.map((message, i) => /* @__PURE__ */ jsx6("li", { children: message }, i)) }) : null
504
+ hasErrors ? /* @__PURE__ */ jsx7("ul", { className: styles7.errors, id: errorId, children: shownErrors.map((message, i) => /* @__PURE__ */ jsx7("li", { children: message }, i)) }) : null
330
505
  ]
331
506
  }
332
507
  )
@@ -334,8 +509,8 @@ function LocaleTabs({
334
509
  }
335
510
 
336
511
  // src/admin/primitives/Repeater.tsx
337
- import styles7 from "../Repeater.module-MWSEKS4G.module.css";
338
- import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
512
+ import styles8 from "../Repeater.module-MWSEKS4G.module.css";
513
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
339
514
  function Repeater({
340
515
  legend,
341
516
  items,
@@ -367,20 +542,20 @@ function Repeater({
367
542
  copy.splice(to, 0, moved);
368
543
  onChange(copy);
369
544
  };
370
- return /* @__PURE__ */ jsxs4("fieldset", { className: styles7.fieldset, children: [
371
- /* @__PURE__ */ jsx7("legend", { className: styles7.legend, children: legend }),
372
- items.length === 0 && emptyHint ? /* @__PURE__ */ jsx7("p", { className: styles7.empty, children: emptyHint }) : null,
373
- /* @__PURE__ */ jsx7("ol", { className: styles7.list, children: items.map((item, index) => {
545
+ return /* @__PURE__ */ jsxs5("fieldset", { className: styles8.fieldset, children: [
546
+ /* @__PURE__ */ jsx8("legend", { className: styles8.legend, children: legend }),
547
+ items.length === 0 && emptyHint ? /* @__PURE__ */ jsx8("p", { className: styles8.empty, children: emptyHint }) : null,
548
+ /* @__PURE__ */ jsx8("ol", { className: styles8.list, children: items.map((item, index) => {
374
549
  const caption = itemLabel(item, index);
375
- return /* @__PURE__ */ jsx7("li", { className: styles7.item, children: /* @__PURE__ */ jsxs4("div", { role: "group", "aria-label": caption, children: [
376
- /* @__PURE__ */ jsxs4("div", { className: styles7.itemHeader, children: [
377
- /* @__PURE__ */ jsx7("span", { className: styles7.itemCaption, children: caption }),
378
- /* @__PURE__ */ jsxs4("div", { className: styles7.itemActions, children: [
379
- /* @__PURE__ */ jsx7(
550
+ return /* @__PURE__ */ jsx8("li", { className: styles8.item, children: /* @__PURE__ */ jsxs5("div", { role: "group", "aria-label": caption, children: [
551
+ /* @__PURE__ */ jsxs5("div", { className: styles8.itemHeader, children: [
552
+ /* @__PURE__ */ jsx8("span", { className: styles8.itemCaption, children: caption }),
553
+ /* @__PURE__ */ jsxs5("div", { className: styles8.itemActions, children: [
554
+ /* @__PURE__ */ jsx8(
380
555
  "button",
381
556
  {
382
557
  type: "button",
383
- className: styles7.iconButton,
558
+ className: styles8.iconButton,
384
559
  "aria-label": `${moveUpLabel}: ${caption}`,
385
560
  disabled: index === 0,
386
561
  onClick: () => {
@@ -389,11 +564,11 @@ function Repeater({
389
564
  children: "\u2191"
390
565
  }
391
566
  ),
392
- /* @__PURE__ */ jsx7(
567
+ /* @__PURE__ */ jsx8(
393
568
  "button",
394
569
  {
395
570
  type: "button",
396
- className: styles7.iconButton,
571
+ className: styles8.iconButton,
397
572
  "aria-label": `${moveDownLabel}: ${caption}`,
398
573
  disabled: index === items.length - 1,
399
574
  onClick: () => {
@@ -402,11 +577,11 @@ function Repeater({
402
577
  children: "\u2193"
403
578
  }
404
579
  ),
405
- /* @__PURE__ */ jsx7(
580
+ /* @__PURE__ */ jsx8(
406
581
  "button",
407
582
  {
408
583
  type: "button",
409
- className: styles7.removeButton,
584
+ className: styles8.removeButton,
410
585
  "aria-label": `${removeLabel}: ${caption}`,
411
586
  onClick: () => {
412
587
  remove(index);
@@ -416,7 +591,7 @@ function Repeater({
416
591
  )
417
592
  ] })
418
593
  ] }),
419
- /* @__PURE__ */ jsx7("div", { className: styles7.itemBody, children: renderItem(
594
+ /* @__PURE__ */ jsx8("div", { className: styles8.itemBody, children: renderItem(
420
595
  item,
421
596
  (next) => {
422
597
  update(index, next);
@@ -425,11 +600,11 @@ function Repeater({
425
600
  ) })
426
601
  ] }) }, key(item, index));
427
602
  }) }),
428
- /* @__PURE__ */ jsx7(
603
+ /* @__PURE__ */ jsx8(
429
604
  "button",
430
605
  {
431
606
  type: "button",
432
- className: styles7.addButton,
607
+ className: styles8.addButton,
433
608
  onClick: () => {
434
609
  onChange([...items, createItem()]);
435
610
  },
@@ -439,106 +614,9 @@ function Repeater({
439
614
  ] });
440
615
  }
441
616
 
442
- // src/admin/admin-labels.ts
443
- var EN = {
444
- "app.title": "takuhon admin",
445
- "toolbar.label": "Editor actions",
446
- "mode.label": "Editing mode",
447
- "mode.form": "Form",
448
- "mode.advanced": "Advanced (JSON)",
449
- "action.save": "Save",
450
- "action.reload": "Reload",
451
- "action.export": "Export",
452
- "action.import": "Import",
453
- "action.add": "Add",
454
- "action.remove": "Remove",
455
- "action.moveUp": "Move up",
456
- "action.moveDown": "Move down",
457
- "status.saving": "Saving\u2026",
458
- "status.saved": "Saved.",
459
- "status.loading": "Loading\u2026",
460
- "status.conflict": "The profile changed on the server since it was loaded. Reload, then reapply the edits.",
461
- "status.invalid": "Some fields need attention before saving.",
462
- "status.fixSummary": "Please fix the following:",
463
- "status.error": "Something went wrong. Please try again.",
464
- "status.imported": "Imported. Review the fields, then save to apply.",
465
- "status.importInvalid": "The imported file is not a valid takuhon document.",
466
- "section.profile": "Profile",
467
- "section.about": "About",
468
- "section.links": "Links",
469
- "section.careers": "Experience",
470
- "section.projects": "Projects",
471
- "section.skills": "Skills",
472
- "section.settings": "Settings",
473
- "field.displayName": "Display name",
474
- "field.tagline": "Tagline",
475
- "field.bio": "Bio",
476
- "field.avatarUrl": "Avatar URL",
477
- "field.avatarAlt": "Avatar alternative text",
478
- "field.location.country": "Country",
479
- "field.location.region": "Region",
480
- "field.location.locality": "Locality",
481
- "field.location.display": "Location (display)",
482
- "field.link.type": "Type",
483
- "field.link.url": "URL",
484
- "field.link.label": "Label",
485
- "field.link.iconUrl": "Icon URL",
486
- "field.link.featured": "Featured",
487
- "field.career.organization": "Organization",
488
- "field.career.role": "Role",
489
- "field.career.description": "Description",
490
- "field.career.startDate": "Start",
491
- "field.career.endDate": "End",
492
- "field.career.isCurrent": "Current position",
493
- "field.career.url": "URL",
494
- "field.project.title": "Title",
495
- "field.project.description": "Description",
496
- "field.project.url": "URL",
497
- "field.project.tags": "Tags",
498
- "field.project.highlighted": "Highlighted",
499
- "field.project.startDate": "Start",
500
- "field.project.endDate": "End",
501
- "field.skill.label": "Label",
502
- "field.skill.category": "Category",
503
- "field.settings.defaultLocale": "Default locale",
504
- "field.settings.fallbackLocale": "Fallback locale",
505
- "field.settings.availableLocales": "Available locales",
506
- "field.settings.theme": "Theme",
507
- "field.settings.showPoweredBy": 'Show the "Powered by takuhon" footer',
508
- "field.settings.enableJsonLd": "Emit Schema.org JSON-LD",
509
- "field.settings.enableApi": "Expose the public read API",
510
- "field.settings.enableAnalytics": "Enable first-party analytics",
511
- "item.link": "Link",
512
- "item.career": "Position",
513
- "item.project": "Project",
514
- "item.skill": "Skill",
515
- "empty.links": "No links yet.",
516
- "empty.careers": "No positions yet.",
517
- "empty.projects": "No projects yet.",
518
- "empty.skills": "No skills yet.",
519
- "hint.avatarNoUpload": "Paste an image URL. Uploading image files is not available yet.",
520
- "hint.month": "Format: YYYY-MM (e.g. 2024-03).",
521
- "hint.country": "ISO 3166-1 alpha-2 code, e.g. US.",
522
- "hint.tags": "Comma-separated.",
523
- "hint.locales": "Comma-separated BCP-47 tags, e.g. en, ja. Drives the language tabs above.",
524
- "advanced.hint": "Edit the entire document as JSON. Edits apply only while the JSON is valid.",
525
- "advanced.invalid": "The JSON is not a valid takuhon document:",
526
- "option.none": "(none)"
527
- };
528
- var DICTIONARIES = {
529
- en: EN
530
- };
531
- function getAdminLabel(key, locale = "en") {
532
- const exact = DICTIONARIES[locale]?.[key];
533
- if (exact !== void 0) return exact;
534
- const base = locale.split("-")[0];
535
- const baseMatch = base ? DICTIONARIES[base]?.[key] : void 0;
536
- return baseMatch ?? EN[key];
537
- }
538
-
539
617
  // src/admin/sections/ProfileForm.tsx
540
- import styles8 from "../sections.module-ZVBKZHDE.module.css";
541
- import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
618
+ import styles9 from "../sections.module-ZVBKZHDE.module.css";
619
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
542
620
  var POINTER = "/profile";
543
621
  function isEmptyRecord(record) {
544
622
  return !record || Object.keys(record).length === 0;
@@ -548,7 +626,8 @@ function ProfileForm({
548
626
  onChange,
549
627
  locales,
550
628
  errors = NO_FIELD_ERRORS,
551
- formatLocale
629
+ formatLocale,
630
+ uploadAsset
552
631
  }) {
553
632
  const updateAvatar = (patch) => {
554
633
  const merged = { url: "", ...value.avatar, ...patch };
@@ -561,9 +640,9 @@ function ProfileForm({
561
640
  onChange({ ...value, location: empty ? void 0 : merged });
562
641
  };
563
642
  const headingId = "admin-section-profile";
564
- return /* @__PURE__ */ jsxs5("section", { className: styles8.section, "aria-labelledby": headingId, children: [
565
- /* @__PURE__ */ jsx8("h2", { className: styles8.heading, id: headingId, children: getAdminLabel("section.profile") }),
566
- /* @__PURE__ */ jsx8(
643
+ return /* @__PURE__ */ jsxs6("section", { className: styles9.section, "aria-labelledby": headingId, children: [
644
+ /* @__PURE__ */ jsx9("h2", { className: styles9.heading, id: headingId, children: getAdminLabel("section.profile") }),
645
+ /* @__PURE__ */ jsx9(
567
646
  LocaleTabs,
568
647
  {
569
648
  label: getAdminLabel("field.displayName"),
@@ -578,7 +657,7 @@ function ProfileForm({
578
657
  formatLocale
579
658
  }
580
659
  ),
581
- /* @__PURE__ */ jsx8(
660
+ /* @__PURE__ */ jsx9(
582
661
  LocaleTabs,
583
662
  {
584
663
  label: getAdminLabel("field.tagline"),
@@ -592,8 +671,8 @@ function ProfileForm({
592
671
  formatLocale
593
672
  }
594
673
  ),
595
- /* @__PURE__ */ jsx8("h3", { className: styles8.subheading, children: getAdminLabel("section.about") }),
596
- /* @__PURE__ */ jsx8(
674
+ /* @__PURE__ */ jsx9("h3", { className: styles9.subheading, children: getAdminLabel("section.about") }),
675
+ /* @__PURE__ */ jsx9(
597
676
  LocaleTabs,
598
677
  {
599
678
  label: getAdminLabel("field.bio"),
@@ -608,20 +687,20 @@ function ProfileForm({
608
687
  formatLocale
609
688
  }
610
689
  ),
611
- /* @__PURE__ */ jsx8(
612
- TextField,
690
+ /* @__PURE__ */ jsx9(
691
+ ImageField,
613
692
  {
614
693
  label: getAdminLabel("field.avatarUrl"),
615
- type: "url",
616
694
  value: value.avatar?.url ?? "",
617
695
  onChange: (url) => {
618
696
  updateAvatar({ url });
619
697
  },
620
- hint: getAdminLabel("hint.avatarNoUpload"),
621
- errors: errorsAt(errors, `${POINTER}/avatar/url`)
698
+ hint: getAdminLabel(uploadAsset ? "hint.avatarUpload" : "hint.avatarNoUpload"),
699
+ errors: errorsAt(errors, `${POINTER}/avatar/url`),
700
+ uploadAsset
622
701
  }
623
702
  ),
624
- /* @__PURE__ */ jsx8(
703
+ /* @__PURE__ */ jsx9(
625
704
  LocaleTabs,
626
705
  {
627
706
  label: getAdminLabel("field.avatarAlt"),
@@ -635,7 +714,7 @@ function ProfileForm({
635
714
  formatLocale
636
715
  }
637
716
  ),
638
- /* @__PURE__ */ jsx8(
717
+ /* @__PURE__ */ jsx9(
639
718
  TextField,
640
719
  {
641
720
  label: getAdminLabel("field.location.country"),
@@ -647,7 +726,7 @@ function ProfileForm({
647
726
  errors: errorsAt(errors, `${POINTER}/location/country`)
648
727
  }
649
728
  ),
650
- /* @__PURE__ */ jsx8(
729
+ /* @__PURE__ */ jsx9(
651
730
  TextField,
652
731
  {
653
732
  label: getAdminLabel("field.location.region"),
@@ -658,7 +737,7 @@ function ProfileForm({
658
737
  errors: errorsAt(errors, `${POINTER}/location/region`)
659
738
  }
660
739
  ),
661
- /* @__PURE__ */ jsx8(
740
+ /* @__PURE__ */ jsx9(
662
741
  LocaleTabs,
663
742
  {
664
743
  label: getAdminLabel("field.location.locality"),
@@ -685,7 +764,7 @@ function makeId(prefix, taken) {
685
764
  }
686
765
 
687
766
  // src/admin/sections/LinksForm.tsx
688
- import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
767
+ import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
689
768
  var LINK_TYPES = [
690
769
  "website",
691
770
  "blog",
@@ -737,7 +816,7 @@ function LinksForm({
737
816
  errors = NO_FIELD_ERRORS,
738
817
  formatLocale
739
818
  }) {
740
- return /* @__PURE__ */ jsx9(
819
+ return /* @__PURE__ */ jsx10(
741
820
  Repeater,
742
821
  {
743
822
  legend: getAdminLabel("section.links"),
@@ -760,8 +839,8 @@ function LinksForm({
760
839
  emptyHint: getAdminLabel("empty.links"),
761
840
  renderItem: (link, update, index) => {
762
841
  const at = `/links/${String(index)}`;
763
- return /* @__PURE__ */ jsxs6(Fragment2, { children: [
764
- /* @__PURE__ */ jsx9(
842
+ return /* @__PURE__ */ jsxs7(Fragment3, { children: [
843
+ /* @__PURE__ */ jsx10(
765
844
  SelectField,
766
845
  {
767
846
  label: getAdminLabel("field.link.type"),
@@ -773,7 +852,7 @@ function LinksForm({
773
852
  errors: errorsAt(errors, `${at}/type`)
774
853
  }
775
854
  ),
776
- /* @__PURE__ */ jsx9(
855
+ /* @__PURE__ */ jsx10(
777
856
  TextField,
778
857
  {
779
858
  label: getAdminLabel("field.link.url"),
@@ -786,7 +865,7 @@ function LinksForm({
786
865
  errors: errorsAt(errors, `${at}/url`)
787
866
  }
788
867
  ),
789
- /* @__PURE__ */ jsx9(
868
+ /* @__PURE__ */ jsx10(
790
869
  LocaleTabs,
791
870
  {
792
871
  label: getAdminLabel("field.link.label"),
@@ -800,7 +879,7 @@ function LinksForm({
800
879
  formatLocale
801
880
  }
802
881
  ),
803
- /* @__PURE__ */ jsx9(
882
+ /* @__PURE__ */ jsx10(
804
883
  TextField,
805
884
  {
806
885
  label: getAdminLabel("field.link.iconUrl"),
@@ -813,7 +892,7 @@ function LinksForm({
813
892
  errors: errorsAt(errors, `${at}/iconUrl`)
814
893
  }
815
894
  ),
816
- /* @__PURE__ */ jsx9(
895
+ /* @__PURE__ */ jsx10(
817
896
  CheckboxField,
818
897
  {
819
898
  label: getAdminLabel("field.link.featured"),
@@ -840,7 +919,7 @@ function firstLocalized(record, locales) {
840
919
  }
841
920
 
842
921
  // src/admin/sections/CareersForm.tsx
843
- import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
922
+ import { Fragment as Fragment4, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
844
923
  function CareersForm({
845
924
  value,
846
925
  onChange,
@@ -848,7 +927,7 @@ function CareersForm({
848
927
  errors = NO_FIELD_ERRORS,
849
928
  formatLocale
850
929
  }) {
851
- return /* @__PURE__ */ jsx10(
930
+ return /* @__PURE__ */ jsx11(
852
931
  Repeater,
853
932
  {
854
933
  legend: getAdminLabel("section.careers"),
@@ -872,8 +951,8 @@ function CareersForm({
872
951
  emptyHint: getAdminLabel("empty.careers"),
873
952
  renderItem: (career, update, index) => {
874
953
  const at = `/careers/${String(index)}`;
875
- return /* @__PURE__ */ jsxs7(Fragment3, { children: [
876
- /* @__PURE__ */ jsx10(
954
+ return /* @__PURE__ */ jsxs8(Fragment4, { children: [
955
+ /* @__PURE__ */ jsx11(
877
956
  LocaleTabs,
878
957
  {
879
958
  label: getAdminLabel("field.career.organization"),
@@ -888,7 +967,7 @@ function CareersForm({
888
967
  formatLocale
889
968
  }
890
969
  ),
891
- /* @__PURE__ */ jsx10(
970
+ /* @__PURE__ */ jsx11(
892
971
  LocaleTabs,
893
972
  {
894
973
  label: getAdminLabel("field.career.role"),
@@ -903,7 +982,7 @@ function CareersForm({
903
982
  formatLocale
904
983
  }
905
984
  ),
906
- /* @__PURE__ */ jsx10(
985
+ /* @__PURE__ */ jsx11(
907
986
  TextField,
908
987
  {
909
988
  label: getAdminLabel("field.career.startDate"),
@@ -917,7 +996,7 @@ function CareersForm({
917
996
  errors: errorsAt(errors, `${at}/startDate`)
918
997
  }
919
998
  ),
920
- /* @__PURE__ */ jsx10(
999
+ /* @__PURE__ */ jsx11(
921
1000
  TextField,
922
1001
  {
923
1002
  label: getAdminLabel("field.career.endDate"),
@@ -930,7 +1009,7 @@ function CareersForm({
930
1009
  errors: errorsAt(errors, `${at}/endDate`)
931
1010
  }
932
1011
  ),
933
- /* @__PURE__ */ jsx10(
1012
+ /* @__PURE__ */ jsx11(
934
1013
  CheckboxField,
935
1014
  {
936
1015
  label: getAdminLabel("field.career.isCurrent"),
@@ -940,7 +1019,7 @@ function CareersForm({
940
1019
  }
941
1020
  }
942
1021
  ),
943
- /* @__PURE__ */ jsx10(
1022
+ /* @__PURE__ */ jsx11(
944
1023
  LocaleTabs,
945
1024
  {
946
1025
  label: getAdminLabel("field.career.description"),
@@ -955,7 +1034,7 @@ function CareersForm({
955
1034
  formatLocale
956
1035
  }
957
1036
  ),
958
- /* @__PURE__ */ jsx10(
1037
+ /* @__PURE__ */ jsx11(
959
1038
  TextField,
960
1039
  {
961
1040
  label: getAdminLabel("field.career.url"),
@@ -974,7 +1053,7 @@ function CareersForm({
974
1053
  }
975
1054
 
976
1055
  // src/admin/sections/ProjectsForm.tsx
977
- import { Fragment as Fragment4, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
1056
+ import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
978
1057
  function parseTags(input) {
979
1058
  const tags = input.split(",").map((tag) => tag.trim()).filter((tag) => tag !== "");
980
1059
  return tags.length > 0 ? tags : void 0;
@@ -986,7 +1065,7 @@ function ProjectsForm({
986
1065
  errors = NO_FIELD_ERRORS,
987
1066
  formatLocale
988
1067
  }) {
989
- return /* @__PURE__ */ jsx11(
1068
+ return /* @__PURE__ */ jsx12(
990
1069
  Repeater,
991
1070
  {
992
1071
  legend: getAdminLabel("section.projects"),
@@ -1008,8 +1087,8 @@ function ProjectsForm({
1008
1087
  emptyHint: getAdminLabel("empty.projects"),
1009
1088
  renderItem: (project, update, index) => {
1010
1089
  const at = `/projects/${String(index)}`;
1011
- return /* @__PURE__ */ jsxs8(Fragment4, { children: [
1012
- /* @__PURE__ */ jsx11(
1090
+ return /* @__PURE__ */ jsxs9(Fragment5, { children: [
1091
+ /* @__PURE__ */ jsx12(
1013
1092
  LocaleTabs,
1014
1093
  {
1015
1094
  label: getAdminLabel("field.project.title"),
@@ -1024,7 +1103,7 @@ function ProjectsForm({
1024
1103
  formatLocale
1025
1104
  }
1026
1105
  ),
1027
- /* @__PURE__ */ jsx11(
1106
+ /* @__PURE__ */ jsx12(
1028
1107
  LocaleTabs,
1029
1108
  {
1030
1109
  label: getAdminLabel("field.project.description"),
@@ -1039,7 +1118,7 @@ function ProjectsForm({
1039
1118
  formatLocale
1040
1119
  }
1041
1120
  ),
1042
- /* @__PURE__ */ jsx11(
1121
+ /* @__PURE__ */ jsx12(
1043
1122
  TextField,
1044
1123
  {
1045
1124
  label: getAdminLabel("field.project.url"),
@@ -1051,7 +1130,7 @@ function ProjectsForm({
1051
1130
  errors: errorsAt(errors, `${at}/url`)
1052
1131
  }
1053
1132
  ),
1054
- /* @__PURE__ */ jsx11(
1133
+ /* @__PURE__ */ jsx12(
1055
1134
  TextField,
1056
1135
  {
1057
1136
  label: getAdminLabel("field.project.tags"),
@@ -1063,7 +1142,7 @@ function ProjectsForm({
1063
1142
  errors: collectErrorsUnder(errors, `${at}/tags`)
1064
1143
  }
1065
1144
  ),
1066
- /* @__PURE__ */ jsx11(
1145
+ /* @__PURE__ */ jsx12(
1067
1146
  TextField,
1068
1147
  {
1069
1148
  label: getAdminLabel("field.project.startDate"),
@@ -1076,7 +1155,7 @@ function ProjectsForm({
1076
1155
  errors: errorsAt(errors, `${at}/startDate`)
1077
1156
  }
1078
1157
  ),
1079
- /* @__PURE__ */ jsx11(
1158
+ /* @__PURE__ */ jsx12(
1080
1159
  TextField,
1081
1160
  {
1082
1161
  label: getAdminLabel("field.project.endDate"),
@@ -1089,7 +1168,7 @@ function ProjectsForm({
1089
1168
  errors: errorsAt(errors, `${at}/endDate`)
1090
1169
  }
1091
1170
  ),
1092
- /* @__PURE__ */ jsx11(
1171
+ /* @__PURE__ */ jsx12(
1093
1172
  CheckboxField,
1094
1173
  {
1095
1174
  label: getAdminLabel("field.project.highlighted"),
@@ -1106,13 +1185,13 @@ function ProjectsForm({
1106
1185
  }
1107
1186
 
1108
1187
  // src/admin/sections/SkillsForm.tsx
1109
- import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1188
+ import { Fragment as Fragment6, jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
1110
1189
  function SkillsForm({
1111
1190
  value,
1112
1191
  onChange,
1113
1192
  errors = NO_FIELD_ERRORS
1114
1193
  }) {
1115
- return /* @__PURE__ */ jsx12(
1194
+ return /* @__PURE__ */ jsx13(
1116
1195
  Repeater,
1117
1196
  {
1118
1197
  legend: getAdminLabel("section.skills"),
@@ -1134,8 +1213,8 @@ function SkillsForm({
1134
1213
  emptyHint: getAdminLabel("empty.skills"),
1135
1214
  renderItem: (skill, update, index) => {
1136
1215
  const at = `/skills/${String(index)}`;
1137
- return /* @__PURE__ */ jsxs9(Fragment5, { children: [
1138
- /* @__PURE__ */ jsx12(
1216
+ return /* @__PURE__ */ jsxs10(Fragment6, { children: [
1217
+ /* @__PURE__ */ jsx13(
1139
1218
  TextField,
1140
1219
  {
1141
1220
  label: getAdminLabel("field.skill.label"),
@@ -1147,7 +1226,7 @@ function SkillsForm({
1147
1226
  errors: errorsAt(errors, `${at}/label`)
1148
1227
  }
1149
1228
  ),
1150
- /* @__PURE__ */ jsx12(
1229
+ /* @__PURE__ */ jsx13(
1151
1230
  TextField,
1152
1231
  {
1153
1232
  label: getAdminLabel("field.skill.category"),
@@ -1165,8 +1244,8 @@ function SkillsForm({
1165
1244
  }
1166
1245
 
1167
1246
  // src/admin/sections/SettingsForm.tsx
1168
- import styles9 from "../sections.module-ZVBKZHDE.module.css";
1169
- import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
1247
+ import styles10 from "../sections.module-ZVBKZHDE.module.css";
1248
+ import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
1170
1249
  function parseLocales(input) {
1171
1250
  return input.split(",").map((tag) => tag.trim()).filter((tag) => tag !== "");
1172
1251
  }
@@ -1182,9 +1261,9 @@ function SettingsForm({
1182
1261
  label: format(locale)
1183
1262
  }));
1184
1263
  const headingId = "admin-section-settings";
1185
- return /* @__PURE__ */ jsxs10("section", { className: styles9.section, "aria-labelledby": headingId, children: [
1186
- /* @__PURE__ */ jsx13("h2", { className: styles9.heading, id: headingId, children: getAdminLabel("section.settings") }),
1187
- /* @__PURE__ */ jsx13(
1264
+ return /* @__PURE__ */ jsxs11("section", { className: styles10.section, "aria-labelledby": headingId, children: [
1265
+ /* @__PURE__ */ jsx14("h2", { className: styles10.heading, id: headingId, children: getAdminLabel("section.settings") }),
1266
+ /* @__PURE__ */ jsx14(
1188
1267
  TextField,
1189
1268
  {
1190
1269
  label: getAdminLabel("field.settings.availableLocales"),
@@ -1197,7 +1276,7 @@ function SettingsForm({
1197
1276
  errors: collectErrorsUnder(errors, "/settings/availableLocales")
1198
1277
  }
1199
1278
  ),
1200
- /* @__PURE__ */ jsx13(
1279
+ /* @__PURE__ */ jsx14(
1201
1280
  SelectField,
1202
1281
  {
1203
1282
  label: getAdminLabel("field.settings.defaultLocale"),
@@ -1210,7 +1289,7 @@ function SettingsForm({
1210
1289
  errors: errorsAt(errors, "/settings/defaultLocale")
1211
1290
  }
1212
1291
  ),
1213
- /* @__PURE__ */ jsx13(
1292
+ /* @__PURE__ */ jsx14(
1214
1293
  SelectField,
1215
1294
  {
1216
1295
  label: getAdminLabel("field.settings.fallbackLocale"),
@@ -1222,7 +1301,7 @@ function SettingsForm({
1222
1301
  errors: errorsAt(errors, "/settings/fallbackLocale")
1223
1302
  }
1224
1303
  ),
1225
- /* @__PURE__ */ jsx13(
1304
+ /* @__PURE__ */ jsx14(
1226
1305
  TextField,
1227
1306
  {
1228
1307
  label: getAdminLabel("field.settings.theme"),
@@ -1233,7 +1312,7 @@ function SettingsForm({
1233
1312
  errors: errorsAt(errors, "/settings/theme")
1234
1313
  }
1235
1314
  ),
1236
- /* @__PURE__ */ jsx13(
1315
+ /* @__PURE__ */ jsx14(
1237
1316
  CheckboxField,
1238
1317
  {
1239
1318
  label: getAdminLabel("field.settings.showPoweredBy"),
@@ -1243,7 +1322,7 @@ function SettingsForm({
1243
1322
  }
1244
1323
  }
1245
1324
  ),
1246
- /* @__PURE__ */ jsx13(
1325
+ /* @__PURE__ */ jsx14(
1247
1326
  CheckboxField,
1248
1327
  {
1249
1328
  label: getAdminLabel("field.settings.enableJsonLd"),
@@ -1253,7 +1332,7 @@ function SettingsForm({
1253
1332
  }
1254
1333
  }
1255
1334
  ),
1256
- /* @__PURE__ */ jsx13(
1335
+ /* @__PURE__ */ jsx14(
1257
1336
  CheckboxField,
1258
1337
  {
1259
1338
  label: getAdminLabel("field.settings.enableApi"),
@@ -1263,7 +1342,7 @@ function SettingsForm({
1263
1342
  }
1264
1343
  }
1265
1344
  ),
1266
- /* @__PURE__ */ jsx13(
1345
+ /* @__PURE__ */ jsx14(
1267
1346
  CheckboxField,
1268
1347
  {
1269
1348
  label: getAdminLabel("field.settings.enableAnalytics"),
@@ -1278,15 +1357,15 @@ function SettingsForm({
1278
1357
 
1279
1358
  // src/admin/RawJsonEditor.tsx
1280
1359
  import { validate } from "@takuhon/core";
1281
- import { useId as useId4, useState as useState2 } from "react";
1282
- import styles10 from "../RawJsonEditor.module-NGGM3IBY.module.css";
1283
- import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
1360
+ import { useId as useId5, useState as useState3 } from "react";
1361
+ import styles11 from "../RawJsonEditor.module-NGGM3IBY.module.css";
1362
+ import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
1284
1363
  var MAX_SHOWN_PROBLEMS = 50;
1285
1364
  function RawJsonEditor({ value, onChange }) {
1286
- const [text, setText] = useState2(() => JSON.stringify(value, null, 2));
1287
- const [problems, setProblems] = useState2([]);
1288
- const labelId = useId4();
1289
- const errorId = useId4();
1365
+ const [text, setText] = useState3(() => JSON.stringify(value, null, 2));
1366
+ const [problems, setProblems] = useState3([]);
1367
+ const labelId = useId5();
1368
+ const errorId = useId5();
1290
1369
  const apply = (next) => {
1291
1370
  setText(next);
1292
1371
  let parsed;
@@ -1307,13 +1386,13 @@ function RawJsonEditor({ value, onChange }) {
1307
1386
  onChange(result.data);
1308
1387
  };
1309
1388
  const hasProblems = problems.length > 0;
1310
- return /* @__PURE__ */ jsxs11("section", { className: styles10.wrapper, "aria-labelledby": labelId, children: [
1311
- /* @__PURE__ */ jsx14("h2", { className: styles10.heading, id: labelId, children: getAdminLabel("mode.advanced") }),
1312
- /* @__PURE__ */ jsx14("p", { className: styles10.hint, children: getAdminLabel("advanced.hint") }),
1313
- /* @__PURE__ */ jsx14(
1389
+ return /* @__PURE__ */ jsxs12("section", { className: styles11.wrapper, "aria-labelledby": labelId, children: [
1390
+ /* @__PURE__ */ jsx15("h2", { className: styles11.heading, id: labelId, children: getAdminLabel("mode.advanced") }),
1391
+ /* @__PURE__ */ jsx15("p", { className: styles11.hint, children: getAdminLabel("advanced.hint") }),
1392
+ /* @__PURE__ */ jsx15(
1314
1393
  "textarea",
1315
1394
  {
1316
- className: styles10.textarea,
1395
+ className: styles11.textarea,
1317
1396
  value: text,
1318
1397
  spellCheck: false,
1319
1398
  "aria-label": getAdminLabel("mode.advanced"),
@@ -1324,33 +1403,34 @@ function RawJsonEditor({ value, onChange }) {
1324
1403
  }
1325
1404
  }
1326
1405
  ),
1327
- hasProblems ? /* @__PURE__ */ jsxs11("div", { className: styles10.problems, id: errorId, role: "alert", children: [
1328
- /* @__PURE__ */ jsx14("p", { className: styles10.problemsTitle, children: getAdminLabel("advanced.invalid") }),
1329
- /* @__PURE__ */ jsx14("ul", { children: problems.map((problem, i) => /* @__PURE__ */ jsx14("li", { children: problem }, i)) })
1406
+ hasProblems ? /* @__PURE__ */ jsxs12("div", { className: styles11.problems, id: errorId, role: "alert", children: [
1407
+ /* @__PURE__ */ jsx15("p", { className: styles11.problemsTitle, children: getAdminLabel("advanced.invalid") }),
1408
+ /* @__PURE__ */ jsx15("ul", { children: problems.map((problem, i) => /* @__PURE__ */ jsx15("li", { children: problem }, i)) })
1330
1409
  ] }) : null
1331
1410
  ] });
1332
1411
  }
1333
1412
 
1334
1413
  // src/admin/AdminEditor.tsx
1335
1414
  import { validate as validate2 } from "@takuhon/core";
1336
- import { useRef as useRef2, useState as useState3 } from "react";
1337
- import styles11 from "../AdminEditor.module-ABYQXFN4.module.css";
1338
- import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
1415
+ import { useRef as useRef3, useState as useState4 } from "react";
1416
+ import styles12 from "../AdminEditor.module-ABYQXFN4.module.css";
1417
+ import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
1339
1418
  function AdminEditor({
1340
1419
  initialDocument,
1341
1420
  onSave,
1342
1421
  onReload,
1343
1422
  onExport,
1344
1423
  onImport,
1345
- formatLocale
1424
+ formatLocale,
1425
+ uploadAsset
1346
1426
  }) {
1347
- const [draft, setDraft] = useState3(initialDocument);
1348
- const [mode, setMode] = useState3("form");
1349
- const [errors, setErrors] = useState3(NO_FIELD_ERRORS);
1350
- const [status, setStatus] = useState3(null);
1351
- const [busy, setBusy] = useState3(false);
1352
- const [loadGen, setLoadGen] = useState3(0);
1353
- const intentRef = useRef2(0);
1427
+ const [draft, setDraft] = useState4(initialDocument);
1428
+ const [mode, setMode] = useState4("form");
1429
+ const [errors, setErrors] = useState4(NO_FIELD_ERRORS);
1430
+ const [status, setStatus] = useState4(null);
1431
+ const [busy, setBusy] = useState4(false);
1432
+ const [loadGen, setLoadGen] = useState4(0);
1433
+ const intentRef = useRef3(0);
1354
1434
  const locales = draft.settings.availableLocales;
1355
1435
  const updateDraft = (next) => {
1356
1436
  intentRef.current += 1;
@@ -1437,14 +1517,14 @@ function AdminEditor({
1437
1517
  const errorEntries = [...errors].flatMap(
1438
1518
  ([pointer, messages]) => messages.map((message) => ({ pointer, message }))
1439
1519
  );
1440
- return /* @__PURE__ */ jsxs12("div", { className: styles11.editor, children: [
1441
- /* @__PURE__ */ jsxs12("div", { className: styles11.toolbar, role: "toolbar", "aria-label": getAdminLabel("toolbar.label"), children: [
1442
- /* @__PURE__ */ jsxs12("div", { className: styles11.modes, role: "group", "aria-label": getAdminLabel("mode.label"), children: [
1443
- /* @__PURE__ */ jsx15(
1520
+ return /* @__PURE__ */ jsxs13("div", { className: styles12.editor, children: [
1521
+ /* @__PURE__ */ jsxs13("div", { className: styles12.toolbar, role: "toolbar", "aria-label": getAdminLabel("toolbar.label"), children: [
1522
+ /* @__PURE__ */ jsxs13("div", { className: styles12.modes, role: "group", "aria-label": getAdminLabel("mode.label"), children: [
1523
+ /* @__PURE__ */ jsx16(
1444
1524
  "button",
1445
1525
  {
1446
1526
  type: "button",
1447
- className: `${styles11.modeButton} ${mode === "form" ? styles11.modeActive : ""}`,
1527
+ className: `${styles12.modeButton} ${mode === "form" ? styles12.modeActive : ""}`,
1448
1528
  "aria-pressed": mode === "form",
1449
1529
  onClick: () => {
1450
1530
  setMode("form");
@@ -1452,11 +1532,11 @@ function AdminEditor({
1452
1532
  children: getAdminLabel("mode.form")
1453
1533
  }
1454
1534
  ),
1455
- /* @__PURE__ */ jsx15(
1535
+ /* @__PURE__ */ jsx16(
1456
1536
  "button",
1457
1537
  {
1458
1538
  type: "button",
1459
- className: `${styles11.modeButton} ${mode === "advanced" ? styles11.modeActive : ""}`,
1539
+ className: `${styles12.modeButton} ${mode === "advanced" ? styles12.modeActive : ""}`,
1460
1540
  "aria-pressed": mode === "advanced",
1461
1541
  onClick: () => {
1462
1542
  setMode("advanced");
@@ -1465,12 +1545,12 @@ function AdminEditor({
1465
1545
  }
1466
1546
  )
1467
1547
  ] }),
1468
- /* @__PURE__ */ jsxs12("div", { className: styles11.actions, children: [
1469
- /* @__PURE__ */ jsx15(
1548
+ /* @__PURE__ */ jsxs13("div", { className: styles12.actions, children: [
1549
+ /* @__PURE__ */ jsx16(
1470
1550
  "button",
1471
1551
  {
1472
1552
  type: "button",
1473
- className: styles11.primary,
1553
+ className: styles12.primary,
1474
1554
  disabled: busy,
1475
1555
  onClick: () => {
1476
1556
  void handleSave();
@@ -1478,11 +1558,11 @@ function AdminEditor({
1478
1558
  children: getAdminLabel("action.save")
1479
1559
  }
1480
1560
  ),
1481
- onReload ? /* @__PURE__ */ jsx15(
1561
+ onReload ? /* @__PURE__ */ jsx16(
1482
1562
  "button",
1483
1563
  {
1484
1564
  type: "button",
1485
- className: styles11.secondary,
1565
+ className: styles12.secondary,
1486
1566
  disabled: busy,
1487
1567
  onClick: () => {
1488
1568
  void handleReload();
@@ -1490,22 +1570,22 @@ function AdminEditor({
1490
1570
  children: getAdminLabel("action.reload")
1491
1571
  }
1492
1572
  ) : null,
1493
- onExport ? /* @__PURE__ */ jsx15(
1573
+ onExport ? /* @__PURE__ */ jsx16(
1494
1574
  "button",
1495
1575
  {
1496
1576
  type: "button",
1497
- className: styles11.secondary,
1577
+ className: styles12.secondary,
1498
1578
  onClick: () => {
1499
1579
  onExport(draft);
1500
1580
  },
1501
1581
  children: getAdminLabel("action.export")
1502
1582
  }
1503
1583
  ) : null,
1504
- onImport ? /* @__PURE__ */ jsx15(
1584
+ onImport ? /* @__PURE__ */ jsx16(
1505
1585
  "button",
1506
1586
  {
1507
1587
  type: "button",
1508
- className: styles11.secondary,
1588
+ className: styles12.secondary,
1509
1589
  disabled: busy,
1510
1590
  onClick: () => {
1511
1591
  void handleImport();
@@ -1515,13 +1595,13 @@ function AdminEditor({
1515
1595
  ) : null
1516
1596
  ] })
1517
1597
  ] }),
1518
- /* @__PURE__ */ jsx15("p", { className: styles11.status, role: "status", "aria-live": "polite", "data-tone": status?.tone, children: status?.message ?? "" }),
1519
- errorEntries.length > 0 ? /* @__PURE__ */ jsxs12("section", { className: styles11.summary, "aria-labelledby": "admin-error-summary", children: [
1520
- /* @__PURE__ */ jsx15("h2", { className: styles11.summaryHeading, id: "admin-error-summary", children: getAdminLabel("status.fixSummary") }),
1521
- /* @__PURE__ */ jsx15("ul", { children: errorEntries.map((entry, i) => /* @__PURE__ */ jsx15("li", { children: entry.pointer === "" ? entry.message : `${entry.pointer.replace(/^\//, "")}: ${entry.message}` }, i)) })
1598
+ /* @__PURE__ */ jsx16("p", { className: styles12.status, role: "status", "aria-live": "polite", "data-tone": status?.tone, children: status?.message ?? "" }),
1599
+ errorEntries.length > 0 ? /* @__PURE__ */ jsxs13("section", { className: styles12.summary, "aria-labelledby": "admin-error-summary", children: [
1600
+ /* @__PURE__ */ jsx16("h2", { className: styles12.summaryHeading, id: "admin-error-summary", children: getAdminLabel("status.fixSummary") }),
1601
+ /* @__PURE__ */ jsx16("ul", { children: errorEntries.map((entry, i) => /* @__PURE__ */ jsx16("li", { children: entry.pointer === "" ? entry.message : `${entry.pointer.replace(/^\//, "")}: ${entry.message}` }, i)) })
1522
1602
  ] }) : null,
1523
- mode === "form" ? /* @__PURE__ */ jsxs12("div", { className: styles11.sections, children: [
1524
- /* @__PURE__ */ jsx15(
1603
+ mode === "form" ? /* @__PURE__ */ jsxs13("div", { className: styles12.sections, children: [
1604
+ /* @__PURE__ */ jsx16(
1525
1605
  ProfileForm,
1526
1606
  {
1527
1607
  value: draft.profile,
@@ -1530,10 +1610,11 @@ function AdminEditor({
1530
1610
  },
1531
1611
  locales,
1532
1612
  errors,
1533
- formatLocale
1613
+ formatLocale,
1614
+ uploadAsset
1534
1615
  }
1535
1616
  ),
1536
- /* @__PURE__ */ jsx15(
1617
+ /* @__PURE__ */ jsx16(
1537
1618
  LinksForm,
1538
1619
  {
1539
1620
  value: draft.links,
@@ -1545,7 +1626,7 @@ function AdminEditor({
1545
1626
  formatLocale
1546
1627
  }
1547
1628
  ),
1548
- /* @__PURE__ */ jsx15(
1629
+ /* @__PURE__ */ jsx16(
1549
1630
  CareersForm,
1550
1631
  {
1551
1632
  value: draft.careers,
@@ -1557,7 +1638,7 @@ function AdminEditor({
1557
1638
  formatLocale
1558
1639
  }
1559
1640
  ),
1560
- /* @__PURE__ */ jsx15(
1641
+ /* @__PURE__ */ jsx16(
1561
1642
  ProjectsForm,
1562
1643
  {
1563
1644
  value: draft.projects,
@@ -1569,7 +1650,7 @@ function AdminEditor({
1569
1650
  formatLocale
1570
1651
  }
1571
1652
  ),
1572
- /* @__PURE__ */ jsx15(
1653
+ /* @__PURE__ */ jsx16(
1573
1654
  SkillsForm,
1574
1655
  {
1575
1656
  value: draft.skills,
@@ -1579,7 +1660,7 @@ function AdminEditor({
1579
1660
  errors
1580
1661
  }
1581
1662
  ),
1582
- /* @__PURE__ */ jsx15(
1663
+ /* @__PURE__ */ jsx16(
1583
1664
  SettingsForm,
1584
1665
  {
1585
1666
  value: draft.settings,
@@ -1590,7 +1671,7 @@ function AdminEditor({
1590
1671
  formatLocale
1591
1672
  }
1592
1673
  )
1593
- ] }) : /* @__PURE__ */ jsx15(RawJsonEditor, { value: draft, onChange: updateDraft }, loadGen)
1674
+ ] }) : /* @__PURE__ */ jsx16(RawJsonEditor, { value: draft, onChange: updateDraft }, loadGen)
1594
1675
  ] });
1595
1676
  }
1596
1677
  export {
@@ -1598,6 +1679,7 @@ export {
1598
1679
  CareersForm,
1599
1680
  CheckboxField,
1600
1681
  Field,
1682
+ ImageField,
1601
1683
  LinksForm,
1602
1684
  LocaleTabs,
1603
1685
  NO_FIELD_ERRORS,