@jant/core 0.2.3 → 0.2.5
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/client.d.ts +6 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +6 -2
- package/dist/lib/assets.d.ts +4 -3
- package/dist/lib/assets.d.ts.map +1 -1
- package/dist/lib/assets.js +1 -3
- package/dist/lib/image-processor.d.ts +30 -0
- package/dist/lib/image-processor.d.ts.map +1 -0
- package/dist/lib/image-processor.js +191 -0
- package/dist/routes/dash/media.d.ts.map +1 -1
- package/dist/routes/dash/media.js +2 -8
- package/dist/theme/layouts/BaseLayout.d.ts +2 -0
- package/dist/theme/layouts/BaseLayout.d.ts.map +1 -1
- package/dist/theme/layouts/BaseLayout.js +9 -15
- package/package.json +5 -4
- package/src/client.ts +6 -2
- package/src/lib/image-processor.ts +218 -0
- package/src/preset.css +2 -11
- package/src/routes/dash/media.tsx +1 -7
- package/src/theme/layouts/BaseLayout.tsx +6 -8
- package/src/lib/assets.ts +0 -51
- package/static/assets/datastar.min.js +0 -7
- package/static/assets/image-processor.js +0 -234
package/dist/client.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Client-side JavaScript entry point
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Bundles all interactive components:
|
|
5
|
+
* - Datastar (reactivity)
|
|
6
|
+
* - BaseCoat (dialogs, dropdowns)
|
|
7
|
+
* - ImageProcessor (media uploads)
|
|
5
8
|
*/
|
|
9
|
+
import "@sudodevnull/datastar";
|
|
6
10
|
import "basecoat-css/all";
|
|
11
|
+
import "./lib/image-processor.js";
|
|
7
12
|
//# sourceMappingURL=client.d.ts.map
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,uBAAuB,CAAC;AAC/B,OAAO,kBAAkB,CAAC;AAC1B,OAAO,0BAA0B,CAAC"}
|
package/dist/client.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Client-side JavaScript entry point
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
|
|
4
|
+
* Bundles all interactive components:
|
|
5
|
+
* - Datastar (reactivity)
|
|
6
|
+
* - BaseCoat (dialogs, dropdowns)
|
|
7
|
+
* - ImageProcessor (media uploads)
|
|
8
|
+
*/ import "@sudodevnull/datastar";
|
|
6
9
|
import "basecoat-css/all";
|
|
10
|
+
import "./lib/image-processor.js";
|
package/dist/lib/assets.d.ts
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
* Asset paths for SSR
|
|
3
3
|
*
|
|
4
4
|
* Development: Paths injected via vite.config.ts `define`
|
|
5
|
-
* Production: Paths replaced at build time with hashed filenames
|
|
5
|
+
* Production: Paths replaced at build time with hashed filenames
|
|
6
6
|
*/
|
|
7
7
|
interface Assets {
|
|
8
|
-
/** CSS path
|
|
8
|
+
/** CSS path */
|
|
9
9
|
styles: string;
|
|
10
|
+
/** Main client bundle (includes Datastar + BaseCoat) */
|
|
10
11
|
client: string;
|
|
11
|
-
|
|
12
|
+
/** Image processor script (lazy-loaded on media page) */
|
|
12
13
|
imageProcessor: string;
|
|
13
14
|
}
|
|
14
15
|
/**
|
package/dist/lib/assets.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../../src/lib/assets.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,UAAU,MAAM;IACd,
|
|
1
|
+
{"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../../src/lib/assets.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,UAAU,MAAM;IACd,eAAe;IACf,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,cAAc,EAAE,MAAM,CAAC;CACxB;AAcD;;GAEG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAclC;AAGD,eAAO,MAAM,MAAM,QAAc,CAAC"}
|
package/dist/lib/assets.js
CHANGED
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
* Asset paths for SSR
|
|
3
3
|
*
|
|
4
4
|
* Development: Paths injected via vite.config.ts `define`
|
|
5
|
-
* Production: Paths replaced at build time with hashed filenames
|
|
5
|
+
* Production: Paths replaced at build time with hashed filenames
|
|
6
6
|
*/ // Production paths - replaced at build time
|
|
7
7
|
const PROD_ASSETS = {
|
|
8
8
|
styles: "__JANT_ASSET_STYLES__",
|
|
9
9
|
client: "__JANT_ASSET_CLIENT__",
|
|
10
|
-
datastar: "__JANT_ASSET_DATASTAR__",
|
|
11
10
|
imageProcessor: "__JANT_ASSET_IMAGE_PROCESSOR__"
|
|
12
11
|
};
|
|
13
12
|
/**
|
|
@@ -18,7 +17,6 @@ const PROD_ASSETS = {
|
|
|
18
17
|
return {
|
|
19
18
|
styles: __JANT_DEV_STYLES__,
|
|
20
19
|
client: __JANT_DEV_CLIENT__,
|
|
21
|
-
datastar: __JANT_DEV_DATASTAR__,
|
|
22
20
|
imageProcessor: __JANT_DEV_IMAGE_PROCESSOR__
|
|
23
21
|
};
|
|
24
22
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side Image Processor
|
|
3
|
+
*
|
|
4
|
+
* Processes images before upload:
|
|
5
|
+
* - Corrects EXIF orientation
|
|
6
|
+
* - Resizes to max dimensions
|
|
7
|
+
* - Strips all metadata (privacy)
|
|
8
|
+
* - Converts to WebP format
|
|
9
|
+
*/
|
|
10
|
+
declare const DEFAULT_OPTIONS: {
|
|
11
|
+
maxWidth: number;
|
|
12
|
+
maxHeight: number;
|
|
13
|
+
quality: number;
|
|
14
|
+
mimeType: "image/webp";
|
|
15
|
+
};
|
|
16
|
+
type ProcessOptions = Partial<typeof DEFAULT_OPTIONS>;
|
|
17
|
+
/**
|
|
18
|
+
* Process image file
|
|
19
|
+
*/
|
|
20
|
+
declare function process(file: File, options?: ProcessOptions): Promise<Blob>;
|
|
21
|
+
/**
|
|
22
|
+
* Process file and create a new File object
|
|
23
|
+
*/
|
|
24
|
+
declare function processToFile(file: File, options?: ProcessOptions): Promise<File>;
|
|
25
|
+
export declare const ImageProcessor: {
|
|
26
|
+
process: typeof process;
|
|
27
|
+
processToFile: typeof processToFile;
|
|
28
|
+
};
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=image-processor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-processor.d.ts","sourceRoot":"","sources":["../../src/lib/image-processor.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,QAAA,MAAM,eAAe;;;;;CAKpB,CAAC;AAEF,KAAK,cAAc,GAAG,OAAO,CAAC,OAAO,eAAe,CAAC,CAAC;AAiHtD;;GAEG;AACH,iBAAe,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgE9E;AAED;;GAEG;AACH,iBAAe,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQpF;AAED,eAAO,MAAM,cAAc;;;CAA6B,CAAC"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side Image Processor
|
|
3
|
+
*
|
|
4
|
+
* Processes images before upload:
|
|
5
|
+
* - Corrects EXIF orientation
|
|
6
|
+
* - Resizes to max dimensions
|
|
7
|
+
* - Strips all metadata (privacy)
|
|
8
|
+
* - Converts to WebP format
|
|
9
|
+
*/ const DEFAULT_OPTIONS = {
|
|
10
|
+
maxWidth: 1920,
|
|
11
|
+
maxHeight: 1920,
|
|
12
|
+
quality: 0.85,
|
|
13
|
+
mimeType: "image/webp"
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* EXIF Orientation values and their transformations
|
|
17
|
+
*/ const ORIENTATIONS = {
|
|
18
|
+
1: {
|
|
19
|
+
rotate: 0,
|
|
20
|
+
flip: false
|
|
21
|
+
},
|
|
22
|
+
2: {
|
|
23
|
+
rotate: 0,
|
|
24
|
+
flip: true
|
|
25
|
+
},
|
|
26
|
+
3: {
|
|
27
|
+
rotate: 180,
|
|
28
|
+
flip: false
|
|
29
|
+
},
|
|
30
|
+
4: {
|
|
31
|
+
rotate: 180,
|
|
32
|
+
flip: true
|
|
33
|
+
},
|
|
34
|
+
5: {
|
|
35
|
+
rotate: 90,
|
|
36
|
+
flip: true
|
|
37
|
+
},
|
|
38
|
+
6: {
|
|
39
|
+
rotate: 90,
|
|
40
|
+
flip: false
|
|
41
|
+
},
|
|
42
|
+
7: {
|
|
43
|
+
rotate: 270,
|
|
44
|
+
flip: true
|
|
45
|
+
},
|
|
46
|
+
8: {
|
|
47
|
+
rotate: 270,
|
|
48
|
+
flip: false
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Read EXIF orientation from JPEG file
|
|
53
|
+
*/ function readExifOrientation(buffer) {
|
|
54
|
+
const view = new DataView(buffer);
|
|
55
|
+
// Check for JPEG SOI marker
|
|
56
|
+
if (view.getUint16(0) !== 0xffd8) return 1;
|
|
57
|
+
let offset = 2;
|
|
58
|
+
const length = view.byteLength;
|
|
59
|
+
while(offset < length){
|
|
60
|
+
if (view.getUint8(offset) !== 0xff) return 1;
|
|
61
|
+
const marker = view.getUint8(offset + 1);
|
|
62
|
+
// APP1 marker (EXIF)
|
|
63
|
+
if (marker === 0xe1) {
|
|
64
|
+
const exifOffset = offset + 4;
|
|
65
|
+
// Check for "Exif\0\0"
|
|
66
|
+
if (view.getUint32(exifOffset) !== 0x45786966 || view.getUint16(exifOffset + 4) !== 0x0000) {
|
|
67
|
+
return 1;
|
|
68
|
+
}
|
|
69
|
+
const tiffOffset = exifOffset + 6;
|
|
70
|
+
const littleEndian = view.getUint16(tiffOffset) === 0x4949;
|
|
71
|
+
// Validate TIFF header
|
|
72
|
+
if (view.getUint16(tiffOffset + 2, littleEndian) !== 0x002a) return 1;
|
|
73
|
+
const ifdOffset = view.getUint32(tiffOffset + 4, littleEndian);
|
|
74
|
+
const numEntries = view.getUint16(tiffOffset + ifdOffset, littleEndian);
|
|
75
|
+
// Search for orientation tag (0x0112)
|
|
76
|
+
for(let i = 0; i < numEntries; i++){
|
|
77
|
+
const entryOffset = tiffOffset + ifdOffset + 2 + i * 12;
|
|
78
|
+
const tag = view.getUint16(entryOffset, littleEndian);
|
|
79
|
+
if (tag === 0x0112) {
|
|
80
|
+
return view.getUint16(entryOffset + 8, littleEndian);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return 1;
|
|
84
|
+
}
|
|
85
|
+
// Skip to next marker
|
|
86
|
+
if (marker === 0xd8 || marker === 0xd9) {
|
|
87
|
+
offset += 2;
|
|
88
|
+
} else {
|
|
89
|
+
offset += 2 + view.getUint16(offset + 2);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return 1;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Load image from file
|
|
96
|
+
*/ function loadImage(file) {
|
|
97
|
+
return new Promise((resolve, reject)=>{
|
|
98
|
+
const img = new Image();
|
|
99
|
+
img.onload = ()=>{
|
|
100
|
+
URL.revokeObjectURL(img.src);
|
|
101
|
+
resolve(img);
|
|
102
|
+
};
|
|
103
|
+
img.onerror = ()=>reject(new Error("Failed to load image"));
|
|
104
|
+
img.src = URL.createObjectURL(file);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Calculate output dimensions maintaining aspect ratio
|
|
109
|
+
*/ function calculateDimensions(width, height, maxWidth, maxHeight) {
|
|
110
|
+
if (width <= maxWidth && height <= maxHeight) {
|
|
111
|
+
return {
|
|
112
|
+
width,
|
|
113
|
+
height
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const ratio = Math.min(maxWidth / width, maxHeight / height);
|
|
117
|
+
return {
|
|
118
|
+
width: Math.round(width * ratio),
|
|
119
|
+
height: Math.round(height * ratio)
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Process image file
|
|
124
|
+
*/ async function process(file, options = {}) {
|
|
125
|
+
const opts = {
|
|
126
|
+
...DEFAULT_OPTIONS,
|
|
127
|
+
...options
|
|
128
|
+
};
|
|
129
|
+
// Read file buffer for EXIF
|
|
130
|
+
const buffer = await file.arrayBuffer();
|
|
131
|
+
const orientation = readExifOrientation(buffer);
|
|
132
|
+
const transform = ORIENTATIONS[orientation] || ORIENTATIONS[1];
|
|
133
|
+
// Load image
|
|
134
|
+
const img = await loadImage(file);
|
|
135
|
+
// For 90° or 270° rotation, swap dimensions
|
|
136
|
+
const isRotated = transform.rotate === 90 || transform.rotate === 270;
|
|
137
|
+
const srcWidth = isRotated ? img.height : img.width;
|
|
138
|
+
const srcHeight = isRotated ? img.width : img.height;
|
|
139
|
+
// Calculate output size
|
|
140
|
+
const { width, height } = calculateDimensions(srcWidth, srcHeight, opts.maxWidth, opts.maxHeight);
|
|
141
|
+
// Create canvas
|
|
142
|
+
const canvas = document.createElement("canvas");
|
|
143
|
+
canvas.width = width;
|
|
144
|
+
canvas.height = height;
|
|
145
|
+
const ctx = canvas.getContext("2d");
|
|
146
|
+
if (!ctx) throw new Error("Failed to get canvas context");
|
|
147
|
+
// Apply transformations
|
|
148
|
+
ctx.save();
|
|
149
|
+
ctx.translate(width / 2, height / 2);
|
|
150
|
+
if (transform.rotate) {
|
|
151
|
+
ctx.rotate(transform.rotate * Math.PI / 180);
|
|
152
|
+
}
|
|
153
|
+
if (transform.flip) {
|
|
154
|
+
ctx.scale(-1, 1);
|
|
155
|
+
}
|
|
156
|
+
const drawWidth = isRotated ? height : width;
|
|
157
|
+
const drawHeight = isRotated ? width : height;
|
|
158
|
+
ctx.drawImage(img, -drawWidth / 2, -drawHeight / 2, drawWidth, drawHeight);
|
|
159
|
+
ctx.restore();
|
|
160
|
+
// Export as WebP
|
|
161
|
+
return new Promise((resolve, reject)=>{
|
|
162
|
+
canvas.toBlob((blob)=>{
|
|
163
|
+
if (blob) {
|
|
164
|
+
resolve(blob);
|
|
165
|
+
} else {
|
|
166
|
+
reject(new Error("Failed to create blob"));
|
|
167
|
+
}
|
|
168
|
+
}, opts.mimeType, opts.quality);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Process file and create a new File object
|
|
173
|
+
*/ async function processToFile(file, options = {}) {
|
|
174
|
+
const blob = await process(file, options);
|
|
175
|
+
// Generate new filename with .webp extension
|
|
176
|
+
const originalName = file.name.replace(/\.[^.]+$/, "");
|
|
177
|
+
const newName = `${originalName}.webp`;
|
|
178
|
+
return new File([
|
|
179
|
+
blob
|
|
180
|
+
], newName, {
|
|
181
|
+
type: "image/webp"
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
export const ImageProcessor = {
|
|
185
|
+
process,
|
|
186
|
+
processToFile
|
|
187
|
+
};
|
|
188
|
+
// Expose globally for inline scripts
|
|
189
|
+
if (typeof window !== "undefined") {
|
|
190
|
+
window.ImageProcessor = ImageProcessor;
|
|
191
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../../src/routes/dash/media.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,QAAQ,EAAS,MAAM,gBAAgB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../../src/routes/dash/media.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,QAAQ,EAAS,MAAM,gBAAgB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAMjD,KAAK,GAAG,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,YAAY,CAAA;CAAE,CAAC;AAE3D,eAAO,MAAM,WAAW,kDAAkB,CAAC"}
|
|
@@ -10,7 +10,6 @@ import { DashLayout } from "../../theme/layouts/index.js";
|
|
|
10
10
|
import { EmptyState, DangerZone } from "../../theme/components/index.js";
|
|
11
11
|
import * as time from "../../lib/time.js";
|
|
12
12
|
import { getMediaUrl, getImageUrl } from "../../lib/image.js";
|
|
13
|
-
import { getAssets } from "../../lib/assets.js";
|
|
14
13
|
export const mediaRoutes = new Hono();
|
|
15
14
|
/**
|
|
16
15
|
* Format file size for display
|
|
@@ -73,7 +72,7 @@ export const mediaRoutes = new Hono();
|
|
|
73
72
|
*
|
|
74
73
|
* Uses plain JavaScript for upload state management (more reliable than Datastar signals
|
|
75
74
|
* for complex async flows like file uploads with SSE responses).
|
|
76
|
-
*/ function MediaListContent({ mediaList, r2PublicUrl, imageTransformUrl
|
|
75
|
+
*/ function MediaListContent({ mediaList, r2PublicUrl, imageTransformUrl }) {
|
|
77
76
|
const { t } = useLingui();
|
|
78
77
|
const processingText = t({
|
|
79
78
|
message: "Processing...",
|
|
@@ -245,9 +244,6 @@ function processSSEEvent(event) {
|
|
|
245
244
|
`.trim();
|
|
246
245
|
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
247
246
|
children: [
|
|
248
|
-
/*#__PURE__*/ _jsx("script", {
|
|
249
|
-
src: imageProcessorUrl
|
|
250
|
-
}),
|
|
251
247
|
/*#__PURE__*/ _jsx("script", {
|
|
252
248
|
dangerouslySetInnerHTML: {
|
|
253
249
|
__html: uploadScript
|
|
@@ -535,7 +531,6 @@ mediaRoutes.get("/", async (c)=>{
|
|
|
535
531
|
const siteName = await c.var.services.settings.get("SITE_NAME") ?? "Jant";
|
|
536
532
|
const r2PublicUrl = c.env.R2_PUBLIC_URL;
|
|
537
533
|
const imageTransformUrl = c.env.IMAGE_TRANSFORM_URL;
|
|
538
|
-
const assets = getAssets();
|
|
539
534
|
return c.html(/*#__PURE__*/ _jsx(DashLayout, {
|
|
540
535
|
c: c,
|
|
541
536
|
title: "Media",
|
|
@@ -544,8 +539,7 @@ mediaRoutes.get("/", async (c)=>{
|
|
|
544
539
|
children: /*#__PURE__*/ _jsx(MediaListContent, {
|
|
545
540
|
mediaList: mediaList,
|
|
546
541
|
r2PublicUrl: r2PublicUrl,
|
|
547
|
-
imageTransformUrl: imageTransformUrl
|
|
548
|
-
imageProcessorUrl: assets.imageProcessor
|
|
542
|
+
imageTransformUrl: imageTransformUrl
|
|
549
543
|
})
|
|
550
544
|
}));
|
|
551
545
|
});
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides the HTML shell with meta tags, styles, and scripts.
|
|
5
5
|
* If Context is provided, automatically wraps children with I18nProvider.
|
|
6
|
+
*
|
|
7
|
+
* Uses vite-ssr-components for automatic dev/prod asset path resolution.
|
|
6
8
|
*/
|
|
7
9
|
import type { FC, PropsWithChildren } from "hono/jsx";
|
|
8
10
|
import type { Context } from "hono";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseLayout.d.ts","sourceRoot":"","sources":["../../../src/theme/layouts/BaseLayout.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"BaseLayout.d.ts","sourceRoot":"","sources":["../../../src/theme/layouts/BaseLayout.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AACtD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAIpC,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,CAAC,CAAC,EAAE,OAAO,CAAC;CACb;AAED,eAAO,MAAM,UAAU,EAAE,EAAE,CAAC,iBAAiB,CAAC,eAAe,CAAC,CA0B7D,CAAC"}
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides the HTML shell with meta tags, styles, and scripts.
|
|
5
5
|
* If Context is provided, automatically wraps children with I18nProvider.
|
|
6
|
+
*
|
|
7
|
+
* Uses vite-ssr-components for automatic dev/prod asset path resolution.
|
|
6
8
|
*/ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
7
|
-
import {
|
|
9
|
+
import { Script, Link, ViteClient } from "vite-ssr-components/hono";
|
|
8
10
|
import { I18nProvider } from "../../i18n/index.js";
|
|
9
11
|
export const BaseLayout = ({ title, description, lang = "en", c, children })=>{
|
|
10
|
-
// Get assets at render time (supports runtime manifest loading)
|
|
11
|
-
const assets = getAssets();
|
|
12
12
|
// Automatically wrap with I18nProvider if Context is provided
|
|
13
13
|
const content = c ? /*#__PURE__*/ _jsx(I18nProvider, {
|
|
14
14
|
c: c,
|
|
@@ -33,19 +33,13 @@ export const BaseLayout = ({ title, description, lang = "en", c, children })=>{
|
|
|
33
33
|
name: "description",
|
|
34
34
|
content: description
|
|
35
35
|
}),
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
href:
|
|
39
|
-
|
|
40
|
-
/*#__PURE__*/ _jsx("script", {
|
|
41
|
-
type: "module",
|
|
42
|
-
src: assets.client,
|
|
43
|
-
defer: true
|
|
36
|
+
/*#__PURE__*/ _jsx(ViteClient, {}),
|
|
37
|
+
/*#__PURE__*/ _jsx(Link, {
|
|
38
|
+
href: "/src/style.css",
|
|
39
|
+
rel: "stylesheet"
|
|
44
40
|
}),
|
|
45
|
-
/*#__PURE__*/ _jsx(
|
|
46
|
-
|
|
47
|
-
src: assets.datastar,
|
|
48
|
-
defer: true
|
|
41
|
+
/*#__PURE__*/ _jsx(Script, {
|
|
42
|
+
src: "/src/client.ts"
|
|
49
43
|
})
|
|
50
44
|
]
|
|
51
45
|
}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jant/core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "A modern, open-source microblogging platform built on Cloudflare Workers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
"files": [
|
|
23
23
|
"bin",
|
|
24
24
|
"dist",
|
|
25
|
-
"static",
|
|
26
25
|
"migrations",
|
|
27
26
|
"src"
|
|
28
27
|
],
|
|
@@ -31,6 +30,7 @@
|
|
|
31
30
|
},
|
|
32
31
|
"dependencies": {
|
|
33
32
|
"@lingui/core": "^5.9.0",
|
|
33
|
+
"@sudodevnull/datastar": "^0.19.9",
|
|
34
34
|
"better-auth": "^1.4.18",
|
|
35
35
|
"drizzle-orm": "^0.45.1",
|
|
36
36
|
"hono": "^4.11.7",
|
|
@@ -44,7 +44,6 @@
|
|
|
44
44
|
"tailwindcss": "^4.0.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"basecoat-css": "^0.3.10",
|
|
48
47
|
"@cloudflare/vite-plugin": "^1.22.1",
|
|
49
48
|
"@cloudflare/workers-types": "^4.20260131.0",
|
|
50
49
|
"@eslint/js": "^9.39.2",
|
|
@@ -58,6 +57,7 @@
|
|
|
58
57
|
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
|
59
58
|
"@typescript-eslint/parser": "^8.54.0",
|
|
60
59
|
"autoprefixer": "^10.4.24",
|
|
60
|
+
"basecoat-css": "^0.3.10",
|
|
61
61
|
"drizzle-kit": "^0.31.8",
|
|
62
62
|
"eslint": "^9.39.2",
|
|
63
63
|
"eslint-plugin-react": "^7.37.5",
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
"typescript": "^5.9.3",
|
|
72
72
|
"unplugin-swc": "^1.5.9",
|
|
73
73
|
"vite": "^7.3.1",
|
|
74
|
+
"vite-ssr-components": "^0.5.2",
|
|
74
75
|
"wrangler": "^4.61.1"
|
|
75
76
|
},
|
|
76
77
|
"repository": {
|
|
@@ -104,7 +105,7 @@
|
|
|
104
105
|
"build:types": "tsc -p tsconfig.build.json",
|
|
105
106
|
"deploy": "pnpm build && wrangler deploy",
|
|
106
107
|
"preview": "vite preview",
|
|
107
|
-
"typecheck": "tsc --noEmit",
|
|
108
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.client.json",
|
|
108
109
|
"lint": "eslint src/",
|
|
109
110
|
"format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"",
|
|
110
111
|
"format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"",
|
package/src/client.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Client-side JavaScript entry point
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Bundles all interactive components:
|
|
5
|
+
* - Datastar (reactivity)
|
|
6
|
+
* - BaseCoat (dialogs, dropdowns)
|
|
7
|
+
* - ImageProcessor (media uploads)
|
|
5
8
|
*/
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
import "@sudodevnull/datastar";
|
|
8
11
|
import "basecoat-css/all";
|
|
12
|
+
import "./lib/image-processor.js";
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side Image Processor
|
|
3
|
+
*
|
|
4
|
+
* Processes images before upload:
|
|
5
|
+
* - Corrects EXIF orientation
|
|
6
|
+
* - Resizes to max dimensions
|
|
7
|
+
* - Strips all metadata (privacy)
|
|
8
|
+
* - Converts to WebP format
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const DEFAULT_OPTIONS = {
|
|
12
|
+
maxWidth: 1920,
|
|
13
|
+
maxHeight: 1920,
|
|
14
|
+
quality: 0.85,
|
|
15
|
+
mimeType: "image/webp" as const,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type ProcessOptions = Partial<typeof DEFAULT_OPTIONS>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* EXIF Orientation values and their transformations
|
|
22
|
+
*/
|
|
23
|
+
const ORIENTATIONS: Record<number, { rotate: number; flip: boolean }> = {
|
|
24
|
+
1: { rotate: 0, flip: false }, // Normal
|
|
25
|
+
2: { rotate: 0, flip: true }, // Flipped horizontally
|
|
26
|
+
3: { rotate: 180, flip: false }, // Rotated 180°
|
|
27
|
+
4: { rotate: 180, flip: true }, // Flipped vertically
|
|
28
|
+
5: { rotate: 90, flip: true }, // Rotated 90° CCW + flipped
|
|
29
|
+
6: { rotate: 90, flip: false }, // Rotated 90° CW
|
|
30
|
+
7: { rotate: 270, flip: true }, // Rotated 90° CW + flipped
|
|
31
|
+
8: { rotate: 270, flip: false }, // Rotated 90° CCW
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Read EXIF orientation from JPEG file
|
|
36
|
+
*/
|
|
37
|
+
function readExifOrientation(buffer: ArrayBuffer): number {
|
|
38
|
+
const view = new DataView(buffer);
|
|
39
|
+
|
|
40
|
+
// Check for JPEG SOI marker
|
|
41
|
+
if (view.getUint16(0) !== 0xffd8) return 1;
|
|
42
|
+
|
|
43
|
+
let offset = 2;
|
|
44
|
+
const length = view.byteLength;
|
|
45
|
+
|
|
46
|
+
while (offset < length) {
|
|
47
|
+
if (view.getUint8(offset) !== 0xff) return 1;
|
|
48
|
+
|
|
49
|
+
const marker = view.getUint8(offset + 1);
|
|
50
|
+
|
|
51
|
+
// APP1 marker (EXIF)
|
|
52
|
+
if (marker === 0xe1) {
|
|
53
|
+
const exifOffset = offset + 4;
|
|
54
|
+
|
|
55
|
+
// Check for "Exif\0\0"
|
|
56
|
+
if (
|
|
57
|
+
view.getUint32(exifOffset) !== 0x45786966 ||
|
|
58
|
+
view.getUint16(exifOffset + 4) !== 0x0000
|
|
59
|
+
) {
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const tiffOffset = exifOffset + 6;
|
|
64
|
+
const littleEndian = view.getUint16(tiffOffset) === 0x4949;
|
|
65
|
+
|
|
66
|
+
// Validate TIFF header
|
|
67
|
+
if (view.getUint16(tiffOffset + 2, littleEndian) !== 0x002a) return 1;
|
|
68
|
+
|
|
69
|
+
const ifdOffset = view.getUint32(tiffOffset + 4, littleEndian);
|
|
70
|
+
const numEntries = view.getUint16(tiffOffset + ifdOffset, littleEndian);
|
|
71
|
+
|
|
72
|
+
// Search for orientation tag (0x0112)
|
|
73
|
+
for (let i = 0; i < numEntries; i++) {
|
|
74
|
+
const entryOffset = tiffOffset + ifdOffset + 2 + i * 12;
|
|
75
|
+
const tag = view.getUint16(entryOffset, littleEndian);
|
|
76
|
+
|
|
77
|
+
if (tag === 0x0112) {
|
|
78
|
+
return view.getUint16(entryOffset + 8, littleEndian);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return 1;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Skip to next marker
|
|
86
|
+
if (marker === 0xd8 || marker === 0xd9) {
|
|
87
|
+
offset += 2;
|
|
88
|
+
} else {
|
|
89
|
+
offset += 2 + view.getUint16(offset + 2);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return 1;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Load image from file
|
|
98
|
+
*/
|
|
99
|
+
function loadImage(file: File): Promise<HTMLImageElement> {
|
|
100
|
+
return new Promise((resolve, reject) => {
|
|
101
|
+
const img = new Image();
|
|
102
|
+
img.onload = () => {
|
|
103
|
+
URL.revokeObjectURL(img.src);
|
|
104
|
+
resolve(img);
|
|
105
|
+
};
|
|
106
|
+
img.onerror = () => reject(new Error("Failed to load image"));
|
|
107
|
+
img.src = URL.createObjectURL(file);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Calculate output dimensions maintaining aspect ratio
|
|
113
|
+
*/
|
|
114
|
+
function calculateDimensions(
|
|
115
|
+
width: number,
|
|
116
|
+
height: number,
|
|
117
|
+
maxWidth: number,
|
|
118
|
+
maxHeight: number
|
|
119
|
+
): { width: number; height: number } {
|
|
120
|
+
if (width <= maxWidth && height <= maxHeight) {
|
|
121
|
+
return { width, height };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const ratio = Math.min(maxWidth / width, maxHeight / height);
|
|
125
|
+
return {
|
|
126
|
+
width: Math.round(width * ratio),
|
|
127
|
+
height: Math.round(height * ratio),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Process image file
|
|
133
|
+
*/
|
|
134
|
+
async function process(file: File, options: ProcessOptions = {}): Promise<Blob> {
|
|
135
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
136
|
+
|
|
137
|
+
// Read file buffer for EXIF
|
|
138
|
+
const buffer = await file.arrayBuffer();
|
|
139
|
+
const orientation = readExifOrientation(buffer);
|
|
140
|
+
const transform = ORIENTATIONS[orientation] || ORIENTATIONS[1];
|
|
141
|
+
|
|
142
|
+
// Load image
|
|
143
|
+
const img = await loadImage(file);
|
|
144
|
+
|
|
145
|
+
// For 90° or 270° rotation, swap dimensions
|
|
146
|
+
const isRotated = transform.rotate === 90 || transform.rotate === 270;
|
|
147
|
+
const srcWidth = isRotated ? img.height : img.width;
|
|
148
|
+
const srcHeight = isRotated ? img.width : img.height;
|
|
149
|
+
|
|
150
|
+
// Calculate output size
|
|
151
|
+
const { width, height } = calculateDimensions(
|
|
152
|
+
srcWidth,
|
|
153
|
+
srcHeight,
|
|
154
|
+
opts.maxWidth,
|
|
155
|
+
opts.maxHeight
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// Create canvas
|
|
159
|
+
const canvas = document.createElement("canvas");
|
|
160
|
+
canvas.width = width;
|
|
161
|
+
canvas.height = height;
|
|
162
|
+
|
|
163
|
+
const ctx = canvas.getContext("2d");
|
|
164
|
+
if (!ctx) throw new Error("Failed to get canvas context");
|
|
165
|
+
|
|
166
|
+
// Apply transformations
|
|
167
|
+
ctx.save();
|
|
168
|
+
ctx.translate(width / 2, height / 2);
|
|
169
|
+
|
|
170
|
+
if (transform.rotate) {
|
|
171
|
+
ctx.rotate((transform.rotate * Math.PI) / 180);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (transform.flip) {
|
|
175
|
+
ctx.scale(-1, 1);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const drawWidth = isRotated ? height : width;
|
|
179
|
+
const drawHeight = isRotated ? width : height;
|
|
180
|
+
ctx.drawImage(img, -drawWidth / 2, -drawHeight / 2, drawWidth, drawHeight);
|
|
181
|
+
|
|
182
|
+
ctx.restore();
|
|
183
|
+
|
|
184
|
+
// Export as WebP
|
|
185
|
+
return new Promise((resolve, reject) => {
|
|
186
|
+
canvas.toBlob(
|
|
187
|
+
(blob) => {
|
|
188
|
+
if (blob) {
|
|
189
|
+
resolve(blob);
|
|
190
|
+
} else {
|
|
191
|
+
reject(new Error("Failed to create blob"));
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
opts.mimeType,
|
|
195
|
+
opts.quality
|
|
196
|
+
);
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Process file and create a new File object
|
|
202
|
+
*/
|
|
203
|
+
async function processToFile(file: File, options: ProcessOptions = {}): Promise<File> {
|
|
204
|
+
const blob = await process(file, options);
|
|
205
|
+
|
|
206
|
+
// Generate new filename with .webp extension
|
|
207
|
+
const originalName = file.name.replace(/\.[^.]+$/, "");
|
|
208
|
+
const newName = `${originalName}.webp`;
|
|
209
|
+
|
|
210
|
+
return new File([blob], newName, { type: "image/webp" });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export const ImageProcessor = { process, processToFile };
|
|
214
|
+
|
|
215
|
+
// Expose globally for inline scripts
|
|
216
|
+
if (typeof window !== "undefined") {
|
|
217
|
+
(window as unknown as { ImageProcessor: typeof ImageProcessor }).ImageProcessor = ImageProcessor;
|
|
218
|
+
}
|
package/src/preset.css
CHANGED
|
@@ -1,22 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Jant Core Preset
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Includes basecoat UI and component styles.
|
|
5
|
+
* Note: @source must be in user's style.css (doesn't work via @import)
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
/* 1. Internal dependencies */
|
|
9
8
|
@import "basecoat-css";
|
|
10
|
-
|
|
11
|
-
/* 2. Component styles (badges, utilities) */
|
|
12
9
|
@import "./styles/components.css";
|
|
13
10
|
|
|
14
|
-
/* 3. Source paths for Tailwind class scanning
|
|
15
|
-
Paths are relative to this file (src/).
|
|
16
|
-
Scans all TSX components for Tailwind classes. */
|
|
17
|
-
@source "./**/*.{ts,tsx}";
|
|
18
|
-
|
|
19
|
-
/* 4. Default theme variables */
|
|
20
11
|
@theme {
|
|
21
12
|
--radius-default: 0.5rem;
|
|
22
13
|
}
|
|
@@ -13,7 +13,6 @@ import { DashLayout } from "../../theme/layouts/index.js";
|
|
|
13
13
|
import { EmptyState, DangerZone } from "../../theme/components/index.js";
|
|
14
14
|
import * as time from "../../lib/time.js";
|
|
15
15
|
import { getMediaUrl, getImageUrl } from "../../lib/image.js";
|
|
16
|
-
import { getAssets } from "../../lib/assets.js";
|
|
17
16
|
|
|
18
17
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
19
18
|
|
|
@@ -96,12 +95,10 @@ function MediaListContent({
|
|
|
96
95
|
mediaList,
|
|
97
96
|
r2PublicUrl,
|
|
98
97
|
imageTransformUrl,
|
|
99
|
-
imageProcessorUrl,
|
|
100
98
|
}: {
|
|
101
99
|
mediaList: Media[];
|
|
102
100
|
r2PublicUrl?: string;
|
|
103
101
|
imageTransformUrl?: string;
|
|
104
|
-
imageProcessorUrl: string;
|
|
105
102
|
}) {
|
|
106
103
|
const { t } = useLingui();
|
|
107
104
|
|
|
@@ -265,8 +262,7 @@ function processSSEEvent(event) {
|
|
|
265
262
|
|
|
266
263
|
return (
|
|
267
264
|
<>
|
|
268
|
-
{/*
|
|
269
|
-
<script src={imageProcessorUrl}></script>
|
|
265
|
+
{/* Upload script */}
|
|
270
266
|
<script dangerouslySetInnerHTML={{ __html: uploadScript }}></script>
|
|
271
267
|
|
|
272
268
|
{/* Header */}
|
|
@@ -493,7 +489,6 @@ mediaRoutes.get("/", async (c) => {
|
|
|
493
489
|
const siteName = (await c.var.services.settings.get("SITE_NAME")) ?? "Jant";
|
|
494
490
|
const r2PublicUrl = c.env.R2_PUBLIC_URL;
|
|
495
491
|
const imageTransformUrl = c.env.IMAGE_TRANSFORM_URL;
|
|
496
|
-
const assets = getAssets();
|
|
497
492
|
|
|
498
493
|
return c.html(
|
|
499
494
|
<DashLayout c={c} title="Media" siteName={siteName} currentPath="/dash/media">
|
|
@@ -501,7 +496,6 @@ mediaRoutes.get("/", async (c) => {
|
|
|
501
496
|
mediaList={mediaList}
|
|
502
497
|
r2PublicUrl={r2PublicUrl}
|
|
503
498
|
imageTransformUrl={imageTransformUrl}
|
|
504
|
-
imageProcessorUrl={assets.imageProcessor}
|
|
505
499
|
/>
|
|
506
500
|
</DashLayout>
|
|
507
501
|
);
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides the HTML shell with meta tags, styles, and scripts.
|
|
5
5
|
* If Context is provided, automatically wraps children with I18nProvider.
|
|
6
|
+
*
|
|
7
|
+
* Uses vite-ssr-components for automatic dev/prod asset path resolution.
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
10
|
import type { FC, PropsWithChildren } from "hono/jsx";
|
|
9
11
|
import type { Context } from "hono";
|
|
10
|
-
import {
|
|
12
|
+
import { Script, Link, ViteClient } from "vite-ssr-components/hono";
|
|
11
13
|
import { I18nProvider } from "../../i18n/index.js";
|
|
12
14
|
|
|
13
15
|
export interface BaseLayoutProps {
|
|
@@ -24,9 +26,6 @@ export const BaseLayout: FC<PropsWithChildren<BaseLayoutProps>> = ({
|
|
|
24
26
|
c,
|
|
25
27
|
children,
|
|
26
28
|
}) => {
|
|
27
|
-
// Get assets at render time (supports runtime manifest loading)
|
|
28
|
-
const assets = getAssets();
|
|
29
|
-
|
|
30
29
|
// Automatically wrap with I18nProvider if Context is provided
|
|
31
30
|
const content = c ? <I18nProvider c={c}>{children}</I18nProvider> : children;
|
|
32
31
|
|
|
@@ -37,10 +36,9 @@ export const BaseLayout: FC<PropsWithChildren<BaseLayoutProps>> = ({
|
|
|
37
36
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
38
37
|
<title>{title}</title>
|
|
39
38
|
{description && <meta name="description" content={description} />}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
<
|
|
43
|
-
<script type="module" src={assets.datastar} defer />
|
|
39
|
+
<ViteClient />
|
|
40
|
+
<Link href="/src/style.css" rel="stylesheet" />
|
|
41
|
+
<Script src="/src/client.ts" />
|
|
44
42
|
</head>
|
|
45
43
|
<body class="bg-background text-foreground antialiased">
|
|
46
44
|
{content}
|
package/src/lib/assets.ts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Asset paths for SSR
|
|
3
|
-
*
|
|
4
|
-
* Development: Paths injected via vite.config.ts `define`
|
|
5
|
-
* Production: Paths replaced at build time with hashed filenames from manifest
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
interface Assets {
|
|
9
|
-
/** CSS path (prevents FOUC in dev, hashed in prod) */
|
|
10
|
-
styles: string;
|
|
11
|
-
client: string;
|
|
12
|
-
datastar: string;
|
|
13
|
-
imageProcessor: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// Injected by vite.config.ts via `define`
|
|
17
|
-
declare const __JANT_DEV_STYLES__: string;
|
|
18
|
-
declare const __JANT_DEV_CLIENT__: string;
|
|
19
|
-
declare const __JANT_DEV_DATASTAR__: string;
|
|
20
|
-
declare const __JANT_DEV_IMAGE_PROCESSOR__: string;
|
|
21
|
-
|
|
22
|
-
// Production paths - replaced at build time
|
|
23
|
-
const PROD_ASSETS: Assets = {
|
|
24
|
-
styles: "__JANT_ASSET_STYLES__",
|
|
25
|
-
client: "__JANT_ASSET_CLIENT__",
|
|
26
|
-
datastar: "__JANT_ASSET_DATASTAR__",
|
|
27
|
-
imageProcessor: "__JANT_ASSET_IMAGE_PROCESSOR__",
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Get assets based on environment
|
|
32
|
-
*/
|
|
33
|
-
export function getAssets(): Assets {
|
|
34
|
-
try {
|
|
35
|
-
if (import.meta.env?.DEV) {
|
|
36
|
-
return {
|
|
37
|
-
styles: __JANT_DEV_STYLES__,
|
|
38
|
-
client: __JANT_DEV_CLIENT__,
|
|
39
|
-
datastar: __JANT_DEV_DATASTAR__,
|
|
40
|
-
imageProcessor: __JANT_DEV_IMAGE_PROCESSOR__,
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
} catch {
|
|
44
|
-
// import.meta.env may not exist in all environments
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return PROD_ASSETS;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// For static imports
|
|
51
|
-
export const ASSETS = PROD_ASSETS;
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minified by jsDelivr using Terser v5.19.2.
|
|
3
|
-
* Original file: /npm/@sudodevnull/datastar@0.19.9/dist/datastar.js
|
|
4
|
-
*
|
|
5
|
-
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
|
6
|
-
*/
|
|
7
|
-
function ze(e){return e instanceof HTMLElement||e instanceof SVGElement?e:null}function ue(){throw new Error("Cycle detected")}function Et(){throw new Error("Computed cannot have side-effects")}const _t=Symbol.for("preact-signals"),O=1,B=2,Y=4,W=8,J=16,H=32;function fe(){G++}function de(){if(G>1)return void G--;let e,t=!1;for(;void 0!==K;){let n=K;for(K=void 0,we++;void 0!==n;){const s=n._nextBatchedEffect;if(n._nextBatchedEffect=void 0,n._flags&=~B,!(n._flags&W)&&Qe(n))try{n._callback()}catch(n){t||(e=n,t=!0)}n=s}}if(we=0,G--,t)throw e}function St(e){if(G>0)return e();fe();try{return e()}finally{de()}}let _,K,G=0,we=0,le=0;function Ze(e){if(void 0===_)return;let t=e._node;return void 0===t||t._target!==_?(t={_version:0,_source:e,_prevSource:_._sources,_nextSource:void 0,_target:_,_prevTarget:void 0,_nextTarget:void 0,_rollbackNode:t},void 0!==_._sources&&(_._sources._nextSource=t),_._sources=t,e._node=t,_._flags&H&&e._subscribe(t),t):-1===t._version?(t._version=0,void 0!==t._nextSource&&(t._nextSource._prevSource=t._prevSource,void 0!==t._prevSource&&(t._prevSource._nextSource=t._nextSource),t._prevSource=_._sources,t._nextSource=void 0,_._sources._nextSource=t,_._sources=t),t):void 0}function T(e){this._value=e,this._version=0,this._node=void 0,this._targets=void 0}function Xe(e){return new T(e)}function Qe(e){for(let t=e._sources;void 0!==t;t=t._nextSource)if(t._source._version!==t._version||!t._source._refresh()||t._source._version!==t._version)return!0;return!1}function et(e){for(let t=e._sources;void 0!==t;t=t._nextSource){const n=t._source._node;if(void 0!==n&&(t._rollbackNode=n),t._source._node=t,t._version=-1,void 0===t._nextSource){e._sources=t;break}}}function tt(e){let t,n=e._sources;for(;void 0!==n;){const e=n._prevSource;-1===n._version?(n._source._unsubscribe(n),void 0!==e&&(e._nextSource=n._nextSource),void 0!==n._nextSource&&(n._nextSource._prevSource=e)):t=n,n._source._node=n._rollbackNode,void 0!==n._rollbackNode&&(n._rollbackNode=void 0),n=e}e._sources=t}function R(e){T.call(this,void 0),this._compute=e,this._sources=void 0,this._globalVersion=le-1,this._flags=Y}function Tt(e){return new R(e)}function st(e){const t=e._cleanup;if(e._cleanup=void 0,"function"==typeof t){fe();const n=_;_=void 0;try{t()}catch(t){throw e._flags&=~O,e._flags|=W,Ne(e),t}finally{_=n,de()}}}function Ne(e){for(let t=e._sources;void 0!==t;t=t._nextSource)t._source._unsubscribe(t);e._compute=void 0,e._sources=void 0,st(e)}function At(e){if(_!==this)throw new Error("Out-of-order effect");tt(this),_=e,this._flags&=~O,this._flags&W&&Ne(this),de()}function X(e){this._compute=e,this._cleanup=void 0,this._sources=void 0,this._nextBatchedEffect=void 0,this._flags=H}function nt(e){const t=new X(e);try{t._callback()}catch(e){throw t._dispose(),e}return t._dispose.bind(t)}T.prototype.brand=_t,T.prototype._refresh=function(){return!0},T.prototype._subscribe=function(e){this._targets!==e&&void 0===e._prevTarget&&(e._nextTarget=this._targets,void 0!==this._targets&&(this._targets._prevTarget=e),this._targets=e)},T.prototype._unsubscribe=function(e){if(void 0!==this._targets){const t=e._prevTarget,n=e._nextTarget;void 0!==t&&(t._nextTarget=n,e._prevTarget=void 0),void 0!==n&&(n._prevTarget=t,e._nextTarget=void 0),e===this._targets&&(this._targets=n)}},T.prototype.subscribe=function(e){const t=this;return nt((function(){const n=t.value,s=this._flags&H;this._flags&=~H;try{e(n)}finally{this._flags|=s}}))},T.prototype.valueOf=function(){return this.value},T.prototype.toString=function(){return this.value+""},T.prototype.toJSON=function(){return this.value},T.prototype.peek=function(){return this._value},Object.defineProperty(T.prototype,"value",{get(){const e=Ze(this);return void 0!==e&&(e._version=this._version),this._value},set(e){if(_ instanceof R&&Et(),e!==this._value){we>100&&ue(),this._value=e,this._version++,le++,fe();try{for(let e=this._targets;void 0!==e;e=e._nextTarget)e._target._notify()}finally{de()}}}}),R.prototype=new T,R.prototype._refresh=function(){if(this._flags&=~B,this._flags&O)return!1;if((this._flags&(Y|H))===H||(this._flags&=~Y,this._globalVersion===le))return!0;if(this._globalVersion=le,this._flags|=O,this._version>0&&!Qe(this))return this._flags&=~O,!0;const e=_;try{et(this),_=this;const e=this._compute();(this._flags&J||this._value!==e||0===this._version)&&(this._value=e,this._flags&=~J,this._version++)}catch(e){this._value=e,this._flags|=J,this._version++}return _=e,tt(this),this._flags&=~O,!0},R.prototype._subscribe=function(e){if(void 0===this._targets){this._flags|=Y|H;for(let e=this._sources;void 0!==e;e=e._nextSource)e._source._subscribe(e)}T.prototype._subscribe.call(this,e)},R.prototype._unsubscribe=function(e){if(void 0!==this._targets&&(T.prototype._unsubscribe.call(this,e),void 0===this._targets)){this._flags&=~H;for(let e=this._sources;void 0!==e;e=e._nextSource)e._source._unsubscribe(e)}},R.prototype._notify=function(){if(!(this._flags&B)){this._flags|=Y|B;for(let e=this._targets;void 0!==e;e=e._nextTarget)e._target._notify()}},R.prototype.peek=function(){if(this._refresh()||ue(),this._flags&J)throw this._value;return this._value},Object.defineProperty(R.prototype,"value",{get(){this._flags&O&&ue();const e=Ze(this);if(this._refresh(),void 0!==e&&(e._version=this._version),this._flags&J)throw this._value;return this._value}}),X.prototype._callback=function(){const e=this._start();try{if(this._flags&W||void 0===this._compute)return;const e=this._compute();"function"==typeof e&&(this._cleanup=e)}finally{e()}},X.prototype._start=function(){this._flags&O&&ue(),this._flags|=O,this._flags&=~W,st(this),et(this),fe();const e=_;return _=this,At.bind(this,e)},X.prototype._notify=function(){this._flags&B||(this._flags|=B,this._nextBatchedEffect=K,K=this)},X.prototype._dispose=function(){this._flags|=W,this._flags&O||Ne(this)};class rt{get value(){return Ee(this)}set value(e){St((()=>Nt(this,e)))}peek(){return Ee(this,{peek:!0})}}const oe=e=>Object.assign(new rt,Object.entries(e).reduce(((e,[t,n])=>{if(["value","peek"].some((e=>e===t)))throw new Error(`${t} is a reserved property name`);return"object"!=typeof n||null===n||Array.isArray(n)?e[t]=Xe(n):e[t]=oe(n),e}),{})),Nt=(e,t)=>Object.keys(t).forEach((n=>e[n].value=t[n])),Ee=(e,{peek:t=!1}={})=>Object.entries(e).reduce(((e,[n,s])=>(s instanceof T?e[n]=t?s.peek():s.value:s instanceof rt&&(e[n]=Ee(s,{peek:t})),e)),{});function ot(e,t){if("object"!=typeof t||Array.isArray(t)||!t)return t;if("object"==typeof t&&void 0!==t.toJSON&&"function"==typeof t.toJSON)return t.toJSON();let n=e;return"object"!=typeof e&&(n={...t}),Object.keys(t).forEach((e=>{n.hasOwnProperty(e)||(n[e]=t[e]),null===t[e]?delete n[e]:n[e]=ot(n[e],t[e])})),n}const q="datastar-event",it="[a-zA-Z_$]+",Lt=it+"[0-9a-zA-Z_$.]*";function Le(e,t,n,s=!0){return new RegExp(`(?<whole>\\${e}(?<${t}>${s?Lt:it})${n})`,"g")}const kt={regexp:Le("$","signal","(?<method>\\([^\\)]*\\))?"),replacer:e=>{const{signal:t,method:n}=e,s="ctx.store()";if(!n?.length)return`${s}.${t}.value`;const o=t.split("."),r=o.pop();return`${s}.${o.join(".")}.value.${r}${n}`}},Mt={regexp:Le("$\\$","action","(?<call>\\((?<args>.*)\\))?"),replacer:({action:e,args:t})=>{const n=["ctx"];t&&n.push(...t.split(",").map((e=>e.trim())));return`ctx.actions.${e}(${n.join(",")})`}},Pt={regexp:Le("~","ref","",!1),replacer:({ref:e})=>`document.querySelector(ctx.store()._dsPlugins.refs.${e})`},$t=[Mt,kt,Pt],Ot={prefix:"store",removeNewLines:!0,preprocessors:{pre:[{regexp:/(?<whole>.+)/g,replacer:e=>{const{whole:t}=e;return`Object.assign({...ctx.store()}, ${t})`}}]},allowedModifiers:new Set(["local","session","ifmissing"]),onLoad:e=>{let t="";const n=n=>{const s=e.store(),o=JSON.stringify(s);o!==t&&(window.localStorage.setItem(U,o),t=o)},s=e.modifiers.has("local");if(s){window.addEventListener(q,n);const t=window.localStorage.getItem(U)||"{}",s=JSON.parse(t);e.mergeStore(s)}const o=e.modifiers.has("session"),r=t=>{const n=e.store(),s=JSON.stringify(n);window.sessionStorage.setItem(U,s)};if(o){window.addEventListener(q,r);const t=window.sessionStorage.getItem(U)||"{}",n=JSON.parse(t);e.mergeStore(n)}const i=e.expressionFn(e),a=lt(e.store(),i,e.modifiers.has("ifmissing"));return e.mergeStore(a),delete e.el.dataset[e.rawKey],()=>{s&&window.removeEventListener(q,n),o&&window.removeEventListener(q,r)}}},It={prefix:"computed",mustNotEmptyKey:!0,onLoad:e=>(e.store()[e.key]=e.reactivity.computed((()=>e.expressionFn(e))),()=>{delete e.store()[e.key]})},Rt={prefix:"ref",mustHaveEmptyKey:!0,mustNotEmptyExpression:!0,bypassExpressionFunctionCreation:()=>!0,onLoad:e=>{e.upsertIfMissingFromStore("_dsPlugins.refs",{});const{el:t,expression:n}=e,s={_dsPlugins:{refs:{...e.store()._dsPlugins.refs.value,[n]:at(t)}}};return e.mergeStore(s),()=>{const t=e.store(),s={...t._dsPlugins.refs.value};delete s[n],t._dsPlugins.refs=s}}},Ct=[Ot,It,Rt];function at(e){if(!e)return"null";if("string"==typeof e)return e;if(e instanceof Window)return"Window";if(e instanceof Document)return"Document";if("BODY"===e.tagName)return"BODY";const t=[];for(;e.parentElement&&"BODY"!==e.tagName;){if(e.id){t.unshift("#"+e.getAttribute("id"));break}{let n=1,s=e;for(;s.previousElementSibling;s=s.previousElementSibling,n++);t.unshift(e.tagName+":nth-child("+n+")")}e=e.parentElement}return t.join(">")}function lt(e,t,n){const s={};if(n)for(const n in t){const o=e[n]?.value;null==o&&(s[n]=t[n])}else Object.assign(s,t);return s}const U="datastar",k=`${U}-`;class Dt{constructor(e={},...t){if(this.plugins=[],this.store=oe({_dsPlugins:{}}),this.actions={},this.refs={},this.reactivity={signal:Xe,computed:Tt,effect:nt},this.parentID="",this.missingIDNext=0,this.removals=new Map,this.mergeRemovals=new Array,this.actions=Object.assign(this.actions,e),!(t=[...Ct,...t]).length)throw new Error("No plugins provided");const n=new Set;for(const e of t){if(e.requiredPluginPrefixes)for(const t of e.requiredPluginPrefixes)if(!n.has(t))throw new Error(`${e.prefix} requires ${t}`);this.plugins.push(e),n.add(e.prefix)}}run(){new MutationObserver(((e,t)=>{N("core","dom","mutation",document.body,document.body.outerHTML)})).observe(document.body,{attributes:!0,childList:!0,subtree:!0}),this.plugins.forEach((e=>{e.onGlobalInit&&(e.onGlobalInit({actions:this.actions,reactivity:this.reactivity,mergeStore:this.mergeStore.bind(this),store:this.store}),N("core","plugins","registration","BODY",`On prefix ${e.prefix}`))})),this.applyPlugins(document.body)}cleanupElementRemovals(e){const t=this.removals.get(e);if(t){for(const e of t.set)e();this.removals.delete(e)}}mergeStore(e){this.mergeRemovals.forEach((e=>e())),this.mergeRemovals=this.mergeRemovals.slice(0);const t=ot(this.store.value,e);this.store=oe(t),this.mergeRemovals.push(this.reactivity.effect((()=>{N("core","store","merged","STORE",JSON.stringify(this.store.value))})))}removeFromStore(...e){const t={...this.store.value};for(const n of e){const e=n.split(".");let s=e[0],o=t;for(let t=1;t<e.length;t++){const n=e[t];o[s]||(o[s]={}),o=o[s],s=n}delete o[s]}this.store=oe(t),this.applyPlugins(document.body)}upsertIfMissingFromStore(e,t){const n=e.split(".");let s=this.store;for(let e=0;e<n.length-1;e++){const t=n[e];s[t]||(s[t]={}),s=s[t]}const o=n[n.length-1];s[o]||(s[o]=this.reactivity.signal(t),N("core","store","upsert",e,t))}signalByName(e){return this.store[e]}applyPlugins(e){const t=new Set;this.plugins.forEach(((n,s)=>{this.walkDownDOM(e,(e=>{s||this.cleanupElementRemovals(e);for(const s in e.dataset){const o=e.dataset[s]||"";let r=o;if(!s.startsWith(n.prefix))continue;if(0===e.id.length&&(e.id=`ds-${this.parentID}-${this.missingIDNext++}`),t.clear(),n.allowedTagRegexps){const t=e.tagName.toLowerCase();if(![...n.allowedTagRegexps].some((e=>t.match(e))))throw new Error(`'${e.tagName}' not allowed for '${s}', allowed ${[[...n.allowedTagRegexps].map((e=>`'${e}'`))].join(", ")}`)}let i=s.slice(n.prefix.length),[a,...l]=i.split(".");if(n.mustHaveEmptyKey&&a.length>0)throw new Error(`'${s}' must have empty key`);if(n.mustNotEmptyKey&&0===a.length)throw new Error(`'${s}' must have non-empty key`);a.length&&(a=a[0].toLowerCase()+a.slice(1));const c=l.map((e=>{const[t,...n]=e.split("_");return{label:t,args:n}}));if(n.allowedModifiers)for(const e of c)if(!n.allowedModifiers.has(e.label))throw new Error(`'${e.label}' is not allowed`);const u=new Map;for(const e of c)u.set(e.label,e.args);if(n.mustHaveEmptyExpression&&r.length)throw new Error(`'${s}' must have empty expression`);if(n.mustNotEmptyExpression&&!r.length)throw new Error(`'${s}' must have non-empty expression`);const d=/;|\n/;n.removeNewLines&&(r=r.split("\n").map((e=>e.trim())).join(" "));const f=[...n.preprocessors?.pre||[],...$t,...n.preprocessors?.post||[]];for(const e of f){if(t.has(e))continue;t.add(e);const n=r.split(d),s=[];n.forEach((t=>{let n=t;const o=[...n.matchAll(e.regexp)];if(o.length)for(const t of o){if(!t.groups)continue;const{groups:s}=t,{whole:o}=s;n=n.replace(o,e.replacer(s))}s.push(n)})),r=s.join("; ")}const h={store:()=>this.store,mergeStore:this.mergeStore.bind(this),upsertIfMissingFromStore:this.upsertIfMissingFromStore.bind(this),removeFromStore:this.removeFromStore.bind(this),applyPlugins:this.applyPlugins.bind(this),cleanupElementRemovals:this.cleanupElementRemovals.bind(this),walkSignals:this.walkSignals.bind(this),actions:this.actions,reactivity:this.reactivity,el:e,rawKey:s,key:a,rawExpression:o,expression:r,expressionFn:()=>{throw new Error("Expression function not created")},modifiers:u,sendDatastarEvent:N};if(!n.bypassExpressionFunctionCreation?.(h)&&!n.mustHaveEmptyExpression&&r.length){const e=r.split(d).map((e=>e.trim())).filter((e=>e.length));e[e.length-1]=`return ${e[e.length-1]}`;const t=e.map((e=>` ${e}`)).join(";\n"),o=`\ntry {\n const _datastarExpression = () => {\n${t}\n }\n const _datastarReturnVal = _datastarExpression()\n ctx.sendDatastarEvent('core', 'attributes', 'expr_eval', ctx.el, '${s} equals ' + JSON.stringify(_datastarReturnVal))\n return _datastarReturnVal\n} catch (e) {\n const msg = \`\nError evaluating Datastar expression:\n${t.replaceAll("`","\\`")}\n\nError: \${e.message}\n\nCheck if the expression is valid before raising an issue.\n\`.trim()\n ctx.sendDatastarEvent('core', 'attributes', 'expr_eval_err', ctx.el, msg)\n console.error(msg)\n debugger\n}\n `;try{const e=n.argumentNames||[],t=new Function("ctx",...e,o);h.expressionFn=t}catch(e){const t=new Error(`Error creating expression function for '${o}', error: ${e}`);N("core","attributes","expr_construction_err",h.el,String(t)),console.error(t)}}const p=n.onLoad(h);p&&(this.removals.has(e)||this.removals.set(e,{id:e.id,set:new Set}),this.removals.get(e).set.add(p))}}))}))}walkSignalsStore(e,t){const n=Object.keys(e);for(let s=0;s<n.length;s++){const o=n[s],r=e[o],i=r instanceof T,a="object"==typeof r&&Object.keys(r).length>0;i?t(o,r):a&&this.walkSignalsStore(r,t)}}walkSignals(e){this.walkSignalsStore(this.store,e)}walkDownDOM(e,t,n=0){if(!e)return;const s=ze(e);if(s)for(t(s),n=0,e=e.firstElementChild;e;)this.walkDownDOM(e,t,n++),e=e.nextElementSibling}}const ct=e=>e.replace(/[A-Z]+(?![a-z])|[A-Z]/g,((e,t)=>(t?"-":"")+e.toLowerCase())),Ft={prefix:"bind",mustNotEmptyKey:!0,mustNotEmptyExpression:!0,onLoad:e=>e.reactivity.effect((async()=>{const t=ct(e.key),n=e.expressionFn(e);let s;s="string"==typeof n?n:JSON.stringify(n),s&&"false"!==s&&"null"!==s&&"undefined"!==s?e.el.setAttribute(t,s):e.el.removeAttribute(t)}))},Ht=/^data:(?<mime>[^;]+);base64,(?<contents>.*)$/,te=["change","input","keydown"],Vt={prefix:"model",mustHaveEmptyKey:!0,preprocessors:{post:[{regexp:/(?<whole>.+)/g,replacer:e=>{const{whole:t}=e;return`ctx.store().${t}`}}]},onLoad:e=>{const{el:t,expression:n}=e,s=e.expressionFn(e),o=t.tagName.toLowerCase();if(n.startsWith("ctx.store().ctx.store()"))throw new Error(`Model attribute on #${t.id} must have a signal name, you probably prefixed with $ by accident`);const r=o.includes("input"),i=t.getAttribute("type"),a=o.includes("checkbox")||r&&"checkbox"===i,l=o.includes("select"),c=o.includes("radio")||r&&"radio"===i,u=r&&"file"===i,d=n.replaceAll("ctx.store().","");c&&(t.getAttribute("name")?.length||t.setAttribute("name",d));const f=e.reactivity.effect((()=>{if(!s)throw new Error(`Signal ${d} not found`);const e="value"in t,n=s.value;if(a||c){const e=t;a?e.checked=n:c&&(e.checked=`${n}`===e.value)}else if(!u)if(l){const e=t;if(e.multiple){const t=s.value;Array.from(e.options).forEach((e=>{e?.disabled||(e.selected=t.includes(e.value))}))}else e.value=`${n}`}else e?t.value=`${n}`:t.setAttribute("value",`${n}`)})),h=async()=>{if(u){const n=[...t?.files||[]],o=[],r=[],i=[];await Promise.all(n.map((e=>new Promise((t=>{const n=new FileReader;n.onload=()=>{if("string"!=typeof n.result)throw new Error("Invalid result type: "+typeof n.result);const t=n.result.match(Ht);if(!t?.groups)throw new Error(`Invalid data URI: ${n.result}`);o.push(t.groups.contents),r.push(t.groups.mime),i.push(e.name)},n.onloadend=()=>t(void 0),n.readAsDataURL(e)}))))),s.value=o;const a=e.store(),l=`${d}Mimes`,c=`${d}Names`;return l in a&&(a[`${l}`].value=r),void(c in a&&(a[`${c}`].value=i))}const n=s.value,o=t||t;if("number"==typeof n)s.value=Number(o.value||o.getAttribute("value"));else if("string"==typeof n)s.value=o.value||o.getAttribute("value")||"";else if("boolean"==typeof n)s.value=a?o.checked||"true"===o.getAttribute("checked"):!(!o.value&&!o.getAttribute("value"));else if(!(typeof n>"u"))if("bigint"==typeof n)s.value=BigInt(o.value||o.getAttribute("value")||"0");else{if(!Array.isArray(n))throw console.log(typeof n),new Error(`Unsupported type ${typeof n} for signal ${d}`);if(l){const e=[...t.selectedOptions].map((e=>e.value));s.value=e}else s.value=JSON.parse(o.value).split(",");console.log(o.value)}},p=t.tagName.split("-");if(p.length>1){const e=p[0].toLowerCase();te.forEach((t=>{te.push(`${e}-${t}`)}))}return te.forEach((e=>t.addEventListener(e,h))),()=>{f(),te.forEach((e=>t.removeEventListener(e,h)))}}},xt={prefix:"text",mustHaveEmptyKey:!0,onLoad:e=>{const{el:t,expressionFn:n}=e;if(!(t instanceof HTMLElement))throw new Error("Element is not HTMLElement");return e.reactivity.effect((()=>{const s=n(e);t.textContent=`${s}`}))}};let $e="";const jt=new Set(["window","once","passive","capture","debounce","throttle","remote","outside"]),Ut={prefix:"on",mustNotEmptyKey:!0,mustNotEmptyExpression:!0,argumentNames:["evt"],onLoad:e=>{const{el:t,key:n,expressionFn:s}=e;let o=e.el;e.modifiers.get("window")&&(o=window);let r=t=>{N("plugin","event",n,o,"triggered"),s(e,t)};const i=e.modifiers.get("debounce");if(i){const e=_e(i),t=se(i,"leading",!1),n=se(i,"noTrail",!0);r=Jt(r,e,t,n)}const a=e.modifiers.get("throttle");if(a){const e=_e(a),t=se(a,"noLead",!0),n=se(a,"noTrail",!1);r=Kt(r,e,t,n)}const l={capture:!0,passive:!1,once:!1};e.modifiers.has("capture")||(l.capture=!1),e.modifiers.has("passive")&&(l.passive=!0),e.modifiers.has("once")&&(l.once=!0),[...e.modifiers.keys()].filter((e=>!jt.has(e))).forEach((s=>{const o=e.modifiers.get(s)||[],i=r;r=()=>{const e=event,r=e[s];let a;if("function"==typeof r)a=r(...o);else if("boolean"==typeof r)a=r;else{if("string"!=typeof r){const e=`Invalid value for ${s} modifier on ${n} on ${t}`;throw console.error(e),new Error(e)}a=r.toLowerCase().trim()===o.join("").toLowerCase().trim()}a&&i(e)}}));const c=ct(n).toLowerCase();switch(c){case"load":return r(),delete e.el.dataset.onLoad,()=>{};case"raf":let n;const s=()=>{r(),n=requestAnimationFrame(s)};return n=requestAnimationFrame(s),()=>{n&&cancelAnimationFrame(n)};case"store-change":return e.reactivity.effect((()=>{let t=e.store().value;e.modifiers.has("remote")&&(t=he(t));const n=JSON.stringify(t);$e!==n&&($e=n,r())}));default:if(e.modifiers.has("outside")){o=document;const e=r;let n=!1;r=s=>{const o=s?.target;if(!o)return;const r=t.id===o.id;r&&n&&(n=!1),!r&&!n&&(e(s),n=!0)}}return o.addEventListener(c,r,l),()=>{o.removeEventListener(c,r)}}}};function he(e){const t={};for(const[n,s]of Object.entries(e))n.startsWith("_")||("object"!=typeof s||Array.isArray(s)?t[n]=s:t[n]=he(s));return t}const Bt={prefix:"class",mustHaveEmptyKey:!0,mustNotEmptyExpression:!0,onLoad:e=>e.reactivity.effect((()=>{const t=e.expressionFn(e);for(const[n,s]of Object.entries(t))s?e.el.classList.add(n):e.el.classList.remove(n);return()=>{e.el.classList.remove(...Object.keys(t))}}))},Wt=[Ft,Vt,xt,Ut,Bt],qt={remote:async e=>he(e.store().value)};function _e(e){if(!e||0===e?.length)return 0;for(const t of e){if(t.endsWith("ms"))return Number(t.replace("ms",""));if(t.endsWith("s"))return 1e3*Number(t.replace("s",""));try{return parseFloat(t)}catch{}}return 0}function se(e,t,n=!1){return!!e&&(e.includes(t)||n)}function Jt(e,t,n=!1,s=!0){let o;const r=()=>o&&clearTimeout(o);return function(...i){r(),n&&!o&&e(...i),o=setTimeout((()=>{s&&e(...i),r()}),t)}}function Kt(e,t,n=!0,s=!1){let o=!1;return function(...r){o||(n&&e(...r),o=!0,setTimeout((()=>{o=!1,s&&e(...r)}),t))}}function Gt(e,{signal:t,headers:n,onopen:s,onmessage:o,onclose:r,onerror:i,openWhenHidden:a,...l}){return new Promise(((c,u)=>{let d=0;const f={...n};let h;function p(){h.abort(),document.hidden||y()}f.accept||(f.accept=Se),a||document.addEventListener("visibilitychange",p);let m=Oe,g=0;function v(){document.removeEventListener("visibilitychange",p),window.clearTimeout(g),h.abort()}t?.addEventListener("abort",(()=>{v(),c()}));const b=s??Zt;async function y(){h=new AbortController;try{const t=await fetch(e,{...l,headers:f,signal:h.signal});await b(t),await Xt(t.body,Qt(es((e=>{e?f[Ie]=e:delete f[Ie]}),(e=>{m=e}),o))),r?.(),v(),c()}catch(e){if(!h.signal.aborted)try{const t=i?.(e)??m;window.clearTimeout(g),g=window.setTimeout(y,t),m*=1.5,m=Math.min(m,Yt),d++,d>=zt?(v(),u(new Error("Max retries hit, check your server or network connection."))):console.error(`Error fetching event source, retrying in ${t}ms`)}catch(e){v(),u(e)}}}m=Oe,y()}))}const Se="text/event-stream",Oe=100,Yt=1e4,zt=10,Ie="last-event-id";function Zt(e){const t=e.headers.get("content-type");if(!t?.startsWith(Se))throw new Error(`Expected content-type to be ${Se}, Actual: ${t}`)}async function Xt(e,t){const n=e.getReader();for(;;){const e=await n.read();if(e.done)break;t(e.value)}}function Qt(e){let t,n,s,o=!1;return function(r){void 0===t?(t=r,n=0,s=-1):t=ts(t,r);const i=t.length;let a=0;for(;n<i;){o&&(10===t[n]&&(a=++n),o=!1);let r=-1;for(;n<i&&-1===r;++n)switch(t[n]){case 58:-1===s&&(s=n-a);break;case 13:o=!0;case 10:r=n}if(-1===r)break;e(t.subarray(a,r),s),a=n,s=-1}a===i?t=void 0:0!==a&&(t=t.subarray(a),n-=a)}}function es(e,t,n){let s=Re();const o=new TextDecoder;return function(r,i){if(0===r.length)n?.(s),s=Re();else if(i>0){const n=o.decode(r.subarray(0,i)),a=i+(32===r[i+1]?2:1),l=o.decode(r.subarray(a));switch(n){case"data":s.data=s.data?s.data+"\n"+l:l;break;case"event":s.event=l;break;case"id":e(s.id=l);break;case"retry":const n=parseInt(l,10);isNaN(n)||t(s.retry=n)}}}}function ts(e,t){const n=new Uint8Array(e.length+t.length);return n.set(e),n.set(t,e.length),n}function Re(){return{data:"",event:"",id:"",retry:void 0}}const ie=new WeakSet;function ss(e,t,n={}){let s;e instanceof Document&&(e=e.documentElement),s="string"==typeof t?as(t):t;const o=ls(s);return ut(e,o,rs(e,o,n))}function ut(e,t,n){if(n.head.block){const s=e.querySelector("head"),o=t.querySelector("head");if(s&&o){const r=dt(o,s,n);return void Promise.all(r).then((()=>{ut(e,t,Object.assign(n,{head:{block:!1,ignore:!0}}))}))}}if("innerHTML"===n.morphStyle)return ft(t,e,n),e.children;if("outerHTML"===n.morphStyle||null==n.morphStyle){const s=us(t,e,n);if(!s)throw new Error("Could not find best match");const o=s?.previousSibling,r=s?.nextSibling,i=ae(e,s,n);return s?cs(o,i,r):[]}throw"Do not understand how to morph style "+n.morphStyle}function ae(e,t,n){if(!n.ignoreActive||e!==document.activeElement){if(null==t){if(!1===n.callbacks.beforeNodeRemoved(e))return;return e.remove(),void n.callbacks.afterNodeRemoved(e)}if(ce(e,t))return!1===n.callbacks.beforeNodeMorphed(e,t)?void 0:(e instanceof HTMLHeadElement&&n.head.ignore||(t instanceof HTMLHeadElement&&e instanceof HTMLHeadElement&&"morph"!==n.head.style?dt(t,e,n):(ns(t,e),ft(t,e,n))),n.callbacks.afterNodeMorphed(e,t),e);if(!1===n.callbacks.beforeNodeRemoved(e)||!1===n.callbacks.beforeNodeAdded(t))return;if(!e.parentElement)throw new Error("oldNode has no parentElement");return e.parentElement.replaceChild(t,e),n.callbacks.afterNodeAdded(t),n.callbacks.afterNodeRemoved(e),t}}function ft(e,t,n){let s,o=e.firstChild,r=t.firstChild;for(;o;){if(s=o,o=s.nextSibling,null==r){if(!1===n.callbacks.beforeNodeAdded(s))return;t.appendChild(s),n.callbacks.afterNodeAdded(s),x(n,s);continue}if(ht(s,r,n)){ae(r,s,n),r=r.nextSibling,x(n,s);continue}let i=os(e,t,s,r,n);if(i){r=Ce(r,i,n),ae(i,s,n),x(n,s);continue}let a=is(e,s,r,n);if(a)r=Ce(r,a,n),ae(a,s,n),x(n,s);else{if(!1===n.callbacks.beforeNodeAdded(s))return;t.insertBefore(s,r),n.callbacks.afterNodeAdded(s),x(n,s)}}for(;null!==r;){let e=r;r=r.nextSibling,pt(e,n)}}function ns(e,t){let n=e.nodeType;if(1===n){for(const n of e.attributes)t.getAttribute(n.name)!==n.value&&t.setAttribute(n.name,n.value);for(const n of t.attributes)e.hasAttribute(n.name)||t.removeAttribute(n.name)}if((n===Node.COMMENT_NODE||n===Node.TEXT_NODE)&&t.nodeValue!==e.nodeValue&&(t.nodeValue=e.nodeValue),e instanceof HTMLInputElement&&t instanceof HTMLInputElement&&"file"!==e.type)t.value=e.value||"",ne(e,t,"value"),ne(e,t,"checked"),ne(e,t,"disabled");else if(e instanceof HTMLOptionElement)ne(e,t,"selected");else if(e instanceof HTMLTextAreaElement&&t instanceof HTMLTextAreaElement){const n=e.value;n!==t.value&&(t.value=n),t.firstChild&&t.firstChild.nodeValue!==n&&(t.firstChild.nodeValue=n)}}function ne(e,t,n){const s=e.getAttribute(n);s!==t.getAttribute(n)&&(s?t.setAttribute(n,s):t.removeAttribute(n))}function dt(e,t,n){const s=[],o=[],r=[],i=[],a=n.head.style,l=new Map;for(const t of e.children)l.set(t.outerHTML,t);for(const e of t.children){let t=l.has(e.outerHTML),s=n.head.shouldReAppend(e),c=n.head.shouldPreserve(e);t||c?s?o.push(e):(l.delete(e.outerHTML),r.push(e)):"append"===a?s&&(o.push(e),i.push(e)):!1!==n.head.shouldRemove(e)&&o.push(e)}i.push(...l.values());const c=[];for(const e of i){const o=document.createRange().createContextualFragment(e.outerHTML).firstChild;if(!o)throw new Error("could not create new element from: "+e.outerHTML);if(n.callbacks.beforeNodeAdded(o)){if(o.hasAttribute("href")||o.hasAttribute("src")){let e;const t=new Promise((t=>{e=t}));o.addEventListener("load",(function(){e(void 0)})),c.push(t)}t.appendChild(o),n.callbacks.afterNodeAdded(o),s.push(o)}}for(const e of o)!1!==n.callbacks.beforeNodeRemoved(e)&&(t.removeChild(e),n.callbacks.afterNodeRemoved(e));return n.head.afterHeadMorphed(t,{added:s,kept:r,removed:o}),c}function F(){}function rs(e,t,n){return{target:e,newContent:t,config:n,morphStyle:n.morphStyle,ignoreActive:n.ignoreActive,idMap:ps(e,t),deadIds:new Set,callbacks:Object.assign({beforeNodeAdded:F,afterNodeAdded:F,beforeNodeMorphed:F,afterNodeMorphed:F,beforeNodeRemoved:F,afterNodeRemoved:F},n.callbacks),head:Object.assign({style:"merge",shouldPreserve:e=>"true"===e.getAttribute("im-preserve"),shouldReAppend:e=>"true"===e.getAttribute("im-re-append"),shouldRemove:F,afterHeadMorphed:F},n.head)}}function ht(e,t,n){return!(!e||!t)&&(e.nodeType===t.nodeType&&e.tagName===t.tagName&&(!(!e?.id?.length||e.id!==t.id)||z(n,e,t)>0))}function ce(e,t){return!(!e||!t)&&(e.nodeType===t.nodeType&&e.tagName===t.tagName)}function Ce(e,t,n){for(;e!==t;){const t=e;if(e=e?.nextSibling,!t)throw new Error("tempNode is null");pt(t,n)}return x(n,t),t.nextSibling}function os(e,t,n,s,o){const r=z(o,n,t);let i=null;if(r>0){i=s;let t=0;for(;null!=i;){if(ht(n,i,o))return i;if(t+=z(o,i,e),t>r)return null;i=i.nextSibling}}return i}function is(e,t,n,s){let o=n,r=t.nextSibling,i=0;for(;o&&r;){if(z(s,o,e)>0)return null;if(ce(t,o))return o;if(ce(r,o)&&(i++,r=r.nextSibling,i>=2))return null;o=o.nextSibling}return o}const De=new DOMParser;function as(e){const t=e.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim,"");if(t.match(/<\/html>/)||t.match(/<\/head>/)||t.match(/<\/body>/)){const n=De.parseFromString(e,"text/html");if(t.match(/<\/html>/))return ie.add(n),n;{let e=n.firstChild;return e?(ie.add(e),e):null}}{const t=De.parseFromString(`<body><template>${e}</template></body>`,"text/html").body.querySelector("template")?.content;if(!t)throw new Error("content is null");return ie.add(t),t}}function ls(e){if(null==e)return document.createElement("div");if(ie.has(e))return e;if(e instanceof Node){const t=document.createElement("div");return t.append(e),t}{const t=document.createElement("div");for(const n of[...e])t.append(n);return t}}function cs(e,t,n){const s=[],o=[];for(;e;)s.push(e),e=e.previousSibling;for(;s.length>0;){const e=s.pop();o.push(e),t?.parentElement?.insertBefore(e,t)}for(o.push(t);n;)s.push(n),o.push(n),n=n.nextSibling;for(;s.length;)t?.parentElement?.insertBefore(s.pop(),t.nextSibling);return o}function us(e,t,n){let s=e.firstChild,o=s,r=0;for(;s;){let e=fs(s,t,n);e>r&&(o=s,r=e),s=s.nextSibling}return o}function fs(e,t,n){return ce(e,t)?.5+z(n,e,t):0}function pt(e,t){x(t,e),!1!==t.callbacks.beforeNodeRemoved(e)&&(e.remove(),t.callbacks.afterNodeRemoved(e))}function ds(e,t){return!e.deadIds.has(t)}function hs(e,t,n){return e.idMap.get(n)?.has(t)||!1}function x(e,t){const n=e.idMap.get(t);if(n)for(const t of n)e.deadIds.add(t)}function z(e,t,n){const s=e.idMap.get(t);if(!s)return 0;let o=0;for(const t of s)ds(e,t)&&hs(e,t,n)&&++o;return o}function Fe(e,t){const n=e.parentElement,s=e.querySelectorAll("[id]");for(const e of s){let s=e;for(;s!==n&&s;){let n=t.get(s);null==n&&(n=new Set,t.set(s,n)),n.add(e.id),s=s.parentElement}}}function ps(e,t){const n=new Map;return Fe(e,n),Fe(t,n),n}const ge="display",He="none",ve="important",Ve="duration",ms="show",be=`${k}showing`,ye=`${k}hiding`,xe=`${k}show-transition-style`,gs={prefix:ms,allowedModifiers:new Set([ve,Ve]),onLoad:e=>{const{el:t,modifiers:n,expressionFn:s,reactivity:o}=e,r=n.has(ve)?ve:void 0;let i,a;const l=e.modifiers.get(Ve);if(l){let e=document.getElementById(xe);if(!e){e=document.createElement("style"),e.id=xe,document.head.appendChild(e);const t=_e(l)||"300";e.innerHTML=`\n .${be} {\n visibility: visible;\n transition: opacity ${t}ms linear;\n }\n .${ye} {\n visibility: hidden;\n transition: visibility 0s ${t}ms, opacity ${t}ms linear;\n }\n `}const n=e=>s=>{s.target===t&&(t.classList.remove(e),t.removeEventListener("transitionend",n(e)))};i=()=>{t.addEventListener("transitionend",n(be)),t.classList.add(be),requestAnimationFrame((()=>{t.style.setProperty("opacity","1",r)}))},a=()=>{t.addEventListener("transitionend",n(ye)),t.classList.add(ye),requestAnimationFrame((()=>{t.style.setProperty("opacity","0",r)}))}}else i=()=>{1===t.style.length&&t.style.display===He?t.style.removeProperty(ge):t.style.setProperty(ge,"",r)},a=()=>{t.style.setProperty(ge,He,r)};return o.effect((async()=>{await s(e)?i():a()}))}},vs="intersects",je="once",Ue="half",Be="full",bs={prefix:vs,allowedModifiers:new Set([je,Ue,Be]),mustHaveEmptyKey:!0,onLoad:e=>{const{modifiers:t}=e,n={threshold:0};t.has(Be)?n.threshold=1:t.has(Ue)&&(n.threshold=.5);const s=new IntersectionObserver((n=>{n.forEach((n=>{n.isIntersecting&&(e.expressionFn(e),t.has(je)&&(s.disconnect(),delete e.el.dataset[e.rawKey]))}))}),n);return s.observe(e.el),()=>s.disconnect()}},We="prepend",qe="append",Je=new Error("Target element must have a parent if using prepend or append"),ys={prefix:"teleport",allowedModifiers:new Set([We,qe]),allowedTagRegexps:new Set(["template"]),bypassExpressionFunctionCreation:()=>!0,onLoad:e=>{const{el:t,modifiers:n,expression:s}=e;if(!(t instanceof HTMLTemplateElement))throw new Error("el must be a template element");const o=document.querySelector(s);if(!o)throw new Error(`Target element not found: ${s}`);if(!t.content)throw new Error("Template element must have content");const r=t.content.cloneNode(!0);if(ze(r)?.firstElementChild)throw new Error("Empty template");if(n.has(We)){if(!o.parentNode)throw Je;o.parentNode.insertBefore(r,o)}else if(n.has(qe)){if(!o.parentNode)throw Je;o.parentNode.insertBefore(r,o.nextSibling)}else o.appendChild(r)}},ws={prefix:"scrollIntoView",mustHaveEmptyKey:!0,mustHaveEmptyExpression:!0,allowedModifiers:new Set(["smooth","instant","auto","hstart","hcenter","hend","hnearest","vstart","vcenter","vend","vnearest","focus"]),onLoad:({el:e,modifiers:t,rawKey:n})=>{e.tabIndex||e.setAttribute("tabindex","0");const s={behavior:"smooth",block:"center",inline:"center"};return t.has("smooth")&&(s.behavior="smooth"),t.has("instant")&&(s.behavior="instant"),t.has("auto")&&(s.behavior="auto"),t.has("hstart")&&(s.inline="start"),t.has("hcenter")&&(s.inline="center"),t.has("hend")&&(s.inline="end"),t.has("hnearest")&&(s.inline="nearest"),t.has("vstart")&&(s.block="start"),t.has("vcenter")&&(s.block="center"),t.has("vend")&&(s.block="end"),t.has("vnearest")&&(s.block="nearest"),vt(e,s,t.has("focus")),delete e.dataset[n],()=>{}}},mt=document,gt=!!mt.startViewTransition,Es={prefix:"viewTransition",onGlobalInit(){let e=!1;if(document.head.childNodes.forEach((t=>{t instanceof HTMLMetaElement&&"view-transition"===t.name&&(e=!0)})),!e){const e=document.createElement("meta");e.name="view-transition",e.content="same-origin",document.head.appendChild(e)}},onLoad:e=>{if(gt)return e.reactivity.effect((()=>{const{el:t,expressionFn:n}=e;let s=n(e);if(!s)return;t.style.viewTransitionName=s}));console.error("Browser does not support view transitions")}},_s=[gs,bs,ys,ws,Es],Ss={scroll:async(e,t,n)=>{const s=Object.assign({behavior:"smooth",vertical:"center",horizontal:"center",shouldFocus:!0},n);vt(document.querySelector(t),s)}};function vt(e,t,n=!0){if(!(e instanceof HTMLElement||e instanceof SVGElement))throw new Error("Element not found");e.tabIndex||e.setAttribute("tabindex","0"),e.scrollIntoView(t),n&&e.focus()}const Ts=500,As=!0,Ns="morph",Ls="Content-Type",ks=`${U}-request`,Ms="application/json",Ps="true",$s=`${k}fragment`,Os=`${k}signal`,Is=`${k}delete`,Rs=`${k}redirect`,Cs=`${k}console`,Z=`${k}indicator`,Te=`${Z}-loading`,Ke=`${k}settling`,re=`${k}swapping`,Ds="self",Fs="get",Hs="post",Vs="put",xs="patch",js="delete",I={MorphElement:"morph",InnerElement:"inner",OuterElement:"outer",PrependElement:"prepend",AppendElement:"append",BeforeElement:"before",AfterElement:"after",UpsertAttributes:"upsert_attributes"},Us={prefix:"fetchIndicator",mustHaveEmptyKey:!0,mustNotEmptyExpression:!0,onGlobalInit:()=>{const e=document.createElement("style");e.innerHTML=`\n.${Z}{\n opacity:0;\n transition: opacity 300ms ease-out;\n}\n.${Te} {\n opacity:1;\n transition: opacity 300ms ease-in;\n}\n`,document.head.appendChild(e)},onLoad:e=>e.reactivity.effect((()=>{e.upsertIfMissingFromStore("_dsPlugins.fetch.indicatorElements",{}),e.upsertIfMissingFromStore("_dsPlugins.fetch.indicatorsVisible",[]);const t=e.reactivity.computed((()=>`${e.expressionFn(e)}`)),n=e.store(),s=document.querySelectorAll(t.value);if(0===s.length)throw new Error("No indicator found");return s.forEach((e=>{e.classList.add(Z)})),n._dsPlugins.fetch.indicatorElements[e.el.id]=e.reactivity.signal(s),()=>{delete n._dsPlugins.fetch.indicatorElements[e.el.id]}}))},Bs={prefix:"header",mustNotEmptyKey:!0,mustNotEmptyExpression:!0,preprocessors:{post:[{regexp:/(?<whole>.+)/g,replacer:e=>{const{whole:t}=e;return`'${t}'`}}]},onLoad:e=>{e.upsertIfMissingFromStore("_dsPlugins.fetch.headers",{});const t=e.key.replace(/([a-z](?=[A-Z]))/g,"$1-").toUpperCase(),n=e.expressionFn(e);return e.store()._dsPlugins.fetch.headers[t]=n,()=>{delete e.store()._dsPlugins.fetch.headers[t]}}},Ws=[Us,Bs];async function qs(e,t,n,s=!0){const o=n.store();if(!t)throw new Error(`No signal for ${e} on ${t}`);let r={...o.value};s&&(r=he(r));const i=JSON.stringify(r),a=n.el;N("plugin","backend","fetch_start",a,JSON.stringify({method:e,urlExpression:t,onlyRemote:s,storeJSON:i}));const l=o?._dsPlugins?.fetch?.indicatorElements&&o._dsPlugins.fetch.indicatorElements[a.id]?.value||[],c=o?._dsPlugins.fetch?.indicatorsVisible;l?.forEach&&l.forEach((e=>{if(!e||!c)return;const t=c.value.findIndex((t=>!!t&&e.isSameNode(t.el)));if(t>-1){const n=c.value[t],s=[...c.value];delete s[t],c.value=[...s.filter((e=>!!e)),{el:e,count:n.count+1}]}else e.classList.remove(Z),e.classList.add(Te),c.value=[...c.value,{el:e,count:1}]}));const u=new URL(t,window.location.origin),d={method:e=e.toUpperCase(),headers:{[Ls]:Ms,[ks]:Ps},onmessage:e=>{if(e.event)switch(e.event.startsWith(k)||console.log(`Unknown event: ${e.event}`),e.event){case $s:const t=e.data.trim().split("\n"),s=["selector","merge","settle","fragment","vt"];let o="",r=Ns,i=Ts,a=As,l=!1,c="",u="";for(let e=0;e<t.length;e++){let n=t[e];if(!n?.length)continue;const d=n.split(" ",1)[0];if(s.includes(d)&&d!==u)switch(u=d,n=n.slice(d.length+1),u){case"selector":c=n;break;case"merge":if(r=n,l=Object.values(I).includes(r),!l)throw new Error(`Unknown merge option: ${r}`);break;case"settle":i=parseInt(n);break;case"fragment":break;case"vt":a="true"===n;break;default:throw new Error("Unknown data type")}"fragment"===u&&(o+=n+"\n")}o?.length||(o="<div></div>"),Js(n,c,r,o,i,a),N("plugin","backend","merge",c,JSON.stringify({fragment:o,settleTime:i,useViewTransition:a}));break;case Os:let d=!1,f="";const h=e.data.trim().split("\n");for(let e=0;e<h.length;e++){const t=h[e],[n,...s]=t.split(" "),o=s.join(" ");switch(n){case"onlyIfMissing":d="true"===o.trim();break;case"store":f+=`${o}\n`;break;default:throw new Error(`Unknown signal type: ${n}`)}}const p=` return Object.assign({...ctx.store()}, ${f})`;try{const e=new Function("ctx",p)(n),t=lt(n.store(),e,d);n.mergeStore(t),n.applyPlugins(document.body)}catch(e){console.log(p),console.error(e)}break;case Is:const[m,...g]=e.data.trim().split(" ");switch(m){case"selector":const e=g.join(" ");document.querySelectorAll(e).forEach((e=>e.remove()));break;case"paths":const t=g.join(" ").split(" ");n.removeFromStore(...t);break;default:throw new Error(`Unknown delete prefix: ${m}`)}break;case Rs:const[v,...b]=e.data.trim().split(" ");if("url"!==v)throw new Error(`Unknown redirect selector: ${v}`);const y=b.join(" ");N("plugin","backend","redirect","WINDOW",y),window.location.href=y;break;case Cs:const[w,..._]=e.data.trim().split(" "),E=_.join(" ");switch(w){case"debug":case"error":case"info":case"group":case"groupEnd":case"log":case"warn":console[w](E);break;default:throw new Error(`Unknown console mode: '${w}', message: '${E}'`)}}},onerror:e=>{console.error(e)},onclose:()=>{try{const e=n.store(),t=e?._dsPlugins?.fetch?.indicatorsVisible||[],s=e?._dsPlugins?.fetch?.indicatorElements&&e._dsPlugins.fetch.indicatorElements[a.id]?.value||[],o=[];s?.forEach&&s.forEach((e=>{if(!e||!t)return;const n=t.value,s=n.findIndex((t=>!!t&&e.isSameNode(t.el))),r=n[s];r&&(r.count<2?(o.push(new Promise((()=>setTimeout((()=>{e.classList.remove(Te),e.classList.add(Z)}),300)))),delete n[s]):s>-1&&(n[s].count=n[s].count-1),t.value=n.filter((e=>!!e)))})),Promise.all(o)}catch(e){console.error(e)}finally{N("plugin","backend","fetch_end",a,JSON.stringify({method:e,urlExpression:t}))}}};if("GET"===e){const e=new URLSearchParams(u.search);e.append("datastar",i),u.search=e.toString()}else d.body=i;const f=o?._dsPlugins?.fetch?.headers||{};if(d.headers)for(const[e,t]of Object.entries(f))e.startsWith("_")||(d.headers[e]=`${t}`);Gt(u,d)}const Ge=document.createElement("template");function Js(e,t,n,s,o,r){const{el:i}=e;Ge.innerHTML=s.trim(),[...Ge.content.children].forEach((s=>{if(!(s instanceof Element))throw new Error("No fragment found");const a=t=>{for(const r of t){r.classList.add(re);const t=r.outerHTML;let i=r;switch(n){case I.MorphElement:const t=ss(i,s,{callbacks:{beforeNodeRemoved:(t,n)=>(e.cleanupElementRemovals(t),!0)}});if(!t?.length)throw new Error("No morph result");i=t[0];break;case I.InnerElement:i.innerHTML=s.innerHTML;break;case I.OuterElement:i.replaceWith(s);break;case I.PrependElement:i.prepend(s);break;case I.AppendElement:i.append(s);break;case I.BeforeElement:i.before(s);break;case I.AfterElement:i.after(s);break;case I.UpsertAttributes:s.getAttributeNames().forEach((e=>{const t=s.getAttribute(e);i.setAttribute(e,t)}));break;default:throw new Error(`Unknown merge type: ${n}`)}e.cleanupElementRemovals(i),i.classList.add(re),e.applyPlugins(document.body),setTimeout((()=>{r.classList.remove(re),i.classList.remove(re)}),o);t!==i.outerHTML&&(i.classList.add(Ke),setTimeout((()=>{i.classList.remove(Ke)}),o))}};let l;if(t===Ds)l=[i];else{const e=t||`#${s.getAttribute("id")}`;if(l=document.querySelectorAll(e)||[],!l)throw new Error(`No targets found for ${e}`)}const c=[...l];if(!c.length)throw new Error(`No targets found for ${t}`);gt&&r?mt.startViewTransition((()=>a(c))):a(c)}))}const Ks=[Fs,Hs,Vs,xs,js].reduce(((e,t)=>(e[t]=(e,n,s)=>{const o=["true",!0,void 0].includes(s);qs(t,n,e,o)},e)),{isFetching:(e,t)=>{const n=[...document.querySelectorAll(t)],s=e.store()?._dsPlugins?.fetch.indicatorsVisible?.value||[];return!!n.length&&n.some((e=>s.filter((e=>!!e)).some((t=>t.el.isSameNode(e)&&t.count>0))))}}),Ye="0.19.9",ke=(e,t,n,s,o,r)=>(t-n)/(s-n)*(r-o)+o,Gs=(e,t,n,s,o,r)=>Math.round(ke(0,t,n,s,o,r)),bt=(e,t,n,s,o,r)=>Math.max(o,Math.min(r,ke(0,t,n,s,o,r))),Ys=(e,t,n,s,o,r)=>Math.round(bt(0,t,n,s,o,r)),zs={setAll:(e,t,n)=>{const s=new RegExp(t);e.walkSignals(((e,t)=>s.test(e)&&(t.value=n)))},toggleAll:(e,t)=>{const n=new RegExp(t);e.walkSignals(((e,t)=>n.test(e)&&(t.value=!t.value)))},clipboard:(e,t)=>{if(!navigator.clipboard)throw new Error("Clipboard API not available");navigator.clipboard.writeText(t)},fit:ke,fitInt:Gs,clampFit:bt,clampFitInt:Ys};function Zs(e={},...t){const n=new Dt(e,...t);return n.run(),n}function Xs(e={},...t){return Zs(Object.assign({},zs,qt,Ks,Ss,e),...[...Ws,..._s,...Wt,...t])}const Qs={bubbles:!0,cancelable:!0,composed:!0},Ae=window,N=(e,t,n,s,o,r=Qs)=>{Ae.dispatchEvent(new CustomEvent(q,Object.assign({detail:{time:new Date,category:e,subcategory:t,type:n,target:at(s),message:o}},r)))};Ae.ds||setTimeout((()=>{N("core","init","start",document.body,`Datastar v${Ye} loading`);const e=performance.now();Ae.ds=Xs();const t=performance.now();N("core","init","end",document.body,`Datastar v${Ye} loaded and attached to all DOM elements in ${(t-e).toFixed(2)}ms`);const n=document.createElement("style");n.innerHTML="\n.datastar-inspector-highlight {\n border: 2px solid blue;\n}\n",document.head.appendChild(n),window.addEventListener("datastar-inspector-event",(e=>{if("detail"in e&&"object"==typeof e.detail&&e.detail){const{detail:t}=e;if("script"in t&&"string"==typeof t.script)try{new Function(t.script)()}catch(e){console.error(e)}}}))}),0);export{qt as AttributeActions,Wt as AttributePlugins,Ks as BackendActions,Ws as BackendPlugins,Ft as BindAttributePlugin,Bt as ClassPlugin,Ct as CorePlugins,$t as CorePreprocessors,k as DATASTAR_CLASS_PREFIX,U as DATASTAR_STR,Dt as Datastar,Ut as EventPlugin,Us as FetchIndicatorPlugin,Bs as HeadersPlugin,bs as IntersectionPlugin,ws as ScrollIntoViewPlugin,gs as ShowPlugin,ys as TeleportPlugin,xt as TextPlugin,Vt as TwoWayBindingModelPlugin,Es as ViewTransitionPlugin,Ss as VisibilityActions,_s as VisibilityPlugins,ot as apply,se as argsHas,_e as argsToMs,q as datastarEventName,mt as docWithViewTransitionAPI,at as elemToSelector,Js as mergeHTMLFragment,he as remoteSignals,Zs as runDatastarWith,Xs as runDatastarWithAllPlugins,N as sendDatastarEvent,lt as storeFromPossibleContents,gt as supportsViewTransitions,ze as toHTMLorSVGElement};
|
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Client-side Image Processor
|
|
3
|
-
*
|
|
4
|
-
* Processes images before upload:
|
|
5
|
-
* - Corrects EXIF orientation
|
|
6
|
-
* - Resizes to max dimensions
|
|
7
|
-
* - Strips all metadata (privacy)
|
|
8
|
-
* - Converts to WebP format
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
window.ImageProcessor = (() => {
|
|
12
|
-
const DEFAULT_OPTIONS = {
|
|
13
|
-
maxWidth: 1920,
|
|
14
|
-
maxHeight: 1920,
|
|
15
|
-
quality: 0.85,
|
|
16
|
-
mimeType: 'image/webp',
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* EXIF Orientation values and their transformations
|
|
21
|
-
* @see https://exiftool.org/TagNames/EXIF.html
|
|
22
|
-
*/
|
|
23
|
-
const ORIENTATIONS = {
|
|
24
|
-
1: { rotate: 0, flip: false }, // Normal
|
|
25
|
-
2: { rotate: 0, flip: true }, // Flipped horizontally
|
|
26
|
-
3: { rotate: 180, flip: false }, // Rotated 180°
|
|
27
|
-
4: { rotate: 180, flip: true }, // Flipped vertically
|
|
28
|
-
5: { rotate: 90, flip: true }, // Rotated 90° CCW + flipped
|
|
29
|
-
6: { rotate: 90, flip: false }, // Rotated 90° CW
|
|
30
|
-
7: { rotate: 270, flip: true }, // Rotated 90° CW + flipped
|
|
31
|
-
8: { rotate: 270, flip: false }, // Rotated 90° CCW
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Read EXIF orientation from JPEG file
|
|
36
|
-
* @param {ArrayBuffer} buffer - File buffer
|
|
37
|
-
* @returns {number} Orientation value (1-8), defaults to 1
|
|
38
|
-
*/
|
|
39
|
-
function readExifOrientation(buffer) {
|
|
40
|
-
const view = new DataView(buffer);
|
|
41
|
-
|
|
42
|
-
// Check for JPEG SOI marker
|
|
43
|
-
if (view.getUint16(0) !== 0xFFD8) return 1;
|
|
44
|
-
|
|
45
|
-
let offset = 2;
|
|
46
|
-
const length = view.byteLength;
|
|
47
|
-
|
|
48
|
-
while (offset < length) {
|
|
49
|
-
if (view.getUint8(offset) !== 0xFF) return 1;
|
|
50
|
-
|
|
51
|
-
const marker = view.getUint8(offset + 1);
|
|
52
|
-
|
|
53
|
-
// APP1 marker (EXIF)
|
|
54
|
-
if (marker === 0xE1) {
|
|
55
|
-
const exifOffset = offset + 4;
|
|
56
|
-
|
|
57
|
-
// Check for "Exif\0\0"
|
|
58
|
-
if (
|
|
59
|
-
view.getUint32(exifOffset) !== 0x45786966 ||
|
|
60
|
-
view.getUint16(exifOffset + 4) !== 0x0000
|
|
61
|
-
) {
|
|
62
|
-
return 1;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const tiffOffset = exifOffset + 6;
|
|
66
|
-
const littleEndian = view.getUint16(tiffOffset) === 0x4949;
|
|
67
|
-
|
|
68
|
-
// Validate TIFF header
|
|
69
|
-
if (view.getUint16(tiffOffset + 2, littleEndian) !== 0x002A) return 1;
|
|
70
|
-
|
|
71
|
-
const ifdOffset = view.getUint32(tiffOffset + 4, littleEndian);
|
|
72
|
-
const numEntries = view.getUint16(tiffOffset + ifdOffset, littleEndian);
|
|
73
|
-
|
|
74
|
-
// Search for orientation tag (0x0112)
|
|
75
|
-
for (let i = 0; i < numEntries; i++) {
|
|
76
|
-
const entryOffset = tiffOffset + ifdOffset + 2 + i * 12;
|
|
77
|
-
const tag = view.getUint16(entryOffset, littleEndian);
|
|
78
|
-
|
|
79
|
-
if (tag === 0x0112) {
|
|
80
|
-
return view.getUint16(entryOffset + 8, littleEndian);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return 1;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Skip to next marker
|
|
88
|
-
if (marker === 0xD8 || marker === 0xD9) {
|
|
89
|
-
offset += 2;
|
|
90
|
-
} else {
|
|
91
|
-
offset += 2 + view.getUint16(offset + 2);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return 1;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Load image from file
|
|
100
|
-
* @param {File} file - Image file
|
|
101
|
-
* @returns {Promise<HTMLImageElement>}
|
|
102
|
-
*/
|
|
103
|
-
function loadImage(file) {
|
|
104
|
-
return new Promise((resolve, reject) => {
|
|
105
|
-
const img = new Image();
|
|
106
|
-
img.onload = () => {
|
|
107
|
-
URL.revokeObjectURL(img.src);
|
|
108
|
-
resolve(img);
|
|
109
|
-
};
|
|
110
|
-
img.onerror = () => reject(new Error('Failed to load image'));
|
|
111
|
-
img.src = URL.createObjectURL(file);
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Calculate output dimensions maintaining aspect ratio
|
|
117
|
-
* @param {number} width - Original width
|
|
118
|
-
* @param {number} height - Original height
|
|
119
|
-
* @param {number} maxWidth - Maximum width
|
|
120
|
-
* @param {number} maxHeight - Maximum height
|
|
121
|
-
* @returns {{ width: number, height: number }}
|
|
122
|
-
*/
|
|
123
|
-
function calculateDimensions(width, height, maxWidth, maxHeight) {
|
|
124
|
-
if (width <= maxWidth && height <= maxHeight) {
|
|
125
|
-
return { width, height };
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const ratio = Math.min(maxWidth / width, maxHeight / height);
|
|
129
|
-
return {
|
|
130
|
-
width: Math.round(width * ratio),
|
|
131
|
-
height: Math.round(height * ratio),
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Process image file
|
|
137
|
-
* @param {File} file - Image file to process
|
|
138
|
-
* @param {Object} options - Processing options
|
|
139
|
-
* @returns {Promise<Blob>} Processed image as WebP blob
|
|
140
|
-
*/
|
|
141
|
-
async function process(file, options = {}) {
|
|
142
|
-
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
143
|
-
|
|
144
|
-
// Read file buffer for EXIF
|
|
145
|
-
const buffer = await file.arrayBuffer();
|
|
146
|
-
const orientation = readExifOrientation(buffer);
|
|
147
|
-
const transform = ORIENTATIONS[orientation] || ORIENTATIONS[1];
|
|
148
|
-
|
|
149
|
-
// Load image
|
|
150
|
-
const img = await loadImage(file);
|
|
151
|
-
|
|
152
|
-
// For 90° or 270° rotation, swap dimensions
|
|
153
|
-
const isRotated = transform.rotate === 90 || transform.rotate === 270;
|
|
154
|
-
const srcWidth = isRotated ? img.height : img.width;
|
|
155
|
-
const srcHeight = isRotated ? img.width : img.height;
|
|
156
|
-
|
|
157
|
-
// Calculate output size
|
|
158
|
-
const { width, height } = calculateDimensions(
|
|
159
|
-
srcWidth,
|
|
160
|
-
srcHeight,
|
|
161
|
-
opts.maxWidth,
|
|
162
|
-
opts.maxHeight
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
// Create canvas
|
|
166
|
-
const canvas = document.createElement('canvas');
|
|
167
|
-
canvas.width = width;
|
|
168
|
-
canvas.height = height;
|
|
169
|
-
|
|
170
|
-
const ctx = canvas.getContext('2d');
|
|
171
|
-
if (!ctx) throw new Error('Failed to get canvas context');
|
|
172
|
-
|
|
173
|
-
// Apply transformations
|
|
174
|
-
ctx.save();
|
|
175
|
-
|
|
176
|
-
// Move to center for rotation
|
|
177
|
-
ctx.translate(width / 2, height / 2);
|
|
178
|
-
|
|
179
|
-
// Apply rotation
|
|
180
|
-
if (transform.rotate) {
|
|
181
|
-
ctx.rotate((transform.rotate * Math.PI) / 180);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Apply flip
|
|
185
|
-
if (transform.flip) {
|
|
186
|
-
ctx.scale(-1, 1);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Draw image centered
|
|
190
|
-
const drawWidth = isRotated ? height : width;
|
|
191
|
-
const drawHeight = isRotated ? width : height;
|
|
192
|
-
ctx.drawImage(img, -drawWidth / 2, -drawHeight / 2, drawWidth, drawHeight);
|
|
193
|
-
|
|
194
|
-
ctx.restore();
|
|
195
|
-
|
|
196
|
-
// Export as WebP
|
|
197
|
-
return new Promise((resolve, reject) => {
|
|
198
|
-
canvas.toBlob(
|
|
199
|
-
(blob) => {
|
|
200
|
-
if (blob) {
|
|
201
|
-
resolve(blob);
|
|
202
|
-
} else {
|
|
203
|
-
reject(new Error('Failed to create blob'));
|
|
204
|
-
}
|
|
205
|
-
},
|
|
206
|
-
opts.mimeType,
|
|
207
|
-
opts.quality
|
|
208
|
-
);
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Process file and create a new File object
|
|
214
|
-
* @param {File} file - Original file
|
|
215
|
-
* @param {Object} options - Processing options
|
|
216
|
-
* @returns {Promise<File>} Processed file with .webp extension
|
|
217
|
-
*/
|
|
218
|
-
async function processToFile(file, options = {}) {
|
|
219
|
-
const blob = await process(file, options);
|
|
220
|
-
|
|
221
|
-
// Generate new filename with .webp extension
|
|
222
|
-
const originalName = file.name.replace(/\.[^.]+$/, '');
|
|
223
|
-
const newName = `${originalName}.webp`;
|
|
224
|
-
|
|
225
|
-
return new File([blob], newName, { type: 'image/webp' });
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return { process, processToFile };
|
|
229
|
-
})();
|
|
230
|
-
|
|
231
|
-
// Also export for module systems
|
|
232
|
-
if (typeof module !== 'undefined' && module.exports) {
|
|
233
|
-
module.exports = window.ImageProcessor;
|
|
234
|
-
}
|