@autumnsgrove/groveengine 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +163 -0
- package/dist/auth/jwt.d.ts +14 -0
- package/dist/auth/jwt.js +109 -0
- package/dist/auth/session.d.ts +42 -0
- package/dist/auth/session.js +105 -0
- package/dist/components/admin/GutterManager.svelte +910 -0
- package/dist/components/admin/GutterManager.svelte.d.ts +15 -0
- package/dist/components/admin/MarkdownEditor.svelte +3114 -0
- package/dist/components/admin/MarkdownEditor.svelte.d.ts +43 -0
- package/dist/components/custom/CollapsibleSection.svelte +74 -0
- package/dist/components/custom/CollapsibleSection.svelte.d.ts +15 -0
- package/dist/components/custom/ContentWithGutter.svelte +646 -0
- package/dist/components/custom/ContentWithGutter.svelte.d.ts +19 -0
- package/dist/components/custom/GutterItem.svelte +201 -0
- package/dist/components/custom/GutterItem.svelte.d.ts +11 -0
- package/dist/components/custom/LeftGutter.svelte +271 -0
- package/dist/components/custom/LeftGutter.svelte.d.ts +17 -0
- package/dist/components/custom/MobileTOC.svelte +273 -0
- package/dist/components/custom/MobileTOC.svelte.d.ts +11 -0
- package/dist/components/custom/TableOfContents.svelte +163 -0
- package/dist/components/custom/TableOfContents.svelte.d.ts +11 -0
- package/dist/components/gallery/ImageGallery.svelte +681 -0
- package/dist/components/gallery/ImageGallery.svelte.d.ts +11 -0
- package/dist/components/gallery/Lightbox.svelte +107 -0
- package/dist/components/gallery/Lightbox.svelte.d.ts +19 -0
- package/dist/components/gallery/LightboxCaption.svelte +25 -0
- package/dist/components/gallery/LightboxCaption.svelte.d.ts +11 -0
- package/dist/components/gallery/ZoomableImage.svelte +163 -0
- package/dist/components/gallery/ZoomableImage.svelte.d.ts +17 -0
- package/dist/components/ui/Accordion.svelte +74 -0
- package/dist/components/ui/Accordion.svelte.d.ts +42 -0
- package/dist/components/ui/Badge.svelte +48 -0
- package/dist/components/ui/Badge.svelte.d.ts +26 -0
- package/dist/components/ui/Button.svelte +74 -0
- package/dist/components/ui/Button.svelte.d.ts +34 -0
- package/dist/components/ui/Card.svelte +102 -0
- package/dist/components/ui/Card.svelte.d.ts +46 -0
- package/dist/components/ui/Dialog.svelte +91 -0
- package/dist/components/ui/Dialog.svelte.d.ts +43 -0
- package/dist/components/ui/Input.svelte +81 -0
- package/dist/components/ui/Input.svelte.d.ts +35 -0
- package/dist/components/ui/Select.svelte +69 -0
- package/dist/components/ui/Select.svelte.d.ts +36 -0
- package/dist/components/ui/Sheet.svelte +98 -0
- package/dist/components/ui/Sheet.svelte.d.ts +45 -0
- package/dist/components/ui/Skeleton.svelte +31 -0
- package/dist/components/ui/Skeleton.svelte.d.ts +26 -0
- package/dist/components/ui/Table.svelte +59 -0
- package/dist/components/ui/Table.svelte.d.ts +44 -0
- package/dist/components/ui/Tabs.svelte +76 -0
- package/dist/components/ui/Tabs.svelte.d.ts +41 -0
- package/dist/components/ui/Textarea.svelte +81 -0
- package/dist/components/ui/Textarea.svelte.d.ts +35 -0
- package/dist/components/ui/Toast.svelte +18 -0
- package/dist/components/ui/Toast.svelte.d.ts +7 -0
- package/dist/components/ui/accordion/accordion-content.svelte +24 -0
- package/dist/components/ui/accordion/accordion-content.svelte.d.ts +4 -0
- package/dist/components/ui/accordion/accordion-item.svelte +12 -0
- package/dist/components/ui/accordion/accordion-item.svelte.d.ts +4 -0
- package/dist/components/ui/accordion/accordion-trigger.svelte +29 -0
- package/dist/components/ui/accordion/accordion-trigger.svelte.d.ts +7 -0
- package/dist/components/ui/accordion/index.d.ts +6 -0
- package/dist/components/ui/accordion/index.js +8 -0
- package/dist/components/ui/badge/badge.svelte +50 -0
- package/dist/components/ui/badge/badge.svelte.d.ts +60 -0
- package/dist/components/ui/badge/index.d.ts +2 -0
- package/dist/components/ui/badge/index.js +2 -0
- package/dist/components/ui/button/button.svelte +82 -0
- package/dist/components/ui/button/button.svelte.d.ts +132 -0
- package/dist/components/ui/button/index.d.ts +2 -0
- package/dist/components/ui/button/index.js +4 -0
- package/dist/components/ui/card/card-content.svelte +16 -0
- package/dist/components/ui/card/card-content.svelte.d.ts +5 -0
- package/dist/components/ui/card/card-description.svelte +16 -0
- package/dist/components/ui/card/card-description.svelte.d.ts +5 -0
- package/dist/components/ui/card/card-footer.svelte +16 -0
- package/dist/components/ui/card/card-footer.svelte.d.ts +5 -0
- package/dist/components/ui/card/card-header.svelte +16 -0
- package/dist/components/ui/card/card-header.svelte.d.ts +5 -0
- package/dist/components/ui/card/card-title.svelte +25 -0
- package/dist/components/ui/card/card-title.svelte.d.ts +8 -0
- package/dist/components/ui/card/card.svelte +20 -0
- package/dist/components/ui/card/card.svelte.d.ts +5 -0
- package/dist/components/ui/card/index.d.ts +7 -0
- package/dist/components/ui/card/index.js +9 -0
- package/dist/components/ui/dialog/dialog-content.svelte +38 -0
- package/dist/components/ui/dialog/dialog-content.svelte.d.ts +9 -0
- package/dist/components/ui/dialog/dialog-description.svelte +16 -0
- package/dist/components/ui/dialog/dialog-description.svelte.d.ts +4 -0
- package/dist/components/ui/dialog/dialog-footer.svelte +20 -0
- package/dist/components/ui/dialog/dialog-footer.svelte.d.ts +5 -0
- package/dist/components/ui/dialog/dialog-header.svelte +20 -0
- package/dist/components/ui/dialog/dialog-header.svelte.d.ts +5 -0
- package/dist/components/ui/dialog/dialog-overlay.svelte +19 -0
- package/dist/components/ui/dialog/dialog-overlay.svelte.d.ts +4 -0
- package/dist/components/ui/dialog/dialog-title.svelte +16 -0
- package/dist/components/ui/dialog/dialog-title.svelte.d.ts +4 -0
- package/dist/components/ui/dialog/index.d.ts +12 -0
- package/dist/components/ui/dialog/index.js +14 -0
- package/dist/components/ui/index.d.ts +26 -0
- package/dist/components/ui/index.js +29 -0
- package/dist/components/ui/input/index.d.ts +2 -0
- package/dist/components/ui/input/index.js +4 -0
- package/dist/components/ui/input/input.svelte +46 -0
- package/dist/components/ui/input/input.svelte.d.ts +13 -0
- package/dist/components/ui/select/index.d.ts +11 -0
- package/dist/components/ui/select/index.js +13 -0
- package/dist/components/ui/select/select-content.svelte +39 -0
- package/dist/components/ui/select/select-content.svelte.d.ts +7 -0
- package/dist/components/ui/select/select-group-heading.svelte +16 -0
- package/dist/components/ui/select/select-group-heading.svelte.d.ts +4 -0
- package/dist/components/ui/select/select-item.svelte +37 -0
- package/dist/components/ui/select/select-item.svelte.d.ts +4 -0
- package/dist/components/ui/select/select-scroll-down-button.svelte +19 -0
- package/dist/components/ui/select/select-scroll-down-button.svelte.d.ts +4 -0
- package/dist/components/ui/select/select-scroll-up-button.svelte +19 -0
- package/dist/components/ui/select/select-scroll-up-button.svelte.d.ts +4 -0
- package/dist/components/ui/select/select-separator.svelte +13 -0
- package/dist/components/ui/select/select-separator.svelte.d.ts +4 -0
- package/dist/components/ui/select/select-trigger.svelte +24 -0
- package/dist/components/ui/select/select-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/separator/index.d.ts +2 -0
- package/dist/components/ui/separator/index.js +4 -0
- package/dist/components/ui/separator/separator.svelte +22 -0
- package/dist/components/ui/separator/separator.svelte.d.ts +4 -0
- package/dist/components/ui/sheet/index.d.ts +12 -0
- package/dist/components/ui/sheet/index.js +14 -0
- package/dist/components/ui/sheet/sheet-content.svelte +53 -0
- package/dist/components/ui/sheet/sheet-content.svelte.d.ts +62 -0
- package/dist/components/ui/sheet/sheet-description.svelte +16 -0
- package/dist/components/ui/sheet/sheet-description.svelte.d.ts +4 -0
- package/dist/components/ui/sheet/sheet-footer.svelte +20 -0
- package/dist/components/ui/sheet/sheet-footer.svelte.d.ts +5 -0
- package/dist/components/ui/sheet/sheet-header.svelte +20 -0
- package/dist/components/ui/sheet/sheet-header.svelte.d.ts +5 -0
- package/dist/components/ui/sheet/sheet-overlay.svelte +21 -0
- package/dist/components/ui/sheet/sheet-overlay.svelte.d.ts +6 -0
- package/dist/components/ui/sheet/sheet-title.svelte +16 -0
- package/dist/components/ui/sheet/sheet-title.svelte.d.ts +4 -0
- package/dist/components/ui/skeleton/index.d.ts +2 -0
- package/dist/components/ui/skeleton/index.js +4 -0
- package/dist/components/ui/skeleton/skeleton.svelte +17 -0
- package/dist/components/ui/skeleton/skeleton.svelte.d.ts +5 -0
- package/dist/components/ui/table/index.d.ts +9 -0
- package/dist/components/ui/table/index.js +11 -0
- package/dist/components/ui/table/table-body.svelte +16 -0
- package/dist/components/ui/table/table-body.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-caption.svelte +16 -0
- package/dist/components/ui/table/table-caption.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-cell.svelte +20 -0
- package/dist/components/ui/table/table-cell.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-footer.svelte +16 -0
- package/dist/components/ui/table/table-footer.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-head.svelte +23 -0
- package/dist/components/ui/table/table-head.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-header.svelte +16 -0
- package/dist/components/ui/table/table-header.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-row.svelte +23 -0
- package/dist/components/ui/table/table-row.svelte.d.ts +5 -0
- package/dist/components/ui/table/table.svelte +18 -0
- package/dist/components/ui/table/table.svelte.d.ts +5 -0
- package/dist/components/ui/tabs/index.d.ts +6 -0
- package/dist/components/ui/tabs/index.js +8 -0
- package/dist/components/ui/tabs/tabs-content.svelte +19 -0
- package/dist/components/ui/tabs/tabs-content.svelte.d.ts +4 -0
- package/dist/components/ui/tabs/tabs-list.svelte +19 -0
- package/dist/components/ui/tabs/tabs-list.svelte.d.ts +4 -0
- package/dist/components/ui/tabs/tabs-trigger.svelte +19 -0
- package/dist/components/ui/tabs/tabs-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/textarea/index.d.ts +2 -0
- package/dist/components/ui/textarea/index.js +4 -0
- package/dist/components/ui/textarea/textarea.svelte +24 -0
- package/dist/components/ui/textarea/textarea.svelte.d.ts +6 -0
- package/dist/components/ui/toast.d.ts +86 -0
- package/dist/components/ui/toast.js +99 -0
- package/dist/db/schema.sql +238 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +20 -0
- package/dist/payments/index.d.ts +33 -0
- package/dist/payments/index.js +47 -0
- package/dist/payments/shop.d.ts +165 -0
- package/dist/payments/shop.js +588 -0
- package/dist/payments/stripe/client.d.ts +231 -0
- package/dist/payments/stripe/client.js +198 -0
- package/dist/payments/stripe/index.d.ts +18 -0
- package/dist/payments/stripe/index.js +17 -0
- package/dist/payments/stripe/provider.d.ts +50 -0
- package/dist/payments/stripe/provider.js +530 -0
- package/dist/payments/types.d.ts +355 -0
- package/dist/payments/types.js +7 -0
- package/dist/server/logger.d.ts +53 -0
- package/dist/server/logger.js +252 -0
- package/dist/styles/content.css +514 -0
- package/dist/styles/tokens.css +175 -0
- package/dist/utils/api.d.ts +20 -0
- package/dist/utils/api.js +109 -0
- package/dist/utils/cn.d.ts +15 -0
- package/dist/utils/cn.js +18 -0
- package/dist/utils/csrf.d.ts +22 -0
- package/dist/utils/csrf.js +72 -0
- package/dist/utils/debounce.d.ts +7 -0
- package/dist/utils/debounce.js +14 -0
- package/dist/utils/gallery.d.ts +66 -0
- package/dist/utils/gallery.js +181 -0
- package/dist/utils/gutter.d.ts +54 -0
- package/dist/utils/gutter.js +169 -0
- package/dist/utils/imageProcessor.d.ts +58 -0
- package/dist/utils/imageProcessor.js +205 -0
- package/dist/utils/json.d.ts +17 -0
- package/dist/utils/json.js +26 -0
- package/dist/utils/markdown.d.ts +101 -0
- package/dist/utils/markdown.js +947 -0
- package/dist/utils/sanitize.d.ts +25 -0
- package/dist/utils/sanitize.js +127 -0
- package/dist/utils/validation.d.ts +46 -0
- package/dist/utils/validation.js +169 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +5 -0
- package/package.json +129 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side API utility with automatic CSRF token injection
|
|
3
|
+
* Provides fetch wrapper with security headers and error handling
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Fetch wrapper with automatic CSRF token injection
|
|
8
|
+
* @param {string} url - API endpoint URL
|
|
9
|
+
* @param {RequestInit} options - Fetch options
|
|
10
|
+
* @returns {Promise<any>} Response JSON
|
|
11
|
+
* @throws {Error} If request fails
|
|
12
|
+
*/
|
|
13
|
+
export async function apiRequest(url, options = {}) {
|
|
14
|
+
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;
|
|
15
|
+
|
|
16
|
+
// Build headers - don't set Content-Type for FormData (browser sets it with boundary)
|
|
17
|
+
const headers = {
|
|
18
|
+
...(csrfToken && { 'X-CSRF-Token': csrfToken }),
|
|
19
|
+
...options.headers
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Only add Content-Type if not FormData
|
|
23
|
+
if (!(options.body instanceof FormData)) {
|
|
24
|
+
headers['Content-Type'] = 'application/json';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const response = await fetch(url, {
|
|
28
|
+
...options,
|
|
29
|
+
headers
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
let errorMessage = 'Request failed';
|
|
34
|
+
try {
|
|
35
|
+
const error = await response.json();
|
|
36
|
+
errorMessage = error.message || errorMessage;
|
|
37
|
+
} catch {
|
|
38
|
+
errorMessage = `${response.status} ${response.statusText}`;
|
|
39
|
+
}
|
|
40
|
+
throw new Error(errorMessage);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Handle empty responses (204 No Content)
|
|
44
|
+
if (response.status === 204) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return response.json();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Convenience methods for common HTTP verbs
|
|
53
|
+
*/
|
|
54
|
+
export const api = {
|
|
55
|
+
/**
|
|
56
|
+
* GET request
|
|
57
|
+
* @param {string} url - API endpoint
|
|
58
|
+
* @param {RequestInit} options - Additional fetch options
|
|
59
|
+
*/
|
|
60
|
+
get: (url, options = {}) =>
|
|
61
|
+
apiRequest(url, { ...options, method: 'GET' }),
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* POST request
|
|
65
|
+
* @param {string} url - API endpoint
|
|
66
|
+
* @param {any} body - Request body (will be JSON stringified)
|
|
67
|
+
* @param {RequestInit} options - Additional fetch options
|
|
68
|
+
*/
|
|
69
|
+
post: (url, body, options = {}) =>
|
|
70
|
+
apiRequest(url, {
|
|
71
|
+
...options,
|
|
72
|
+
method: 'POST',
|
|
73
|
+
body: JSON.stringify(body)
|
|
74
|
+
}),
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* PUT request
|
|
78
|
+
* @param {string} url - API endpoint
|
|
79
|
+
* @param {any} body - Request body (will be JSON stringified)
|
|
80
|
+
* @param {RequestInit} options - Additional fetch options
|
|
81
|
+
*/
|
|
82
|
+
put: (url, body, options = {}) =>
|
|
83
|
+
apiRequest(url, {
|
|
84
|
+
...options,
|
|
85
|
+
method: 'PUT',
|
|
86
|
+
body: JSON.stringify(body)
|
|
87
|
+
}),
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* DELETE request
|
|
91
|
+
* @param {string} url - API endpoint
|
|
92
|
+
* @param {RequestInit} options - Additional fetch options
|
|
93
|
+
*/
|
|
94
|
+
delete: (url, options = {}) =>
|
|
95
|
+
apiRequest(url, { ...options, method: 'DELETE' }),
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* PATCH request
|
|
99
|
+
* @param {string} url - API endpoint
|
|
100
|
+
* @param {any} body - Request body (will be JSON stringified)
|
|
101
|
+
* @param {RequestInit} options - Additional fetch options
|
|
102
|
+
*/
|
|
103
|
+
patch: (url, body, options = {}) =>
|
|
104
|
+
apiRequest(url, {
|
|
105
|
+
...options,
|
|
106
|
+
method: 'PATCH',
|
|
107
|
+
body: JSON.stringify(body)
|
|
108
|
+
})
|
|
109
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ClassValue } from "clsx";
|
|
2
|
+
/**
|
|
3
|
+
* Utility function for merging Tailwind CSS classes
|
|
4
|
+
*
|
|
5
|
+
* Combines clsx for conditional class logic and tailwind-merge to
|
|
6
|
+
* handle Tailwind class conflicts intelligently.
|
|
7
|
+
*
|
|
8
|
+
* @param inputs - Class values to merge (strings, objects, arrays)
|
|
9
|
+
* @returns Merged class string
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* cn('px-2 py-1', className)
|
|
13
|
+
* cn('text-blue-500', { 'text-red-500': isError })
|
|
14
|
+
*/
|
|
15
|
+
export declare function cn(...inputs: ClassValue[]): string;
|
package/dist/utils/cn.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { clsx } from "clsx";
|
|
2
|
+
import { twMerge } from "tailwind-merge";
|
|
3
|
+
/**
|
|
4
|
+
* Utility function for merging Tailwind CSS classes
|
|
5
|
+
*
|
|
6
|
+
* Combines clsx for conditional class logic and tailwind-merge to
|
|
7
|
+
* handle Tailwind class conflicts intelligently.
|
|
8
|
+
*
|
|
9
|
+
* @param inputs - Class values to merge (strings, objects, arrays)
|
|
10
|
+
* @returns Merged class string
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* cn('px-2 py-1', className)
|
|
14
|
+
* cn('text-blue-500', { 'text-red-500': isError })
|
|
15
|
+
*/
|
|
16
|
+
export function cn(...inputs) {
|
|
17
|
+
return twMerge(clsx(inputs));
|
|
18
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSRF Protection Utilities
|
|
3
|
+
* Validates requests come from same origin
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Generate cryptographically secure CSRF token
|
|
7
|
+
* @returns {string} UUID v4 token
|
|
8
|
+
*/
|
|
9
|
+
export function generateCSRFToken(): string;
|
|
10
|
+
/**
|
|
11
|
+
* Validate CSRF token from request against session token
|
|
12
|
+
* @param {Request} request - The incoming request
|
|
13
|
+
* @param {string} sessionToken - The token stored in the session
|
|
14
|
+
* @returns {boolean}
|
|
15
|
+
*/
|
|
16
|
+
export function validateCSRFToken(request: Request, sessionToken: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Validate CSRF token from request headers (origin-based fallback)
|
|
19
|
+
* @param {Request} request
|
|
20
|
+
* @returns {boolean}
|
|
21
|
+
*/
|
|
22
|
+
export function validateCSRF(request: Request): boolean;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSRF Protection Utilities
|
|
3
|
+
* Validates requests come from same origin
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generate cryptographically secure CSRF token
|
|
8
|
+
* @returns {string} UUID v4 token
|
|
9
|
+
*/
|
|
10
|
+
export function generateCSRFToken() {
|
|
11
|
+
return crypto.randomUUID();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Validate CSRF token from request against session token
|
|
16
|
+
* @param {Request} request - The incoming request
|
|
17
|
+
* @param {string} sessionToken - The token stored in the session
|
|
18
|
+
* @returns {boolean}
|
|
19
|
+
*/
|
|
20
|
+
export function validateCSRFToken(request, sessionToken) {
|
|
21
|
+
if (!sessionToken) return false;
|
|
22
|
+
|
|
23
|
+
const headerToken = request.headers.get('x-csrf-token');
|
|
24
|
+
const bodyToken = request.headers.get('csrf-token'); // fallback
|
|
25
|
+
|
|
26
|
+
if (!headerToken && !bodyToken) return false;
|
|
27
|
+
|
|
28
|
+
return (headerToken === sessionToken) || (bodyToken === sessionToken);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Validate CSRF token from request headers (origin-based fallback)
|
|
33
|
+
* @param {Request} request
|
|
34
|
+
* @returns {boolean}
|
|
35
|
+
*/
|
|
36
|
+
export function validateCSRF(request) {
|
|
37
|
+
// Handle edge cases
|
|
38
|
+
if (!request || typeof request !== 'object') {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!request.headers || typeof request.headers.get !== 'function') {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const origin = request.headers.get('origin');
|
|
47
|
+
const host = request.headers.get('host');
|
|
48
|
+
|
|
49
|
+
// Allow same-origin requests
|
|
50
|
+
if (origin) {
|
|
51
|
+
try {
|
|
52
|
+
const originUrl = new URL(origin);
|
|
53
|
+
|
|
54
|
+
// Validate protocol (must be http or https)
|
|
55
|
+
if (!['http:', 'https:'].includes(originUrl.protocol)) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const isLocalhost = originUrl.hostname === 'localhost' ||
|
|
60
|
+
originUrl.hostname === '127.0.0.1';
|
|
61
|
+
const hostMatches = host && originUrl.host === host;
|
|
62
|
+
|
|
63
|
+
if (!isLocalhost && !hostMatches) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Debounce function calls
|
|
3
|
+
* @param {Function} fn - Function to debounce
|
|
4
|
+
* @param {number} delay - Delay in milliseconds
|
|
5
|
+
* @returns {Function} Debounced function
|
|
6
|
+
*/
|
|
7
|
+
export function debounce(fn, delay = 300) {
|
|
8
|
+
let timeoutId;
|
|
9
|
+
|
|
10
|
+
return function (...args) {
|
|
11
|
+
clearTimeout(timeoutId);
|
|
12
|
+
timeoutId = setTimeout(() => fn.apply(this, args), delay);
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gallery utilities for filename parsing and filtering
|
|
3
|
+
* Extracts metadata from smart filenames like:
|
|
4
|
+
* - 2025-01-15_forest-walk.jpg → date: 2025-01-15, slug: forest-walk
|
|
5
|
+
* - minecraft/build_2024-12-01.png → category: minecraft, date: 2024-12-01
|
|
6
|
+
* - selfies/2024-summer.jpg → category: selfies, slug: 2024-summer
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Parse a filename to extract metadata
|
|
10
|
+
* @param {string} r2Key - R2 object key (e.g., 'minecraft/build.png' or '2025-01-15_photo.jpg')
|
|
11
|
+
* @returns {Object} Parsed metadata: { category, date, slug, filename, extension }
|
|
12
|
+
*/
|
|
13
|
+
export function parseImageFilename(r2Key: string): Object;
|
|
14
|
+
/**
|
|
15
|
+
* Get display title for an image (uses custom title or parsed slug)
|
|
16
|
+
* @param {Object} image - Gallery image object
|
|
17
|
+
* @returns {string} Human-readable title
|
|
18
|
+
*/
|
|
19
|
+
export function getImageTitle(image: Object): string;
|
|
20
|
+
/**
|
|
21
|
+
* Get display date for an image (uses custom date or parsed date)
|
|
22
|
+
* @param {Object} image - Gallery image object
|
|
23
|
+
* @returns {string|null} YYYY-MM-DD date string or null
|
|
24
|
+
*/
|
|
25
|
+
export function getImageDate(image: Object): string | null;
|
|
26
|
+
/**
|
|
27
|
+
* Filter images by search query (searches title, slug, filename)
|
|
28
|
+
* @param {Array} images - Array of gallery images
|
|
29
|
+
* @param {string} query - Search query
|
|
30
|
+
* @returns {Array} Filtered images
|
|
31
|
+
*/
|
|
32
|
+
export function searchImages(images: any[], query: string): any[];
|
|
33
|
+
/**
|
|
34
|
+
* Filter images by date range
|
|
35
|
+
* @param {Array} images - Array of gallery images
|
|
36
|
+
* @param {string|null} startDate - YYYY-MM-DD start date (inclusive)
|
|
37
|
+
* @param {string|null} endDate - YYYY-MM-DD end date (inclusive)
|
|
38
|
+
* @returns {Array} Filtered images
|
|
39
|
+
*/
|
|
40
|
+
export function filterImagesByDateRange(images: any[], startDate: string | null, endDate: string | null): any[];
|
|
41
|
+
/**
|
|
42
|
+
* Filter images by tags
|
|
43
|
+
* @param {Array} images - Array of gallery images (must include 'tags' array)
|
|
44
|
+
* @param {Array} tagSlugs - Array of tag slugs to filter by
|
|
45
|
+
* @returns {Array} Filtered images
|
|
46
|
+
*/
|
|
47
|
+
export function filterImagesByTags(images: any[], tagSlugs: any[]): any[];
|
|
48
|
+
/**
|
|
49
|
+
* Filter images by category (parsed from path)
|
|
50
|
+
* @param {Array} images - Array of gallery images
|
|
51
|
+
* @param {string|null} category - Category to filter by
|
|
52
|
+
* @returns {Array} Filtered images
|
|
53
|
+
*/
|
|
54
|
+
export function filterImagesByCategory(images: any[], category: string | null): any[];
|
|
55
|
+
/**
|
|
56
|
+
* Extract unique years from image dates
|
|
57
|
+
* @param {Array} images - Array of gallery images
|
|
58
|
+
* @returns {Array} Sorted array of years (descending)
|
|
59
|
+
*/
|
|
60
|
+
export function getAvailableYears(images: any[]): any[];
|
|
61
|
+
/**
|
|
62
|
+
* Extract unique categories from images
|
|
63
|
+
* @param {Array} images - Array of gallery images
|
|
64
|
+
* @returns {Array} Sorted array of categories
|
|
65
|
+
*/
|
|
66
|
+
export function getAvailableCategories(images: any[]): any[];
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gallery utilities for filename parsing and filtering
|
|
3
|
+
* Extracts metadata from smart filenames like:
|
|
4
|
+
* - 2025-01-15_forest-walk.jpg → date: 2025-01-15, slug: forest-walk
|
|
5
|
+
* - minecraft/build_2024-12-01.png → category: minecraft, date: 2024-12-01
|
|
6
|
+
* - selfies/2024-summer.jpg → category: selfies, slug: 2024-summer
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Parse a filename to extract metadata
|
|
11
|
+
* @param {string} r2Key - R2 object key (e.g., 'minecraft/build.png' or '2025-01-15_photo.jpg')
|
|
12
|
+
* @returns {Object} Parsed metadata: { category, date, slug, filename, extension }
|
|
13
|
+
*/
|
|
14
|
+
export function parseImageFilename(r2Key) {
|
|
15
|
+
const parts = r2Key.split('/');
|
|
16
|
+
const filename = parts[parts.length - 1];
|
|
17
|
+
const category = parts.length > 1 ? parts[0] : null;
|
|
18
|
+
|
|
19
|
+
// Extract extension
|
|
20
|
+
const extMatch = filename.match(/\.([^.]+)$/);
|
|
21
|
+
const extension = extMatch ? extMatch[1] : null;
|
|
22
|
+
const nameWithoutExt = filename.replace(/\.[^.]+$/, '');
|
|
23
|
+
|
|
24
|
+
// Try to extract date (YYYY-MM-DD or YYYY_MM_DD)
|
|
25
|
+
const dateMatch = nameWithoutExt.match(/(\d{4}[-_]\d{2}[-_]\d{2})/);
|
|
26
|
+
const date = dateMatch ? dateMatch[1].replace(/_/g, '-') : null;
|
|
27
|
+
|
|
28
|
+
// Extract slug (remove date prefix if present)
|
|
29
|
+
let slug = nameWithoutExt;
|
|
30
|
+
if (date) {
|
|
31
|
+
slug = slug.replace(date, '').replace(/^[-_]+/, '').replace(/[-_]+$/, '');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Clean up slug (convert underscores to hyphens)
|
|
35
|
+
slug = slug.replace(/_/g, '-').toLowerCase();
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
category,
|
|
39
|
+
date,
|
|
40
|
+
slug: slug || 'untitled',
|
|
41
|
+
filename,
|
|
42
|
+
extension,
|
|
43
|
+
r2Key
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get display title for an image (uses custom title or parsed slug)
|
|
49
|
+
* @param {Object} image - Gallery image object
|
|
50
|
+
* @returns {string} Human-readable title
|
|
51
|
+
*/
|
|
52
|
+
export function getImageTitle(image) {
|
|
53
|
+
if (image.custom_title) return image.custom_title;
|
|
54
|
+
if (image.parsed_slug) {
|
|
55
|
+
// Convert slug to Title Case
|
|
56
|
+
return image.parsed_slug
|
|
57
|
+
.split('-')
|
|
58
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
59
|
+
.join(' ');
|
|
60
|
+
}
|
|
61
|
+
return image.r2_key?.split('/').pop() || image.key?.split('/').pop() || 'Untitled';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get display date for an image (uses custom date or parsed date)
|
|
66
|
+
* @param {Object} image - Gallery image object
|
|
67
|
+
* @returns {string|null} YYYY-MM-DD date string or null
|
|
68
|
+
*/
|
|
69
|
+
export function getImageDate(image) {
|
|
70
|
+
return image.custom_date || image.parsed_date || null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Filter images by search query (searches title, slug, filename)
|
|
75
|
+
* @param {Array} images - Array of gallery images
|
|
76
|
+
* @param {string} query - Search query
|
|
77
|
+
* @returns {Array} Filtered images
|
|
78
|
+
*/
|
|
79
|
+
export function searchImages(images, query) {
|
|
80
|
+
if (!query) return images;
|
|
81
|
+
|
|
82
|
+
const lowerQuery = query.toLowerCase();
|
|
83
|
+
|
|
84
|
+
return images.filter((img) => {
|
|
85
|
+
const title = getImageTitle(img).toLowerCase();
|
|
86
|
+
const slug = (img.parsed_slug || '').toLowerCase();
|
|
87
|
+
const filename = (img.r2_key || img.key || '').toLowerCase();
|
|
88
|
+
const description = (img.custom_description || '').toLowerCase();
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
title.includes(lowerQuery) ||
|
|
92
|
+
slug.includes(lowerQuery) ||
|
|
93
|
+
filename.includes(lowerQuery) ||
|
|
94
|
+
description.includes(lowerQuery)
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Filter images by date range
|
|
101
|
+
* @param {Array} images - Array of gallery images
|
|
102
|
+
* @param {string|null} startDate - YYYY-MM-DD start date (inclusive)
|
|
103
|
+
* @param {string|null} endDate - YYYY-MM-DD end date (inclusive)
|
|
104
|
+
* @returns {Array} Filtered images
|
|
105
|
+
*/
|
|
106
|
+
export function filterImagesByDateRange(images, startDate, endDate) {
|
|
107
|
+
if (!startDate && !endDate) return images;
|
|
108
|
+
|
|
109
|
+
return images.filter((img) => {
|
|
110
|
+
const imgDate = getImageDate(img);
|
|
111
|
+
if (!imgDate) return false;
|
|
112
|
+
|
|
113
|
+
if (startDate && imgDate < startDate) return false;
|
|
114
|
+
if (endDate && imgDate > endDate) return false;
|
|
115
|
+
|
|
116
|
+
return true;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Filter images by tags
|
|
122
|
+
* @param {Array} images - Array of gallery images (must include 'tags' array)
|
|
123
|
+
* @param {Array} tagSlugs - Array of tag slugs to filter by
|
|
124
|
+
* @returns {Array} Filtered images
|
|
125
|
+
*/
|
|
126
|
+
export function filterImagesByTags(images, tagSlugs) {
|
|
127
|
+
if (!tagSlugs || tagSlugs.length === 0) return images;
|
|
128
|
+
|
|
129
|
+
return images.filter((img) => {
|
|
130
|
+
if (!img.tags || img.tags.length === 0) return false;
|
|
131
|
+
const imgTagSlugs = img.tags.map((t) => t.slug);
|
|
132
|
+
return tagSlugs.every((slug) => imgTagSlugs.includes(slug));
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Filter images by category (parsed from path)
|
|
138
|
+
* @param {Array} images - Array of gallery images
|
|
139
|
+
* @param {string|null} category - Category to filter by
|
|
140
|
+
* @returns {Array} Filtered images
|
|
141
|
+
*/
|
|
142
|
+
export function filterImagesByCategory(images, category) {
|
|
143
|
+
if (!category) return images;
|
|
144
|
+
return images.filter((img) => img.parsed_category === category);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Extract unique years from image dates
|
|
149
|
+
* @param {Array} images - Array of gallery images
|
|
150
|
+
* @returns {Array} Sorted array of years (descending)
|
|
151
|
+
*/
|
|
152
|
+
export function getAvailableYears(images) {
|
|
153
|
+
const years = new Set();
|
|
154
|
+
|
|
155
|
+
images.forEach((img) => {
|
|
156
|
+
const date = getImageDate(img);
|
|
157
|
+
if (date) {
|
|
158
|
+
const year = date.substring(0, 4);
|
|
159
|
+
years.add(year);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return Array.from(years).sort((a, b) => b.localeCompare(a));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Extract unique categories from images
|
|
168
|
+
* @param {Array} images - Array of gallery images
|
|
169
|
+
* @returns {Array} Sorted array of categories
|
|
170
|
+
*/
|
|
171
|
+
export function getAvailableCategories(images) {
|
|
172
|
+
const categories = new Set();
|
|
173
|
+
|
|
174
|
+
images.forEach((img) => {
|
|
175
|
+
if (img.parsed_category) {
|
|
176
|
+
categories.add(img.parsed_category);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
return Array.from(categories).sort();
|
|
181
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Gutter Utilities
|
|
3
|
+
*
|
|
4
|
+
* This module provides common utilities for gutter content positioning
|
|
5
|
+
* and anchor resolution. Used by ContentWithGutter component and related
|
|
6
|
+
* functionality across the site.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Parse anchor string to determine anchor type and value
|
|
10
|
+
* @param {string} anchor - The anchor string from manifest
|
|
11
|
+
* @returns {Object} Object with type and value properties
|
|
12
|
+
*/
|
|
13
|
+
export function parseAnchor(anchor: string): Object;
|
|
14
|
+
/**
|
|
15
|
+
* Generate a unique key for an anchor (used for grouping and positioning)
|
|
16
|
+
* @param {string} anchor - The anchor string
|
|
17
|
+
* @param {Array} headers - Array of header objects with id and text
|
|
18
|
+
* @returns {string} A unique key for the anchor
|
|
19
|
+
*/
|
|
20
|
+
export function getAnchorKey(anchor: string, headers?: any[]): string;
|
|
21
|
+
/**
|
|
22
|
+
* Get all unique anchors from items (preserving order)
|
|
23
|
+
* @param {Array} items - Array of gutter items
|
|
24
|
+
* @returns {Array} Array of unique anchor strings
|
|
25
|
+
*/
|
|
26
|
+
export function getUniqueAnchors(items: any[]): any[];
|
|
27
|
+
/**
|
|
28
|
+
* Get display label for an anchor (used in overflow section)
|
|
29
|
+
* @param {string} anchor - The anchor string
|
|
30
|
+
* @returns {string} Human-readable label for the anchor
|
|
31
|
+
*/
|
|
32
|
+
export function getAnchorLabel(anchor: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Get items that match a specific anchor
|
|
35
|
+
* @param {Array} items - Array of gutter items
|
|
36
|
+
* @param {string} anchor - The anchor to match
|
|
37
|
+
* @returns {Array} Items matching the anchor
|
|
38
|
+
*/
|
|
39
|
+
export function getItemsForAnchor(items: any[], anchor: string): any[];
|
|
40
|
+
/**
|
|
41
|
+
* Get items that don't have a valid anchor (orphan items shown at top)
|
|
42
|
+
* @param {Array} items - Array of gutter items
|
|
43
|
+
* @param {Array} headers - Array of header objects
|
|
44
|
+
* @returns {Array} Items without valid anchors
|
|
45
|
+
*/
|
|
46
|
+
export function getOrphanItems(items: any[], headers?: any[]): any[];
|
|
47
|
+
/**
|
|
48
|
+
* Find the DOM element for an anchor within a content element
|
|
49
|
+
* @param {string} anchor - The anchor string
|
|
50
|
+
* @param {HTMLElement} contentEl - The content container element
|
|
51
|
+
* @param {Array} headers - Array of header objects
|
|
52
|
+
* @returns {HTMLElement|null} The DOM element or null if not found
|
|
53
|
+
*/
|
|
54
|
+
export function findAnchorElement(anchor: string, contentEl: HTMLElement, headers?: any[]): HTMLElement | null;
|