@autumnsgrove/groveengine 0.6.2 → 0.6.4
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/LICENSE +378 -0
- package/dist/auth/jwt.d.ts +10 -4
- package/dist/auth/jwt.js +18 -4
- package/dist/auth/session.d.ts +22 -15
- package/dist/auth/session.js +35 -16
- package/dist/components/admin/GutterManager.svelte +81 -139
- package/dist/components/admin/GutterManager.svelte.d.ts +6 -6
- package/dist/components/admin/MarkdownEditor.svelte +80 -23
- package/dist/components/admin/MarkdownEditor.svelte.d.ts +14 -8
- package/dist/components/admin/composables/useAmbientSounds.svelte.d.ts +52 -2
- package/dist/components/admin/composables/useAmbientSounds.svelte.js +38 -4
- package/dist/components/admin/composables/useCommandPalette.svelte.d.ts +80 -10
- package/dist/components/admin/composables/useCommandPalette.svelte.js +45 -5
- package/dist/components/admin/composables/useDraftManager.svelte.d.ts +76 -14
- package/dist/components/admin/composables/useDraftManager.svelte.js +44 -10
- package/dist/components/admin/composables/useEditorTheme.svelte.d.ts +168 -2
- package/dist/components/admin/composables/useEditorTheme.svelte.js +40 -7
- package/dist/components/admin/composables/useSlashCommands.svelte.d.ts +94 -22
- package/dist/components/admin/composables/useSlashCommands.svelte.js +58 -9
- package/dist/components/admin/composables/useSnippets.svelte.d.ts +51 -2
- package/dist/components/admin/composables/useSnippets.svelte.js +35 -3
- package/dist/components/admin/composables/useWritingSession.svelte.d.ts +64 -6
- package/dist/components/admin/composables/useWritingSession.svelte.js +42 -5
- package/dist/components/custom/ContentWithGutter.svelte +53 -23
- package/dist/components/custom/ContentWithGutter.svelte.d.ts +6 -14
- package/dist/components/custom/GutterItem.svelte +1 -1
- package/dist/components/custom/LeftGutter.svelte +43 -13
- package/dist/components/custom/LeftGutter.svelte.d.ts +6 -6
- package/dist/config/ai-models.js +1 -1
- package/dist/groveauth/client.js +11 -11
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -2
- package/dist/server/logger.d.ts +74 -26
- package/dist/server/logger.js +133 -184
- package/dist/server/services/cache.js +1 -10
- package/dist/ui/components/charts/ActivityOverview.svelte +14 -3
- package/dist/ui/components/charts/ActivityOverview.svelte.d.ts +10 -7
- package/dist/ui/components/charts/RepoBreakdown.svelte +9 -3
- package/dist/ui/components/charts/RepoBreakdown.svelte.d.ts +12 -11
- package/dist/ui/components/charts/Sparkline.svelte +18 -7
- package/dist/ui/components/charts/Sparkline.svelte.d.ts +21 -2
- package/dist/ui/components/gallery/ImageGallery.svelte +12 -8
- package/dist/ui/components/gallery/ImageGallery.svelte.d.ts +2 -2
- package/dist/ui/components/gallery/Lightbox.svelte +5 -2
- package/dist/ui/components/gallery/ZoomableImage.svelte +8 -5
- package/dist/ui/components/primitives/accordion/index.d.ts +1 -1
- package/dist/ui/components/primitives/input/input.svelte.d.ts +1 -1
- package/dist/ui/components/primitives/tabs/index.d.ts +1 -1
- package/dist/ui/components/primitives/textarea/textarea.svelte.d.ts +1 -1
- package/dist/ui/components/ui/Button.svelte +5 -0
- package/dist/ui/components/ui/Button.svelte.d.ts +4 -1
- package/dist/ui/components/ui/Input.svelte +4 -0
- package/dist/ui/components/ui/Input.svelte.d.ts +3 -1
- package/dist/ui/components/ui/Logo.svelte +86 -0
- package/dist/ui/components/ui/Logo.svelte.d.ts +25 -0
- package/dist/ui/components/ui/LogoLoader.svelte +71 -0
- package/dist/ui/components/ui/LogoLoader.svelte.d.ts +9 -0
- package/dist/ui/components/ui/index.d.ts +2 -0
- package/dist/ui/components/ui/index.js +2 -0
- package/dist/ui/tailwind.preset.js +8 -8
- package/dist/utils/api.js +2 -1
- package/dist/utils/debounce.d.ts +4 -3
- package/dist/utils/debounce.js +10 -6
- package/dist/utils/gallery.d.ts +58 -32
- package/dist/utils/gallery.js +111 -129
- package/dist/utils/gutter.d.ts +47 -26
- package/dist/utils/gutter.js +116 -124
- package/dist/utils/imageProcessor.d.ts +66 -19
- package/dist/utils/imageProcessor.js +31 -10
- package/dist/utils/index.d.ts +11 -11
- package/dist/utils/index.js +4 -3
- package/dist/utils/json.js +1 -1
- package/dist/utils/markdown.d.ts +183 -103
- package/dist/utils/markdown.js +517 -678
- package/dist/utils/sanitize.d.ts +22 -12
- package/dist/utils/sanitize.js +268 -282
- package/dist/utils/validation.js +4 -3
- package/package.json +23 -23
- package/static/fonts/alagard.ttf +0 -0
package/dist/utils/gutter.js
CHANGED
|
@@ -5,165 +5,157 @@
|
|
|
5
5
|
* and anchor resolution. Used by ContentWithGutter component and related
|
|
6
6
|
* functionality across the site.
|
|
7
7
|
*/
|
|
8
|
-
|
|
9
8
|
/**
|
|
10
9
|
* Parse anchor string to determine anchor type and value
|
|
11
|
-
* @param
|
|
12
|
-
* @returns
|
|
10
|
+
* @param anchor - The anchor string from manifest
|
|
11
|
+
* @returns Object with type and value properties
|
|
13
12
|
*/
|
|
14
13
|
export function parseAnchor(anchor) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return { type:
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Unknown format - treat as header for backwards compatibility
|
|
38
|
-
return { type: 'header', value: anchor };
|
|
14
|
+
if (!anchor) {
|
|
15
|
+
return { type: "none", value: null };
|
|
16
|
+
}
|
|
17
|
+
// Check for paragraph anchor: "paragraph:N"
|
|
18
|
+
const paragraphMatch = anchor.match(/^paragraph:(\d+)$/);
|
|
19
|
+
if (paragraphMatch) {
|
|
20
|
+
return { type: "paragraph", value: parseInt(paragraphMatch[1], 10) };
|
|
21
|
+
}
|
|
22
|
+
// Check for tag anchor: "anchor:tagname" (supports alphanumeric, underscores, and hyphens)
|
|
23
|
+
const tagMatch = anchor.match(/^anchor:([\w-]+)$/);
|
|
24
|
+
if (tagMatch) {
|
|
25
|
+
return { type: "tag", value: tagMatch[1] };
|
|
26
|
+
}
|
|
27
|
+
// Check for header anchor: "## Header Text"
|
|
28
|
+
const headerMatch = anchor.match(/^(#{1,6})\s+(.+)$/);
|
|
29
|
+
if (headerMatch) {
|
|
30
|
+
return { type: "header", value: anchor };
|
|
31
|
+
}
|
|
32
|
+
// Unknown format - treat as header for backwards compatibility
|
|
33
|
+
return { type: "header", value: anchor };
|
|
39
34
|
}
|
|
40
|
-
|
|
41
35
|
/**
|
|
42
36
|
* Generate a unique key for an anchor (used for grouping and positioning)
|
|
43
|
-
* @param
|
|
44
|
-
* @param
|
|
45
|
-
* @returns
|
|
37
|
+
* @param anchor - The anchor string
|
|
38
|
+
* @param headers - Array of header objects with id and text
|
|
39
|
+
* @returns A unique key for the anchor
|
|
46
40
|
*/
|
|
47
41
|
export function getAnchorKey(anchor, headers = []) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
42
|
+
const parsed = parseAnchor(anchor);
|
|
43
|
+
switch (parsed.type) {
|
|
44
|
+
case "header": {
|
|
45
|
+
const headerText = anchor.replace(/^#+\s*/, "");
|
|
46
|
+
const header = headers.find((h) => h.text === headerText);
|
|
47
|
+
return header ? `header:${header.id}` : `header:${anchor}`;
|
|
48
|
+
}
|
|
49
|
+
case "paragraph":
|
|
50
|
+
return `paragraph:${parsed.value}`;
|
|
51
|
+
case "tag":
|
|
52
|
+
return `tag:${parsed.value}`;
|
|
53
|
+
default:
|
|
54
|
+
return `unknown:${anchor}`;
|
|
54
55
|
}
|
|
55
|
-
case 'paragraph':
|
|
56
|
-
return `paragraph:${parsed.value}`;
|
|
57
|
-
case 'tag':
|
|
58
|
-
return `tag:${parsed.value}`;
|
|
59
|
-
default:
|
|
60
|
-
return `unknown:${anchor}`;
|
|
61
|
-
}
|
|
62
56
|
}
|
|
63
|
-
|
|
64
57
|
/**
|
|
65
58
|
* Get all unique anchors from items (preserving order)
|
|
66
|
-
* @param
|
|
67
|
-
* @returns
|
|
59
|
+
* @param items - Array of gutter items
|
|
60
|
+
* @returns Array of unique anchor strings
|
|
68
61
|
*/
|
|
69
62
|
export function getUniqueAnchors(items) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
63
|
+
if (!items)
|
|
64
|
+
return [];
|
|
65
|
+
const seen = new Set();
|
|
66
|
+
const anchors = [];
|
|
67
|
+
for (const item of items) {
|
|
68
|
+
if (item.anchor && !seen.has(item.anchor)) {
|
|
69
|
+
seen.add(item.anchor);
|
|
70
|
+
anchors.push(item.anchor);
|
|
71
|
+
}
|
|
77
72
|
}
|
|
78
|
-
|
|
79
|
-
return anchors;
|
|
73
|
+
return anchors;
|
|
80
74
|
}
|
|
81
|
-
|
|
82
75
|
/**
|
|
83
76
|
* Get display label for an anchor (used in overflow section)
|
|
84
|
-
* @param
|
|
85
|
-
* @returns
|
|
77
|
+
* @param anchor - The anchor string
|
|
78
|
+
* @returns Human-readable label for the anchor
|
|
86
79
|
*/
|
|
87
80
|
export function getAnchorLabel(anchor) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
81
|
+
const parsed = parseAnchor(anchor);
|
|
82
|
+
switch (parsed.type) {
|
|
83
|
+
case "header":
|
|
84
|
+
return anchor.replace(/^#+\s*/, "");
|
|
85
|
+
case "paragraph":
|
|
86
|
+
return `Paragraph ${parsed.value}`;
|
|
87
|
+
case "tag":
|
|
88
|
+
return `Tag: ${parsed.value}`;
|
|
89
|
+
default:
|
|
90
|
+
return anchor;
|
|
91
|
+
}
|
|
99
92
|
}
|
|
100
|
-
|
|
101
93
|
/**
|
|
102
94
|
* Get items that match a specific anchor
|
|
103
|
-
* @param
|
|
104
|
-
* @param
|
|
105
|
-
* @returns
|
|
95
|
+
* @param items - Array of gutter items
|
|
96
|
+
* @param anchor - The anchor to match
|
|
97
|
+
* @returns Items matching the anchor
|
|
106
98
|
*/
|
|
107
99
|
export function getItemsForAnchor(items, anchor) {
|
|
108
|
-
|
|
109
|
-
|
|
100
|
+
if (!items)
|
|
101
|
+
return [];
|
|
102
|
+
return items.filter((item) => item.anchor === anchor);
|
|
110
103
|
}
|
|
111
|
-
|
|
112
104
|
/**
|
|
113
105
|
* Get items that don't have a valid anchor (orphan items shown at top)
|
|
114
|
-
* @param
|
|
115
|
-
* @param
|
|
116
|
-
* @returns
|
|
106
|
+
* @param items - Array of gutter items
|
|
107
|
+
* @param headers - Array of header objects
|
|
108
|
+
* @returns Items without valid anchors
|
|
117
109
|
*/
|
|
118
110
|
export function getOrphanItems(items, headers = []) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
111
|
+
if (!items)
|
|
112
|
+
return [];
|
|
113
|
+
return items.filter((item) => {
|
|
114
|
+
if (!item.anchor)
|
|
115
|
+
return true;
|
|
116
|
+
const parsed = parseAnchor(item.anchor);
|
|
117
|
+
if (parsed.type === "header") {
|
|
118
|
+
const headerText = item.anchor.replace(/^#+\s*/, "");
|
|
119
|
+
return !headers.find((h) => h.text === headerText);
|
|
120
|
+
}
|
|
121
|
+
// Paragraph and tag anchors are valid if they have values
|
|
122
|
+
return parsed.type === "none";
|
|
123
|
+
});
|
|
130
124
|
}
|
|
131
|
-
|
|
132
125
|
/**
|
|
133
126
|
* Find the DOM element for an anchor within a content element
|
|
134
|
-
* @param
|
|
135
|
-
* @param
|
|
136
|
-
* @param
|
|
137
|
-
* @returns
|
|
127
|
+
* @param anchor - The anchor string
|
|
128
|
+
* @param contentEl - The content container element
|
|
129
|
+
* @param headers - Array of header objects
|
|
130
|
+
* @returns The DOM element or null if not found
|
|
138
131
|
*/
|
|
139
132
|
export function findAnchorElement(anchor, contentEl, headers = []) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
133
|
+
if (!contentEl)
|
|
134
|
+
return null;
|
|
135
|
+
const parsed = parseAnchor(anchor);
|
|
136
|
+
switch (parsed.type) {
|
|
137
|
+
case "header": {
|
|
138
|
+
const headerText = anchor.replace(/^#+\s*/, "");
|
|
139
|
+
const header = headers.find((h) => h.text === headerText);
|
|
140
|
+
if (header) {
|
|
141
|
+
return document.getElementById(header.id);
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
case "paragraph": {
|
|
146
|
+
// Select only direct child paragraphs to avoid counting paragraphs
|
|
147
|
+
// inside blockquotes, list items, etc.
|
|
148
|
+
const paragraphs = contentEl.querySelectorAll(":scope > p");
|
|
149
|
+
const index = parsed.value - 1; // Convert to 0-based index
|
|
150
|
+
if (index >= 0 && index < paragraphs.length) {
|
|
151
|
+
return paragraphs[index];
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
case "tag": {
|
|
156
|
+
return contentEl.querySelector(`[data-anchor="${parsed.value}"]`);
|
|
157
|
+
}
|
|
158
|
+
default:
|
|
159
|
+
return null;
|
|
165
160
|
}
|
|
166
|
-
default:
|
|
167
|
-
return null;
|
|
168
|
-
}
|
|
169
161
|
}
|
|
@@ -8,28 +8,31 @@
|
|
|
8
8
|
* @returns {Promise<string>} Hex string of the hash
|
|
9
9
|
*/
|
|
10
10
|
export function calculateFileHash(file: File | Blob): Promise<string>;
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {Object} ProcessedImageResult
|
|
13
|
+
* @property {Blob} blob - Processed image blob
|
|
14
|
+
* @property {number} width - Image width
|
|
15
|
+
* @property {number} height - Image height
|
|
16
|
+
* @property {number} originalSize - Original file size
|
|
17
|
+
* @property {number} processedSize - Processed file size
|
|
18
|
+
* @property {boolean} [skipped] - Whether processing was skipped
|
|
19
|
+
* @property {string} [reason] - Reason for skipping
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {Object} ProcessImageOptions
|
|
23
|
+
* @property {number} [quality] - Quality 0-100 (default 80)
|
|
24
|
+
* @property {boolean} [convertToWebP] - Convert to WebP format (default true)
|
|
25
|
+
* @property {boolean} [fullResolution] - Skip resizing (default false)
|
|
26
|
+
*/
|
|
11
27
|
/**
|
|
12
28
|
* Process an image: convert to WebP, adjust quality, strip EXIF
|
|
13
29
|
* Drawing to canvas automatically strips EXIF data including GPS
|
|
14
30
|
*
|
|
15
31
|
* @param {File} file - Original image file
|
|
16
|
-
* @param {
|
|
17
|
-
* @
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
* @returns {Promise<{ blob: Blob, width: number, height: number, originalSize: number, processedSize: number }>}
|
|
21
|
-
*/
|
|
22
|
-
export function processImage(file: File, options?: {
|
|
23
|
-
quality: number;
|
|
24
|
-
convertToWebP: boolean;
|
|
25
|
-
fullResolution: boolean;
|
|
26
|
-
}): Promise<{
|
|
27
|
-
blob: Blob;
|
|
28
|
-
width: number;
|
|
29
|
-
height: number;
|
|
30
|
-
originalSize: number;
|
|
31
|
-
processedSize: number;
|
|
32
|
-
}>;
|
|
32
|
+
* @param {ProcessImageOptions} options - Processing options
|
|
33
|
+
* @returns {Promise<ProcessedImageResult>}
|
|
34
|
+
*/
|
|
35
|
+
export function processImage(file: File, options?: ProcessImageOptions): Promise<ProcessedImageResult>;
|
|
33
36
|
/**
|
|
34
37
|
* Generate a date-based path for organizing uploads
|
|
35
38
|
* Format: photos/YYYY/MM/DD/
|
|
@@ -37,12 +40,12 @@ export function processImage(file: File, options?: {
|
|
|
37
40
|
*/
|
|
38
41
|
export function generateDatePath(): string;
|
|
39
42
|
/**
|
|
40
|
-
* Generate a clean filename from original name
|
|
43
|
+
* Generate a clean filename from original name for image files
|
|
41
44
|
* @param {string} originalName - Original filename
|
|
42
45
|
* @param {boolean} useWebP - Whether to use .webp extension
|
|
43
46
|
* @returns {string} Sanitized filename
|
|
44
47
|
*/
|
|
45
|
-
export function
|
|
48
|
+
export function sanitizeImageFilename(originalName: string, useWebP?: boolean): string;
|
|
46
49
|
/**
|
|
47
50
|
* Format bytes to human-readable string
|
|
48
51
|
* @param {number} bytes - Size in bytes
|
|
@@ -56,3 +59,47 @@ export function formatBytes(bytes: number): string;
|
|
|
56
59
|
* @returns {string} Percentage saved
|
|
57
60
|
*/
|
|
58
61
|
export function compressionRatio(original: number, processed: number): string;
|
|
62
|
+
export type ProcessedImageResult = {
|
|
63
|
+
/**
|
|
64
|
+
* - Processed image blob
|
|
65
|
+
*/
|
|
66
|
+
blob: Blob;
|
|
67
|
+
/**
|
|
68
|
+
* - Image width
|
|
69
|
+
*/
|
|
70
|
+
width: number;
|
|
71
|
+
/**
|
|
72
|
+
* - Image height
|
|
73
|
+
*/
|
|
74
|
+
height: number;
|
|
75
|
+
/**
|
|
76
|
+
* - Original file size
|
|
77
|
+
*/
|
|
78
|
+
originalSize: number;
|
|
79
|
+
/**
|
|
80
|
+
* - Processed file size
|
|
81
|
+
*/
|
|
82
|
+
processedSize: number;
|
|
83
|
+
/**
|
|
84
|
+
* - Whether processing was skipped
|
|
85
|
+
*/
|
|
86
|
+
skipped?: boolean | undefined;
|
|
87
|
+
/**
|
|
88
|
+
* - Reason for skipping
|
|
89
|
+
*/
|
|
90
|
+
reason?: string | undefined;
|
|
91
|
+
};
|
|
92
|
+
export type ProcessImageOptions = {
|
|
93
|
+
/**
|
|
94
|
+
* - Quality 0-100 (default 80)
|
|
95
|
+
*/
|
|
96
|
+
quality?: number | undefined;
|
|
97
|
+
/**
|
|
98
|
+
* - Convert to WebP format (default true)
|
|
99
|
+
*/
|
|
100
|
+
convertToWebP?: boolean | undefined;
|
|
101
|
+
/**
|
|
102
|
+
* - Skip resizing (default false)
|
|
103
|
+
*/
|
|
104
|
+
fullResolution?: boolean | undefined;
|
|
105
|
+
};
|
|
@@ -68,16 +68,31 @@ function getMaxDimensionForQuality(quality) {
|
|
|
68
68
|
return 960;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* @typedef {Object} ProcessedImageResult
|
|
73
|
+
* @property {Blob} blob - Processed image blob
|
|
74
|
+
* @property {number} width - Image width
|
|
75
|
+
* @property {number} height - Image height
|
|
76
|
+
* @property {number} originalSize - Original file size
|
|
77
|
+
* @property {number} processedSize - Processed file size
|
|
78
|
+
* @property {boolean} [skipped] - Whether processing was skipped
|
|
79
|
+
* @property {string} [reason] - Reason for skipping
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @typedef {Object} ProcessImageOptions
|
|
84
|
+
* @property {number} [quality] - Quality 0-100 (default 80)
|
|
85
|
+
* @property {boolean} [convertToWebP] - Convert to WebP format (default true)
|
|
86
|
+
* @property {boolean} [fullResolution] - Skip resizing (default false)
|
|
87
|
+
*/
|
|
88
|
+
|
|
71
89
|
/**
|
|
72
90
|
* Process an image: convert to WebP, adjust quality, strip EXIF
|
|
73
91
|
* Drawing to canvas automatically strips EXIF data including GPS
|
|
74
92
|
*
|
|
75
93
|
* @param {File} file - Original image file
|
|
76
|
-
* @param {
|
|
77
|
-
* @
|
|
78
|
-
* @param {boolean} options.convertToWebP - Convert to WebP format (default true)
|
|
79
|
-
* @param {boolean} options.fullResolution - Skip resizing (default false)
|
|
80
|
-
* @returns {Promise<{ blob: Blob, width: number, height: number, originalSize: number, processedSize: number }>}
|
|
94
|
+
* @param {ProcessImageOptions} options - Processing options
|
|
95
|
+
* @returns {Promise<ProcessedImageResult>}
|
|
81
96
|
*/
|
|
82
97
|
export async function processImage(file, options = {}) {
|
|
83
98
|
const {
|
|
@@ -119,14 +134,20 @@ export async function processImage(file, options = {}) {
|
|
|
119
134
|
canvas.height = targetHeight;
|
|
120
135
|
|
|
121
136
|
const ctx = canvas.getContext('2d');
|
|
137
|
+
if (!ctx) {
|
|
138
|
+
throw new Error('Failed to get canvas 2d context');
|
|
139
|
+
}
|
|
122
140
|
ctx.drawImage(img, 0, 0, targetWidth, targetHeight);
|
|
123
141
|
|
|
124
142
|
// Convert to blob
|
|
125
143
|
const mimeType = convertToWebP ? 'image/webp' : file.type;
|
|
126
144
|
const qualityDecimal = quality / 100;
|
|
127
145
|
|
|
128
|
-
const blob = await new Promise((resolve) => {
|
|
129
|
-
canvas.toBlob(
|
|
146
|
+
const blob = await new Promise((resolve, reject) => {
|
|
147
|
+
canvas.toBlob((b) => {
|
|
148
|
+
if (b) resolve(b);
|
|
149
|
+
else reject(new Error('Failed to create blob'));
|
|
150
|
+
}, mimeType, qualityDecimal);
|
|
130
151
|
});
|
|
131
152
|
|
|
132
153
|
return {
|
|
@@ -134,7 +155,7 @@ export async function processImage(file, options = {}) {
|
|
|
134
155
|
width: targetWidth,
|
|
135
156
|
height: targetHeight,
|
|
136
157
|
originalSize,
|
|
137
|
-
processedSize: blob.size,
|
|
158
|
+
processedSize: /** @type {Blob} */ (blob).size,
|
|
138
159
|
skipped: false
|
|
139
160
|
};
|
|
140
161
|
}
|
|
@@ -153,12 +174,12 @@ export function generateDatePath() {
|
|
|
153
174
|
}
|
|
154
175
|
|
|
155
176
|
/**
|
|
156
|
-
* Generate a clean filename from original name
|
|
177
|
+
* Generate a clean filename from original name for image files
|
|
157
178
|
* @param {string} originalName - Original filename
|
|
158
179
|
* @param {boolean} useWebP - Whether to use .webp extension
|
|
159
180
|
* @returns {string} Sanitized filename
|
|
160
181
|
*/
|
|
161
|
-
export function
|
|
182
|
+
export function sanitizeImageFilename(originalName, useWebP = true) {
|
|
162
183
|
// Get base name without extension
|
|
163
184
|
const lastDot = originalName.lastIndexOf('.');
|
|
164
185
|
const baseName = lastDot > 0 ? originalName.substring(0, lastDot) : originalName;
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
5
|
-
export
|
|
6
|
-
export
|
|
7
|
-
export * from
|
|
8
|
-
export * from
|
|
9
|
-
export * from
|
|
10
|
-
export * from
|
|
11
|
-
export * from
|
|
1
|
+
export * from './api.js';
|
|
2
|
+
export * from './cn.js';
|
|
3
|
+
export * from './csrf.js';
|
|
4
|
+
export * from './debounce.js';
|
|
5
|
+
export { parseImageFilename, getImageTitle, getImageDate, searchImages, filterImagesByDateRange, filterImagesByTags, filterImagesByCategory, getAvailableYears, getAvailableCategories, type GalleryImage, type ImageTag, type ParsedImageMetadata } from './gallery.js';
|
|
6
|
+
export { parseAnchor, getAnchorKey, getUniqueAnchors, getAnchorLabel, getItemsForAnchor, getOrphanItems, findAnchorElement, type AnchorType, type ParsedAnchor, type Header, type GutterItem } from './gutter.js';
|
|
7
|
+
export * from './imageProcessor.js';
|
|
8
|
+
export * from './json.js';
|
|
9
|
+
export * from './markdown.js';
|
|
10
|
+
export * from './sanitize.js';
|
|
11
|
+
export * from './validation.js';
|
package/dist/utils/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// Utils barrel export
|
|
2
2
|
// Re-exports all utility functions from the utils module
|
|
3
|
-
|
|
4
3
|
export * from './api.js';
|
|
5
4
|
export * from './cn.js';
|
|
6
5
|
export * from './csrf.js';
|
|
7
6
|
export * from './debounce.js';
|
|
8
|
-
|
|
9
|
-
export
|
|
7
|
+
// Gallery - explicit exports to avoid ambiguity
|
|
8
|
+
export { parseImageFilename, getImageTitle, getImageDate, searchImages, filterImagesByDateRange, filterImagesByTags, filterImagesByCategory, getAvailableYears, getAvailableCategories } from './gallery.js';
|
|
9
|
+
// Gutter - explicit exports to avoid ambiguity
|
|
10
|
+
export { parseAnchor, getAnchorKey, getUniqueAnchors, getAnchorLabel, getItemsForAnchor, getOrphanItems, findAnchorElement } from './gutter.js';
|
|
10
11
|
export * from './imageProcessor.js';
|
|
11
12
|
export * from './json.js';
|
|
12
13
|
export * from './markdown.js';
|
package/dist/utils/json.js
CHANGED
|
@@ -20,7 +20,7 @@ export function safeJsonParse(str, fallback = []) {
|
|
|
20
20
|
try {
|
|
21
21
|
return JSON.parse(str);
|
|
22
22
|
} catch (e) {
|
|
23
|
-
console.warn('Failed to parse JSON:', e.message);
|
|
23
|
+
console.warn('Failed to parse JSON:', e instanceof Error ? e.message : String(e));
|
|
24
24
|
return fallback;
|
|
25
25
|
}
|
|
26
26
|
}
|