@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.
- package/dist/{chunk-GQV2554Z.js → chunk-62OWSJ7V.js} +1 -1
- package/dist/{chunk-LW5EGJFM.js → chunk-A4RARGF2.js} +1 -0
- package/dist/{chunk-I6ZPOEK2.js → chunk-BU52OBPW.js} +1 -1
- package/dist/{chunk-TNHX35TE.js → chunk-VY67DS3O.js} +8 -0
- package/dist/components/primitives/MediaSettingsForms.d.ts +6 -2
- package/dist/components/primitives/MediaSettingsForms.d.ts.map +1 -1
- package/dist/components/primitives/ResolvedMedia.d.ts +2 -1
- package/dist/components/primitives/ResolvedMedia.d.ts.map +1 -1
- package/dist/components/shell/BugReportFAB.d.ts +6 -0
- package/dist/components/shell/BugReportFAB.d.ts.map +1 -0
- package/dist/components/shell/EditorShell.d.ts.map +1 -1
- package/dist/components/shell/MediaLibraryModal.d.ts.map +1 -1
- package/dist/components/shell/SiteSettingsDisplay.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/lib/dexie.d.ts.map +1 -1
- package/dist/lib/dexie.js +1 -2
- package/dist/lib/index.js +2 -2
- package/dist/media/index.js +3 -1
- package/dist/media/utils.d.ts +1 -0
- package/dist/media/utils.d.ts.map +1 -1
- package/dist/schemas/index.js +2 -2
- package/dist/schemas/site-config.d.ts +1 -0
- package/dist/schemas/site-config.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/primitives/MediaSettingsForms.tsx +25 -6
- package/src/components/primitives/ResolvedMedia.tsx +8 -1
- package/src/components/sections/MediaGrid/MediaGrid.tsx +3 -0
- package/src/components/shell/BugReportFAB.tsx +344 -0
- package/src/components/shell/EditorShell.tsx +4 -0
- package/src/components/shell/MediaLibraryModal.tsx +2 -5
- package/src/components/shell/SiteSettingsDisplay.tsx +7 -0
- package/src/lib/dexie.ts +1 -2
- package/src/media/utils.ts +8 -0
- package/src/schemas/site-config.ts +1 -0
|
@@ -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
|
});
|
|
@@ -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;
|
|
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;
|
|
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 @@
|
|
|
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;
|
|
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;
|
|
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":"
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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,
|
package/dist/lib/dexie.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dexie.d.ts","sourceRoot":"","sources":["../../src/lib/dexie.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,SAAS,EAAe,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACjF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAmI/D,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAEpD;AAED,wBAAsB,oBAAoB,IAAI,OAAO,CAAC;IAAE,eAAe,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAcxF;AAED,wBAAsB,mBAAmB,IAAI,OAAO,CAAC;IACnD,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACzC,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B,CAAC,CAsBD;AAED,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,
|
|
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
|
|
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-
|
|
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-
|
|
32
|
+
} from "../chunk-A4RARGF2.js";
|
|
33
33
|
import "../chunk-S2L3BPLS.js";
|
|
34
34
|
import {
|
|
35
35
|
env
|
package/dist/media/index.js
CHANGED
|
@@ -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-
|
|
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,
|
package/dist/media/utils.d.ts
CHANGED
|
@@ -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"}
|
package/dist/schemas/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
AudienceNameSchema,
|
|
4
4
|
MediaGridOptionsSchema,
|
|
5
5
|
slugifyAudienceName
|
|
6
|
-
} from "../chunk-
|
|
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-
|
|
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
|
|
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
|
@@ -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
|
-
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
package/src/media/utils.ts
CHANGED
|
@@ -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()
|