@drawnagency/primitives 0.1.39 → 0.1.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/{chunk-GQV2554Z.js → chunk-62OWSJ7V.js} +1 -1
  2. package/dist/{chunk-LW5EGJFM.js → chunk-A4RARGF2.js} +1 -0
  3. package/dist/{chunk-I6ZPOEK2.js → chunk-BU52OBPW.js} +1 -1
  4. package/dist/{chunk-TNHX35TE.js → chunk-VY67DS3O.js} +8 -0
  5. package/dist/components/primitives/MediaSettingsForms.d.ts +6 -2
  6. package/dist/components/primitives/MediaSettingsForms.d.ts.map +1 -1
  7. package/dist/components/primitives/ResolvedMedia.d.ts +2 -1
  8. package/dist/components/primitives/ResolvedMedia.d.ts.map +1 -1
  9. package/dist/components/shell/BugReportFAB.d.ts +6 -0
  10. package/dist/components/shell/BugReportFAB.d.ts.map +1 -0
  11. package/dist/components/shell/EditorShell.d.ts.map +1 -1
  12. package/dist/components/shell/MediaLibraryModal.d.ts.map +1 -1
  13. package/dist/components/shell/SiteSettingsDisplay.d.ts.map +1 -1
  14. package/dist/index.js +6 -4
  15. package/dist/lib/dexie.d.ts.map +1 -1
  16. package/dist/lib/dexie.js +1 -2
  17. package/dist/lib/index.js +2 -2
  18. package/dist/media/index.js +3 -1
  19. package/dist/media/utils.d.ts +1 -0
  20. package/dist/media/utils.d.ts.map +1 -1
  21. package/dist/schemas/index.js +2 -2
  22. package/dist/schemas/site-config.d.ts +1 -0
  23. package/dist/schemas/site-config.d.ts.map +1 -1
  24. package/package.json +1 -1
  25. package/src/components/primitives/MediaSettingsForms.tsx +25 -6
  26. package/src/components/primitives/ResolvedMedia.tsx +8 -1
  27. package/src/components/sections/MediaGrid/MediaGrid.tsx +3 -0
  28. package/src/components/shell/BugReportFAB.tsx +344 -0
  29. package/src/components/shell/EditorShell.tsx +4 -0
  30. package/src/components/shell/MediaLibraryModal.tsx +2 -5
  31. package/src/components/shell/SiteSettingsDisplay.tsx +7 -0
  32. package/src/lib/dexie.ts +1 -2
  33. package/src/media/utils.ts +8 -0
  34. package/src/schemas/site-config.ts +1 -0
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  HexColorSchema
3
- } from "./chunk-LW5EGJFM.js";
3
+ } from "./chunk-A4RARGF2.js";
4
4
 
5
5
  // src/schemas/audience.ts
6
6
  import { z } from "zod";
@@ -180,6 +180,7 @@ var SiteConfigSchema = z3.object({
180
180
  darkMode: z3.enum(["light", "dark", "optional"]).default("light"),
181
181
  headingFont: z3.string().default("system-ui"),
182
182
  bodyFont: z3.string().default("system-ui"),
183
+ uppercaseHeadings: z3.boolean().default(true),
183
184
  googleFontsUrl: z3.string().refine((url) => url.startsWith("https://fonts.googleapis.com/"), "Must be a Google Fonts URL").nullable().default(null),
184
185
  media: MediaConfigSchema.default(MediaConfigSchema.parse({}))
185
186
  });
@@ -3,7 +3,7 @@ import {
3
3
  getAllSchemas,
4
4
  getSection,
5
5
  getSectionSchema
6
- } from "./chunk-LW5EGJFM.js";
6
+ } from "./chunk-A4RARGF2.js";
7
7
  import {
8
8
  safeNextPath
9
9
  } from "./chunk-S2L3BPLS.js";
@@ -44,6 +44,13 @@ function displayFilenameExt(mime) {
44
44
  const ext = MIME_TO_EXT[mime];
45
45
  return ext ? `.${ext}` : "";
46
46
  }
47
+ function displayFilename(originalName, mimeType) {
48
+ const ext = MIME_TO_EXT[mimeType];
49
+ if (!ext) return originalName;
50
+ const suffix = `.${ext}`;
51
+ if (originalName.endsWith(suffix)) return originalName;
52
+ return `${originalName}${suffix}`;
53
+ }
47
54
 
48
55
  // src/media/queue.ts
49
56
  var ProcessingQueue = class {
@@ -287,6 +294,7 @@ export {
287
294
  EXT_TO_MIME,
288
295
  mimeToExt,
289
296
  displayFilenameExt,
297
+ displayFilename,
290
298
  ProcessingQueue,
291
299
  resolveMedia,
292
300
  resolveManifestReferences,
@@ -1,18 +1,22 @@
1
- export declare function ImageSettingsForm({ border, objectFit, onChange, }: {
1
+ export declare function ImageSettingsForm({ border, objectFit, invertFrom: initialInvertFrom, onChange, }: {
2
2
  border?: boolean;
3
3
  objectFit?: "cover" | "contain";
4
+ invertFrom?: string;
4
5
  onChange: (update: {
5
6
  border?: boolean;
6
7
  objectFit?: "cover" | "contain";
8
+ invertFrom?: string;
7
9
  }) => void;
8
10
  }): import("react/jsx-runtime").JSX.Element;
9
- export declare function DoDontImageSettingsForm({ border, objectFit, doDont: initialDoDont, onChange, }: {
11
+ export declare function DoDontImageSettingsForm({ border, objectFit, invertFrom, doDont: initialDoDont, onChange, }: {
10
12
  border?: boolean;
11
13
  objectFit?: "cover" | "contain";
14
+ invertFrom?: string;
12
15
  doDont: "do" | "dont";
13
16
  onChange: (update: {
14
17
  border?: boolean;
15
18
  objectFit?: "cover" | "contain";
19
+ invertFrom?: string;
16
20
  doDont: "do" | "dont";
17
21
  }) => void;
18
22
  }): import("react/jsx-runtime").JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"MediaSettingsForms.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/MediaSettingsForms.tsx"],"names":[],"mappings":"AAMA,wBAAgB,iBAAiB,CAAC,EAChC,MAAM,EACN,SAAS,EACT,QAAQ,GACT,EAAE;IACD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAChC,QAAQ,EAAE,CAAC,MAAM,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;KAAE,KAAK,IAAI,CAAC;CACnF,2CA+BA;AAED,wBAAgB,uBAAuB,CAAC,EACtC,MAAM,EACN,SAAS,EACT,MAAM,EAAE,aAAa,EACrB,QAAQ,GACT,EAAE;IACD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAChC,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC;IACtB,QAAQ,EAAE,CAAC,MAAM,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAAC,MAAM,EAAE,IAAI,GAAG,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAC1G,2CAmDA"}
1
+ {"version":3,"file":"MediaSettingsForms.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/MediaSettingsForms.tsx"],"names":[],"mappings":"AAMA,wBAAgB,iBAAiB,CAAC,EAChC,MAAM,EACN,SAAS,EACT,UAAU,EAAE,iBAAiB,EAC7B,QAAQ,GACT,EAAE;IACD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,CAAC,MAAM,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACxG,2CA6CA;AAED,wBAAgB,uBAAuB,CAAC,EACtC,MAAM,EACN,SAAS,EACT,UAAU,EACV,MAAM,EAAE,aAAa,EACrB,QAAQ,GACT,EAAE;IACD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC;IACtB,QAAQ,EAAE,CAAC,MAAM,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,IAAI,GAAG,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAC/H,2CAoDA"}
@@ -5,7 +5,8 @@ interface ResolvedMediaProps {
5
5
  alt?: string;
6
6
  className?: string;
7
7
  imgClassName?: string;
8
+ invertFrom?: string;
8
9
  }
9
- export declare function ResolvedMedia({ imageId, src: propSrc, srcset: propSrcset, alt: propAlt, className, imgClassName, }: ResolvedMediaProps): import("react/jsx-runtime").JSX.Element;
10
+ export declare function ResolvedMedia({ imageId, src: propSrc, srcset: propSrcset, alt: propAlt, className, imgClassName, invertFrom, }: ResolvedMediaProps): import("react/jsx-runtime").JSX.Element;
10
11
  export {};
11
12
  //# sourceMappingURL=ResolvedMedia.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ResolvedMedia.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/ResolvedMedia.tsx"],"names":[],"mappings":"AAIA,UAAU,kBAAkB;IAC1B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,OAAO,EACZ,SAAS,EACT,YAAY,GACb,EAAE,kBAAkB,2CA0BpB"}
1
+ {"version":3,"file":"ResolvedMedia.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/ResolvedMedia.tsx"],"names":[],"mappings":"AAIA,UAAU,kBAAkB;IAC1B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,OAAO,EACZ,SAAS,EACT,YAAY,EACZ,UAAU,GACX,EAAE,kBAAkB,2CA+BpB"}
@@ -0,0 +1,6 @@
1
+ interface Props {
2
+ siteId: string;
3
+ }
4
+ export declare function BugReportFAB({ siteId }: Props): import("react/jsx-runtime").JSX.Element | null;
5
+ export {};
6
+ //# sourceMappingURL=BugReportFAB.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BugReportFAB.d.ts","sourceRoot":"","sources":["../../../src/components/shell/BugReportFAB.tsx"],"names":[],"mappings":"AAMA,UAAU,KAAK;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAyBD,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,EAAE,KAAK,kDAsT7C"}
@@ -1 +1 @@
1
- {"version":3,"file":"EditorShell.d.ts","sourceRoot":"","sources":["../../../src/components/shell/EditorShell.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAqDjD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAQxD,UAAU,KAAK;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,YAAY,EAAE;QACZ,KAAK,EAAE,OAAO,CAAC;QACf,aAAa,EAAE,OAAO,CAAC;QACvB,YAAY,EAAE,OAAO,CAAC;QACtB,cAAc,EAAE,OAAO,CAAC;QACxB,kBAAkB,EAAE,OAAO,CAAC;QAC5B,cAAc,EAAE,OAAO,CAAC;KACzB,CAAC;IACF,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,CAAA;KAAE,GAAG,IAAI,CAAC;CACjE;AAED,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAClC,OAAO,EACP,MAAM,EACN,SAAS,EAAE,gBAAgB,EAC3B,YAAY,EACZ,WAAW,GACZ,EAAE,KAAK,2CAqoBP"}
1
+ {"version":3,"file":"EditorShell.d.ts","sourceRoot":"","sources":["../../../src/components/shell/EditorShell.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAsDjD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAQxD,UAAU,KAAK;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,YAAY,EAAE;QACZ,KAAK,EAAE,OAAO,CAAC;QACf,aAAa,EAAE,OAAO,CAAC;QACvB,YAAY,EAAE,OAAO,CAAC;QACtB,cAAc,EAAE,OAAO,CAAC;QACxB,kBAAkB,EAAE,OAAO,CAAC;QAC5B,cAAc,EAAE,OAAO,CAAC;KACzB,CAAC;IACF,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,CAAA;KAAE,GAAG,IAAI,CAAC;CACjE;AAED,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAClC,OAAO,EACP,MAAM,EACN,SAAS,EAAE,gBAAgB,EAC3B,YAAY,EACZ,WAAW,GACZ,EAAE,KAAK,2CAwoBP"}
@@ -1 +1 @@
1
- {"version":3,"file":"MediaLibraryModal.d.ts","sourceRoot":"","sources":["../../../src/components/shell/MediaLibraryModal.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAa,MAAM,mBAAmB,CAAC;AAG9D,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IAClC,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAClC,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AA+ED,wBAAgB,iBAAiB,CAAC,EAChC,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,iBAAiB,EACjB,WAAgB,EAChB,WAAW,GACZ,EAAE,sBAAsB,2CAuQxB"}
1
+ {"version":3,"file":"MediaLibraryModal.d.ts","sourceRoot":"","sources":["../../../src/components/shell/MediaLibraryModal.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAa,MAAM,mBAAmB,CAAC;AAG9D,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IAClC,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAClC,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AA4ED,wBAAgB,iBAAiB,CAAC,EAChC,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,iBAAiB,EACjB,WAAgB,EAChB,WAAW,GACZ,EAAE,sBAAsB,2CAuQxB"}
@@ -1 +1 @@
1
- {"version":3,"file":"SiteSettingsDisplay.d.ts","sourceRoot":"","sources":["../../../src/components/shell/SiteSettingsDisplay.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D,UAAU,KAAK;IACb,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;CACxC;AAQD,wBAAgB,mBAAmB,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,KAAK,2CAyDlE"}
1
+ {"version":3,"file":"SiteSettingsDisplay.d.ts","sourceRoot":"","sources":["../../../src/components/shell/SiteSettingsDisplay.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D,UAAU,KAAK;IACb,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;CACxC;AAQD,wBAAgB,mBAAmB,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,KAAK,2CA+DlE"}
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  AudienceNameSchema,
4
4
  MediaGridOptionsSchema,
5
5
  slugifyAudienceName
6
- } from "./chunk-GQV2554Z.js";
6
+ } from "./chunk-62OWSJ7V.js";
7
7
  import {
8
8
  AudienceSchema,
9
9
  RoleSchema,
@@ -30,7 +30,7 @@ import {
30
30
  safeRedirect,
31
31
  sanitizeHtml,
32
32
  toSectionId
33
- } from "./chunk-I6ZPOEK2.js";
33
+ } from "./chunk-BU52OBPW.js";
34
34
  import {
35
35
  ColorItemSchema,
36
36
  ColorSpaceSchema,
@@ -51,7 +51,7 @@ import {
51
51
  getSectionSchema,
52
52
  registerSchema,
53
53
  registerSection
54
- } from "./chunk-LW5EGJFM.js";
54
+ } from "./chunk-A4RARGF2.js";
55
55
  import {
56
56
  AUDIENCE_COOKIE,
57
57
  LastOwnerError,
@@ -73,6 +73,7 @@ import {
73
73
  EXT_TO_MIME,
74
74
  MIME_TO_EXT,
75
75
  ProcessingQueue,
76
+ displayFilename,
76
77
  displayFilenameExt,
77
78
  generateVideoPoster,
78
79
  getMediaProvider,
@@ -82,7 +83,7 @@ import {
82
83
  resolveMedia,
83
84
  sanitizeMediaName,
84
85
  setMediaProvider
85
- } from "./chunk-TNHX35TE.js";
86
+ } from "./chunk-VY67DS3O.js";
86
87
  import {
87
88
  ImageManifestSchema,
88
89
  MediaConfigSchema,
@@ -125,6 +126,7 @@ export {
125
126
  darkModeEvent,
126
127
  defineSection,
127
128
  deriveContrast,
129
+ displayFilename,
128
130
  displayFilenameExt,
129
131
  editModeEvent,
130
132
  ensureSanitizer,
@@ -1 +1 @@
1
- {"version":3,"file":"dexie.d.ts","sourceRoot":"","sources":["../../src/lib/dexie.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dexie.d.ts","sourceRoot":"","sources":["../../src/lib/dexie.ts"],"names":[],"mappings":"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"}
package/dist/lib/dexie.js CHANGED
@@ -106,14 +106,13 @@ async function restoreLocalChanges() {
106
106
  }
107
107
  async function discardLocalChanges() {
108
108
  const database = getDb();
109
- await database.transaction("rw", [database.sections, database.siteIndex, database.meta, database.siteConfig, database.pendingMedia, database.pendingMediaDeletions, database.mediaManifest], async () => {
109
+ await database.transaction("rw", [database.sections, database.siteIndex, database.meta, database.siteConfig, database.pendingMedia, database.pendingMediaDeletions], async () => {
110
110
  await database.sections.clear();
111
111
  await database.siteIndex.clear();
112
112
  await database.meta.clear();
113
113
  await database.siteConfig.clear();
114
114
  await database.pendingMedia.clear();
115
115
  await database.pendingMediaDeletions.clear();
116
- await database.mediaManifest.clear();
117
116
  });
118
117
  }
119
118
  async function persistSiteIndex(index, deletedSections = []) {
package/dist/lib/index.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  safeRedirect,
19
19
  sanitizeHtml,
20
20
  toSectionId
21
- } from "../chunk-I6ZPOEK2.js";
21
+ } from "../chunk-BU52OBPW.js";
22
22
  import {
23
23
  clearRegistry,
24
24
  createRegistry,
@@ -29,7 +29,7 @@ import {
29
29
  getSection,
30
30
  registerSchema,
31
31
  registerSection
32
- } from "../chunk-LW5EGJFM.js";
32
+ } from "../chunk-A4RARGF2.js";
33
33
  import "../chunk-S2L3BPLS.js";
34
34
  import {
35
35
  env
@@ -2,6 +2,7 @@ import {
2
2
  EXT_TO_MIME,
3
3
  MIME_TO_EXT,
4
4
  ProcessingQueue,
5
+ displayFilename,
5
6
  displayFilenameExt,
6
7
  generateVideoPoster,
7
8
  getMediaProvider,
@@ -11,7 +12,7 @@ import {
11
12
  resolveMedia,
12
13
  sanitizeMediaName,
13
14
  setMediaProvider
14
- } from "../chunk-TNHX35TE.js";
15
+ } from "../chunk-VY67DS3O.js";
15
16
  import {
16
17
  ImageManifestSchema,
17
18
  MediaConfigSchema,
@@ -26,6 +27,7 @@ export {
26
27
  MediaItemSchema,
27
28
  ProcessingQueue,
28
29
  VariantSchema,
30
+ displayFilename,
29
31
  displayFilenameExt,
30
32
  generateVideoPoster,
31
33
  getMediaProvider,
@@ -4,4 +4,5 @@ export declare const MIME_TO_EXT: Record<string, string>;
4
4
  export declare const EXT_TO_MIME: Record<string, string>;
5
5
  export declare function mimeToExt(mime: string): string;
6
6
  export declare function displayFilenameExt(mime: string): string;
7
+ export declare function displayFilename(originalName: string, mimeType: string): string;
7
8
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/media/utils.ts"],"names":[],"mappings":"AAAA,wBAAsB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAqBzE;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAI9C,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAK9C,CAAC;AAEF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGvD"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/media/utils.ts"],"names":[],"mappings":"AAAA,wBAAsB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAqBzE;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAI9C,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAK9C,CAAC;AAEF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGvD;AAED,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAM9E"}
@@ -3,7 +3,7 @@ import {
3
3
  AudienceNameSchema,
4
4
  MediaGridOptionsSchema,
5
5
  slugifyAudienceName
6
- } from "../chunk-GQV2554Z.js";
6
+ } from "../chunk-62OWSJ7V.js";
7
7
  import {
8
8
  AudienceSchema,
9
9
  RoleSchema,
@@ -21,7 +21,7 @@ import {
21
21
  TextLineSchema,
22
22
  getSectionContentSchema,
23
23
  getSectionSchema
24
- } from "../chunk-LW5EGJFM.js";
24
+ } from "../chunk-A4RARGF2.js";
25
25
  import {
26
26
  ImageManifestSchema,
27
27
  MediaConfigSchema,
@@ -37,6 +37,7 @@ export declare const SiteConfigSchema: z.ZodObject<{
37
37
  }>>;
38
38
  headingFont: z.ZodDefault<z.ZodString>;
39
39
  bodyFont: z.ZodDefault<z.ZodString>;
40
+ uppercaseHeadings: z.ZodDefault<z.ZodBoolean>;
40
41
  googleFontsUrl: z.ZodDefault<z.ZodNullable<z.ZodString>>;
41
42
  media: z.ZodDefault<z.ZodObject<{
42
43
  sizes: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
@@ -1 +1 @@
1
- {"version":3,"file":"site-config.d.ts","sourceRoot":"","sources":["../../src/schemas/site-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB,eAAO,MAAM,iBAAiB;;;;;;;;;iBAI5B,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,eAAO,MAAM,WAAW;;;;;;;;;;;;;;iBAYvB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEpD,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;iBAY3B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
1
+ {"version":3,"file":"site-config.d.ts","sourceRoot":"","sources":["../../src/schemas/site-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB,eAAO,MAAM,iBAAiB;;;;;;;;;iBAI5B,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,eAAO,MAAM,WAAW;;;;;;;;;;;;;;iBAYvB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEpD,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;iBAa3B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drawnagency/primitives",
3
- "version": "0.1.39",
3
+ "version": "0.1.41",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./package.json": "./package.json",
@@ -7,14 +7,17 @@ import { Select } from "../shared/Select";
7
7
  export function ImageSettingsForm({
8
8
  border,
9
9
  objectFit,
10
+ invertFrom: initialInvertFrom,
10
11
  onChange,
11
12
  }: {
12
13
  border?: boolean;
13
14
  objectFit?: "cover" | "contain";
14
- onChange: (update: { border?: boolean; objectFit?: "cover" | "contain" }) => void;
15
+ invertFrom?: string;
16
+ onChange: (update: { border?: boolean; objectFit?: "cover" | "contain"; invertFrom?: string }) => void;
15
17
  }) {
16
18
  const [itemBorder, setItemBorder] = useState(border);
17
19
  const [fit, setFit] = useState(objectFit);
20
+ const [invertFrom, setInvertFrom] = useState(initialInvertFrom ?? "");
18
21
 
19
22
  return (
20
23
  <div className="space-y-4">
@@ -24,7 +27,7 @@ export function ImageSettingsForm({
24
27
  onChange={(v) => {
25
28
  const val = (v || undefined) as "cover" | "contain" | undefined;
26
29
  setFit(val);
27
- onChange({ border: itemBorder, objectFit: val });
30
+ onChange({ border: itemBorder, objectFit: val, invertFrom: invertFrom || undefined });
28
31
  }}
29
32
  options={[
30
33
  { value: "", label: "Default (inherit from grid)" },
@@ -32,12 +35,25 @@ export function ImageSettingsForm({
32
35
  { value: "cover", label: "Crop to fill" },
33
36
  ]}
34
37
  />
38
+ <Select
39
+ label="Invert colors"
40
+ value={invertFrom}
41
+ onChange={(v) => {
42
+ setInvertFrom(v);
43
+ onChange({ border: itemBorder, objectFit: fit, invertFrom: v || undefined });
44
+ }}
45
+ options={[
46
+ { value: "", label: "None" },
47
+ { value: "light", label: "Invert on light theme" },
48
+ { value: "dark", label: "Invert on dark theme" },
49
+ ]}
50
+ />
35
51
  <Checkbox
36
52
  checked={itemBorder ?? false}
37
53
  onChange={(v) => {
38
54
  const val = v || undefined;
39
55
  setItemBorder(val);
40
- onChange({ border: val, objectFit: fit });
56
+ onChange({ border: val, objectFit: fit, invertFrom: invertFrom || undefined });
41
57
  }}
42
58
  label="Override border settings"
43
59
  />
@@ -48,18 +64,20 @@ export function ImageSettingsForm({
48
64
  export function DoDontImageSettingsForm({
49
65
  border,
50
66
  objectFit,
67
+ invertFrom,
51
68
  doDont: initialDoDont,
52
69
  onChange,
53
70
  }: {
54
71
  border?: boolean;
55
72
  objectFit?: "cover" | "contain";
73
+ invertFrom?: string;
56
74
  doDont: "do" | "dont";
57
- onChange: (update: { border?: boolean; objectFit?: "cover" | "contain"; doDont: "do" | "dont" }) => void;
75
+ onChange: (update: { border?: boolean; objectFit?: "cover" | "contain"; invertFrom?: string; doDont: "do" | "dont" }) => void;
58
76
  }) {
59
77
  const [doDont, setDoDont] = useState<"do" | "dont">(initialDoDont);
60
- const latestRef = useRef<{ border?: boolean; objectFit?: "cover" | "contain" }>({ border, objectFit });
78
+ const latestRef = useRef<{ border?: boolean; objectFit?: "cover" | "contain"; invertFrom?: string }>({ border, objectFit, invertFrom });
61
79
 
62
- const handleBaseChange = (updated: { border?: boolean; objectFit?: "cover" | "contain" }) => {
80
+ const handleBaseChange = (updated: { border?: boolean; objectFit?: "cover" | "contain"; invertFrom?: string }) => {
63
81
  latestRef.current = updated;
64
82
  onChange({ ...updated, doDont });
65
83
  };
@@ -69,6 +87,7 @@ export function DoDontImageSettingsForm({
69
87
  <ImageSettingsForm
70
88
  border={border}
71
89
  objectFit={objectFit}
90
+ invertFrom={invertFrom}
72
91
  onChange={handleBaseChange}
73
92
  />
74
93
  <hr className="border-base-200" />
@@ -9,6 +9,7 @@ interface ResolvedMediaProps {
9
9
  alt?: string;
10
10
  className?: string;
11
11
  imgClassName?: string;
12
+ invertFrom?: string;
12
13
  }
13
14
 
14
15
  export function ResolvedMedia({
@@ -18,6 +19,7 @@ export function ResolvedMedia({
18
19
  alt: propAlt,
19
20
  className,
20
21
  imgClassName,
22
+ invertFrom,
21
23
  }: ResolvedMediaProps) {
22
24
  const resolved = useResolvedMedia(imageId);
23
25
  const src = propSrc || resolved.src;
@@ -26,7 +28,12 @@ export function ResolvedMedia({
26
28
  const alt = propAlt ?? resolved.alt;
27
29
  const kind = resolved.kind;
28
30
 
29
- const mediaClass = cn("h-full w-full", imgClassName);
31
+ const invertClass =
32
+ invertFrom === "light" ? "invert dark:invert-0" :
33
+ invertFrom === "dark" ? "dark:invert" :
34
+ undefined;
35
+
36
+ const mediaClass = cn("h-full w-full", imgClassName, invertClass);
30
37
 
31
38
  return (
32
39
  <div className={className}>
@@ -77,6 +77,7 @@ function MediaGridEditable({ media, columns, square, border, crop, showCaptions,
77
77
  <DoDontImageSettingsForm
78
78
  border={item.border}
79
79
  objectFit={item.objectFit}
80
+ invertFrom={item.invertFrom}
80
81
  doDont={item.doDont}
81
82
  onChange={(updated) => {
82
83
  const newMedia = media.map((m, i) => i === index ? { ...m, ...updated } : m);
@@ -90,6 +91,7 @@ function MediaGridEditable({ media, columns, square, border, crop, showCaptions,
90
91
  <ImageSettingsForm
91
92
  border={item.border}
92
93
  objectFit={item.objectFit}
94
+ invertFrom={item.invertFrom}
93
95
  onChange={(updated) => {
94
96
  const newMedia = media.map((m, i) => i === index ? { ...m, ...updated } : m);
95
97
  onChange({ type: sectionType, content: { columns, media: newMedia }, ...opts } as SectionContent);
@@ -173,6 +175,7 @@ function MediaGridItem({
173
175
  alt={itemAny.alt as string | undefined}
174
176
  className="h-full w-full"
175
177
  imgClassName={fitClass}
178
+ invertFrom={item.invertFrom}
176
179
  />
177
180
  );
178
181
 
@@ -0,0 +1,344 @@
1
+ import { useState, useCallback, useRef, useEffect } from "react";
2
+ import { createClient } from "@supabase/supabase-js";
3
+ import { EditorModal } from "./EditorModal";
4
+ import { useEditorContext } from "./EditorContext";
5
+ import { env } from "../../lib/env";
6
+
7
+ interface Props {
8
+ siteId: string;
9
+ }
10
+
11
+ type Category = "Visual" | "Unexpected Behavior" | "Media / Media Library" | "Save / Publish" | "Other";
12
+
13
+ const CATEGORIES: Category[] = [
14
+ "Visual",
15
+ "Unexpected Behavior",
16
+ "Media / Media Library",
17
+ "Save / Publish",
18
+ "Other",
19
+ ];
20
+
21
+ interface CapturedContext {
22
+ url: string;
23
+ section_id: null;
24
+ user_agent: string;
25
+ viewport_width: number;
26
+ viewport_height: number;
27
+ }
28
+
29
+ interface ImagePreview {
30
+ dataUri: string;
31
+ name: string;
32
+ }
33
+
34
+ export function BugReportFAB({ siteId }: Props) {
35
+ const { isEditMode, historyState } = useEditorContext();
36
+ const [isOpen, setIsOpen] = useState(false);
37
+ const [category, setCategory] = useState<Category>("Visual");
38
+ const [isCritical, setIsCritical] = useState(false);
39
+ const [description, setDescription] = useState("");
40
+ const [images, setImages] = useState<ImagePreview[]>([]);
41
+ const [isDragOver, setIsDragOver] = useState(false);
42
+ const [isSubmitting, setIsSubmitting] = useState(false);
43
+ const [submitError, setSubmitError] = useState<string | null>(null);
44
+ const [descriptionError, setDescriptionError] = useState<string | null>(null);
45
+ const [toast, setToast] = useState<string | null>(null);
46
+ const capturedContextRef = useRef<CapturedContext | null>(null);
47
+ const dropZoneRef = useRef<HTMLDivElement>(null);
48
+
49
+ const resetForm = useCallback(() => {
50
+ setCategory("Visual");
51
+ setIsCritical(false);
52
+ setDescription("");
53
+ setImages([]);
54
+ setSubmitError(null);
55
+ setDescriptionError(null);
56
+ capturedContextRef.current = null;
57
+ }, []);
58
+
59
+ const handleOpen = useCallback(() => {
60
+ capturedContextRef.current = {
61
+ url: window.location.pathname,
62
+ section_id: null,
63
+ user_agent: navigator.userAgent,
64
+ viewport_width: window.innerWidth,
65
+ viewport_height: window.innerHeight,
66
+ };
67
+ setIsOpen(true);
68
+ }, []);
69
+
70
+ const handleClose = useCallback(() => {
71
+ setIsOpen(false);
72
+ resetForm();
73
+ }, [resetForm]);
74
+
75
+ const fileToDataUri = useCallback((file: File): Promise<string> => {
76
+ return new Promise((resolve, reject) => {
77
+ const reader = new FileReader();
78
+ reader.onload = () => resolve(reader.result as string);
79
+ reader.onerror = reject;
80
+ reader.readAsDataURL(file);
81
+ });
82
+ }, []);
83
+
84
+ const addImageFiles = useCallback(async (files: File[]) => {
85
+ const imageFiles = files.filter((f) => f.type.startsWith("image/"));
86
+ if (imageFiles.length === 0) return;
87
+ const newPreviews = await Promise.all(
88
+ imageFiles.map(async (file) => ({
89
+ dataUri: await fileToDataUri(file),
90
+ name: file.name,
91
+ })),
92
+ );
93
+ setImages((prev) => [...prev, ...newPreviews].slice(0, 5));
94
+ }, [fileToDataUri]);
95
+
96
+ const handleRemoveImage = useCallback((index: number) => {
97
+ setImages((prev) => prev.filter((_, i) => i !== index));
98
+ }, []);
99
+
100
+ const handleDragOver = useCallback((e: React.DragEvent) => {
101
+ e.preventDefault();
102
+ setIsDragOver(true);
103
+ }, []);
104
+
105
+ const handleDragLeave = useCallback((e: React.DragEvent) => {
106
+ if (dropZoneRef.current && !dropZoneRef.current.contains(e.relatedTarget as Node)) {
107
+ setIsDragOver(false);
108
+ }
109
+ }, []);
110
+
111
+ const handleDrop = useCallback(async (e: React.DragEvent) => {
112
+ e.preventDefault();
113
+ setIsDragOver(false);
114
+ const files = Array.from(e.dataTransfer.files);
115
+ await addImageFiles(files);
116
+ }, [addImageFiles]);
117
+
118
+ // Document-level paste listener when modal is open
119
+ useEffect(() => {
120
+ if (!isOpen) return;
121
+
122
+ const handlePaste = async (e: ClipboardEvent) => {
123
+ const items = e.clipboardData?.items;
124
+ if (!items) return;
125
+ const files: File[] = [];
126
+ for (const item of Array.from(items)) {
127
+ if (item.type.startsWith("image/")) {
128
+ const file = item.getAsFile();
129
+ if (file) files.push(file);
130
+ }
131
+ }
132
+ if (files.length > 0) {
133
+ await addImageFiles(files);
134
+ }
135
+ };
136
+
137
+ document.addEventListener("paste", handlePaste);
138
+ return () => document.removeEventListener("paste", handlePaste);
139
+ }, [isOpen, addImageFiles]);
140
+
141
+ const handleSubmit = useCallback(async () => {
142
+ if (!description.trim()) {
143
+ setDescriptionError("Please describe the issue.");
144
+ return;
145
+ }
146
+ setDescriptionError(null);
147
+ setSubmitError(null);
148
+ setIsSubmitting(true);
149
+
150
+ try {
151
+ const supabase = createClient(
152
+ env("SUPABASE_URL"),
153
+ env("SUPABASE_ANON_KEY"),
154
+ );
155
+
156
+ const { data: userData } = await supabase.auth.getUser();
157
+ const userId = userData?.user?.id ?? null;
158
+
159
+ const { error } = await supabase.from("bug_reports").insert({
160
+ site_id: siteId,
161
+ user_id: userId,
162
+ category,
163
+ is_critical: isCritical,
164
+ description: description.trim(),
165
+ images: images.map((img) => img.dataUri),
166
+ context: capturedContextRef.current,
167
+ });
168
+
169
+ if (error) {
170
+ setSubmitError(error.message);
171
+ return;
172
+ }
173
+
174
+ handleClose();
175
+ setToast("Bug report submitted");
176
+ setTimeout(() => setToast(null), 3000);
177
+ } catch (err) {
178
+ setSubmitError(err instanceof Error ? err.message : "Submission failed");
179
+ } finally {
180
+ setIsSubmitting(false);
181
+ }
182
+ }, [siteId, category, isCritical, description, images, handleClose]);
183
+
184
+ const visible = isEditMode && historyState === null;
185
+
186
+ if (!visible && !isOpen && !toast) return null;
187
+
188
+ return (
189
+ <>
190
+ {/* FAB button */}
191
+ {visible && (
192
+ <button
193
+ type="button"
194
+ onClick={handleOpen}
195
+ aria-label="Report a bug"
196
+ className="cursor-pointer fixed bottom-16 right-4 lg:right-auto z-50 flex h-10 w-10 items-center justify-center rounded-full bg-primary text-primary-contrast shadow-lg hover:opacity-90 transition-opacity fab-container-right"
197
+ >
198
+ {/* Bug / beetle SVG icon */}
199
+ <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
200
+ <path d="M19 8h-1.26A6.003 6.003 0 0012 4a6.003 6.003 0 00-5.74 4H5a1 1 0 000 2h1.05A6.04 6.04 0 006 12H5a1 1 0 000 2h1c0 .34.03.67.08 1H5a1 1 0 000 2h1.26a6 6 0 0011.48 0H19a1 1 0 000-2h-1.08c.05-.33.08-.66.08-1h1a1 1 0 000-2h-1a6.04 6.04 0 00-.05-2H19a1 1 0 000-2zM12 6c1.7 0 3.16.88 4 2.19V8a1 1 0 10-2 0v.04A3.97 3.97 0 0012 8a3.97 3.97 0 00-2 .04V8a1 1 0 10-2 0v.19A4.69 4.69 0 018 6a4 4 0 014 0zm0 14a4 4 0 01-4-4v-4a4 4 0 018 0v4a4 4 0 01-4 4z"/>
201
+ </svg>
202
+ </button>
203
+ )}
204
+
205
+ {/* Modal */}
206
+ <EditorModal
207
+ isOpen={isOpen}
208
+ onClose={handleClose}
209
+ title="Report a Bug"
210
+ >
211
+ <div className="space-y-4">
212
+ {/* Category */}
213
+ <div>
214
+ <label className="mb-1 block text-sm font-medium text-base-contrast" htmlFor="bug-category">
215
+ Category
216
+ </label>
217
+ <select
218
+ id="bug-category"
219
+ value={category}
220
+ onChange={(e) => setCategory(e.target.value as Category)}
221
+ className="w-full rounded-md border border-base-200 bg-base px-3 py-2 text-sm text-base-contrast"
222
+ >
223
+ {CATEGORIES.map((cat) => (
224
+ <option key={cat} value={cat}>{cat}</option>
225
+ ))}
226
+ </select>
227
+ </div>
228
+
229
+ {/* Critical toggle */}
230
+ <div className="flex items-center gap-3">
231
+ <button
232
+ type="button"
233
+ role="switch"
234
+ aria-checked={isCritical}
235
+ onClick={() => setIsCritical((prev) => !prev)}
236
+ className={`relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 ${
237
+ isCritical
238
+ ? "bg-red-600 focus-visible:ring-red-600"
239
+ : "bg-base-200 focus-visible:ring-base-200"
240
+ }`}
241
+ >
242
+ <span
243
+ className={`pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow-lg ring-0 transition-transform ${
244
+ isCritical ? "translate-x-5" : "translate-x-0.5"
245
+ } mt-0.5`}
246
+ />
247
+ </button>
248
+ <span className="text-sm text-base-contrast">This is blocking my work</span>
249
+ </div>
250
+
251
+ {/* Description */}
252
+ <div>
253
+ <label className="mb-1 block text-sm font-medium text-base-contrast" htmlFor="bug-description">
254
+ Description
255
+ </label>
256
+ <textarea
257
+ id="bug-description"
258
+ rows={4}
259
+ value={description}
260
+ onChange={(e) => {
261
+ setDescription(e.target.value);
262
+ if (descriptionError && e.target.value.trim()) {
263
+ setDescriptionError(null);
264
+ }
265
+ }}
266
+ placeholder="Describe the issue..."
267
+ className="w-full rounded-md border border-base-200 bg-base px-3 py-2 text-sm text-base-contrast placeholder:text-base-contrast/50 resize-none"
268
+ />
269
+ {descriptionError && (
270
+ <p className="mt-1 text-xs text-red-600">{descriptionError}</p>
271
+ )}
272
+ </div>
273
+
274
+ {/* Screenshots drop zone */}
275
+ <div>
276
+ <label className="mb-1 block text-sm font-medium text-base-contrast">
277
+ Screenshots
278
+ </label>
279
+ <div
280
+ ref={dropZoneRef}
281
+ onDragOver={handleDragOver}
282
+ onDragLeave={handleDragLeave}
283
+ onDrop={handleDrop}
284
+ className={`rounded-md border-2 border-dashed px-4 py-6 text-center transition-colors ${
285
+ isDragOver
286
+ ? "border-primary bg-primary/5"
287
+ : "border-base-200 bg-base-accent/30"
288
+ }`}
289
+ >
290
+ <p className="text-sm text-base-contrast/60">
291
+ Drop images here or paste from clipboard
292
+ </p>
293
+ </div>
294
+
295
+ {/* Thumbnail previews */}
296
+ {images.length > 0 && (
297
+ <div className="mt-2 flex flex-wrap gap-2">
298
+ {images.map((img, index) => (
299
+ <div key={index} className="group relative h-16 w-16">
300
+ <img
301
+ src={img.dataUri}
302
+ alt={img.name}
303
+ className="h-16 w-16 rounded object-cover border border-base-200"
304
+ />
305
+ <button
306
+ type="button"
307
+ onClick={() => handleRemoveImage(index)}
308
+ aria-label={`Remove screenshot ${img.name}`}
309
+ className="absolute -top-1.5 -right-1.5 hidden h-5 w-5 items-center justify-center rounded-full bg-red-600 text-white text-xs group-hover:flex"
310
+ >
311
+ ×
312
+ </button>
313
+ </div>
314
+ ))}
315
+ </div>
316
+ )}
317
+ </div>
318
+
319
+ {/* Submit error */}
320
+ {submitError && (
321
+ <p className="text-sm text-red-600">{submitError}</p>
322
+ )}
323
+
324
+ {/* Submit button */}
325
+ <button
326
+ type="button"
327
+ onClick={handleSubmit}
328
+ disabled={isSubmitting}
329
+ className="w-full rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-contrast hover:opacity-90 transition-opacity disabled:opacity-60"
330
+ >
331
+ {isSubmitting ? "Submitting..." : "Submit Report"}
332
+ </button>
333
+ </div>
334
+ </EditorModal>
335
+
336
+ {/* Toast */}
337
+ {toast && (
338
+ <div className="fixed bottom-4 left-1/2 z-[60] -translate-x-1/2 rounded-md bg-green-600 px-4 py-2 text-sm font-medium text-white shadow-lg">
339
+ {toast}
340
+ </div>
341
+ )}
342
+ </>
343
+ );
344
+ }
@@ -21,6 +21,7 @@ import { ensureSectionsRegistered } from "../sections/register";
21
21
  import { getSection, getAllSections } from "../../lib/registry";
22
22
 
23
23
  ensureSectionsRegistered();
24
+ import { BugReportFAB } from "./BugReportFAB";
24
25
  import { SectionWrapper } from "../editor/SectionWrapper";
25
26
  import { SectionOrderingModal } from "../editor/SectionOrderingModal";
26
27
  import { SectionLayout } from "../sections/SectionLayout";
@@ -190,6 +191,7 @@ export default function EditorShell({
190
191
  root.style.setProperty("--color-primary-contrast", config.primaryContrast);
191
192
  root.style.setProperty("--font-heading", `${config.headingFont}, system-ui, sans-serif`);
192
193
  root.style.setProperty("--font-body", `${config.bodyFont}, system-ui, sans-serif`);
194
+ root.style.setProperty("--heading-text-transform", config.uppercaseHeadings ? "uppercase" : "none");
193
195
 
194
196
  if (config.googleFontsUrl) {
195
197
  if (fontLinkRef.current?.href !== config.googleFontsUrl) {
@@ -632,6 +634,8 @@ export default function EditorShell({
632
634
  onOrderingClick={() => setShowOrderingModal(true)}
633
635
  />
634
636
 
637
+ <BugReportFAB siteId={siteId} />
638
+
635
639
  <HistoryOrEditorContent sections={sections}>
636
640
  <EditorContent
637
641
  sections={sections}
@@ -4,7 +4,7 @@ import { cn } from "../../lib/cn";
4
4
  import { Button } from "../shared/Button";
5
5
  import { Select } from "../shared/Select";
6
6
  import type { MediaItem, MediaKind } from "../../media/types";
7
- import { displayFilenameExt, mimeToExt } from "../../media/utils";
7
+ import { displayFilename, mimeToExt } from "../../media/utils";
8
8
 
9
9
  export interface MediaLibraryModalProps {
10
10
  mode: "select" | "manage";
@@ -39,9 +39,6 @@ function thumbnailSrc(item: MediaItem, localUrls: Record<string, string>): strin
39
39
  return `/api/media/${item.id}/poster.webp`;
40
40
  }
41
41
 
42
- function displayFilename(item: MediaItem): string {
43
- return `${item.originalName}${displayFilenameExt(item.mimeType)}`;
44
- }
45
42
 
46
43
  function UploadZone({ onUpload }: { onUpload: (files: File[]) => void }) {
47
44
  const [dragging, setDragging] = useState(false);
@@ -342,7 +339,7 @@ export function MediaLibraryModal({
342
339
  {/* Filename overlay — bottom, visible on hover */}
343
340
  <div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/60 to-transparent px-2 pb-1.5 pt-4 opacity-0 transition-opacity group-hover:opacity-100">
344
341
  <p className="truncate text-[11px] text-white">
345
- {displayFilename(item)}
342
+ {displayFilename(item.originalName, item.mimeType)}
346
343
  </p>
347
344
  </div>
348
345
  </div>
@@ -1,3 +1,4 @@
1
+ import { Checkbox } from "../shared/Checkbox";
1
2
  import { ColorPicker } from "../shared/ColorPicker";
2
3
  import { FontPicker } from "../shared/FontPicker";
3
4
  import { Input } from "../shared/Input";
@@ -68,6 +69,12 @@ export function SiteSettingsDisplay({ siteConfig, onChange }: Props) {
68
69
  onChange={(family) => handleFontChange("headingFont", family)}
69
70
  />
70
71
 
72
+ <Checkbox
73
+ checked={siteConfig.uppercaseHeadings}
74
+ onChange={(v) => update({ uppercaseHeadings: v })}
75
+ label="Uppercase headings"
76
+ />
77
+
71
78
  <FontPicker
72
79
  label="Body font"
73
80
  value={siteConfig.bodyFont}
package/src/lib/dexie.ts CHANGED
@@ -184,14 +184,13 @@ export async function restoreLocalChanges(): Promise<{
184
184
 
185
185
  export async function discardLocalChanges(): Promise<void> {
186
186
  const database = getDb();
187
- await database.transaction("rw", [database.sections, database.siteIndex, database.meta, database.siteConfig, database.pendingMedia, database.pendingMediaDeletions, database.mediaManifest], async () => {
187
+ await database.transaction("rw", [database.sections, database.siteIndex, database.meta, database.siteConfig, database.pendingMedia, database.pendingMediaDeletions], async () => {
188
188
  await database.sections.clear();
189
189
  await database.siteIndex.clear();
190
190
  await database.meta.clear();
191
191
  await database.siteConfig.clear();
192
192
  await database.pendingMedia.clear();
193
193
  await database.pendingMediaDeletions.clear();
194
- await database.mediaManifest.clear();
195
194
  });
196
195
  }
197
196
 
@@ -46,3 +46,11 @@ export function displayFilenameExt(mime: string): string {
46
46
  const ext = MIME_TO_EXT[mime];
47
47
  return ext ? `.${ext}` : "";
48
48
  }
49
+
50
+ export function displayFilename(originalName: string, mimeType: string): string {
51
+ const ext = MIME_TO_EXT[mimeType];
52
+ if (!ext) return originalName;
53
+ const suffix = `.${ext}`;
54
+ if (originalName.endsWith(suffix)) return originalName;
55
+ return `${originalName}${suffix}`;
56
+ }
@@ -37,6 +37,7 @@ export const SiteConfigSchema = z.object({
37
37
  darkMode: z.enum(["light", "dark", "optional"]).default("light"),
38
38
  headingFont: z.string().default("system-ui"),
39
39
  bodyFont: z.string().default("system-ui"),
40
+ uppercaseHeadings: z.boolean().default(true),
40
41
  googleFontsUrl: z.string()
41
42
  .refine(url => url.startsWith("https://fonts.googleapis.com/"), "Must be a Google Fonts URL")
42
43
  .nullable()