chaeditor 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/LICENSE.txt +21 -0
- package/README.ko.md +250 -0
- package/README.md +242 -0
- package/dist/core.cjs +1034 -0
- package/dist/core.d.mts +347 -0
- package/dist/core.d.ts +347 -0
- package/dist/core.mjs +988 -0
- package/dist/default-host.cjs +243 -0
- package/dist/default-host.d.mts +52 -0
- package/dist/default-host.d.ts +52 -0
- package/dist/default-host.mjs +239 -0
- package/dist/default-markdown-primitive-registry-B3PGEkqs.d.mts +12 -0
- package/dist/default-markdown-primitive-registry-CqzwhHj2.d.ts +12 -0
- package/dist/image-upload-kind-BJqItE_C.d.mts +18 -0
- package/dist/image-upload-kind-BJqItE_C.d.ts +18 -0
- package/dist/index.cjs +8736 -0
- package/dist/index.d.mts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.mjs +8668 -0
- package/dist/markdown-editor-B1qvE40Z.d.mts +460 -0
- package/dist/markdown-editor-Ce6DpnQk.d.ts +460 -0
- package/dist/markdown-primitive-contract-BXsqbKwY.d.mts +124 -0
- package/dist/markdown-primitive-contract-BXsqbKwY.d.ts +124 -0
- package/dist/panda-primitives.cjs +1299 -0
- package/dist/panda-primitives.d.mts +21127 -0
- package/dist/panda-primitives.d.ts +21127 -0
- package/dist/panda-primitives.mjs +1285 -0
- package/dist/react.cjs +8558 -0
- package/dist/react.d.mts +35 -0
- package/dist/react.d.ts +35 -0
- package/dist/react.mjs +8531 -0
- package/dist/toolbar-preset-B9ttTEol.d.ts +236 -0
- package/dist/toolbar-preset-DIsQN390.d.mts +236 -0
- package/package.json +151 -0
- package/recipes/host-presets/README.md +16 -0
- package/recipes/host-presets/emotion-host-preset.tsx.template +109 -0
- package/recipes/host-presets/styled-components-host-preset.tsx.template +102 -0
- package/recipes/host-presets/tailwind-host-preset.tsx.template +116 -0
- package/recipes/host-presets/vanilla-extract-host-preset.tsx.template +116 -0
- package/styled-system/styles.css +3370 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/integrations/default-host/api/upload-editor-file.ts
|
|
4
|
+
var uploadEditorFile = async ({
|
|
5
|
+
contentType,
|
|
6
|
+
file
|
|
7
|
+
}) => {
|
|
8
|
+
const formData = new FormData();
|
|
9
|
+
formData.set("contentType", contentType);
|
|
10
|
+
formData.set("file", file);
|
|
11
|
+
const response = await fetch("/api/attachments", {
|
|
12
|
+
body: formData,
|
|
13
|
+
method: "POST"
|
|
14
|
+
});
|
|
15
|
+
let body = {};
|
|
16
|
+
try {
|
|
17
|
+
body = await response.json();
|
|
18
|
+
} catch {
|
|
19
|
+
body = {
|
|
20
|
+
error: response.ok ? "Attachment response parse failed" : "Attachment upload failed",
|
|
21
|
+
message: response.statusText || void 0
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
if (!response.ok || !body.url || !body.fileName || typeof body.fileSize !== "number" || !body.contentType) {
|
|
25
|
+
throw new Error(body.error ?? body.message ?? "Attachment upload failed");
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
contentType: body.contentType,
|
|
29
|
+
fileName: body.fileName,
|
|
30
|
+
fileSize: body.fileSize,
|
|
31
|
+
url: body.url
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// src/shared/lib/image/optimize-image-file.ts
|
|
36
|
+
var OUTPUT_TYPE = "image/webp";
|
|
37
|
+
var OPTIMIZABLE_IMAGE_TYPES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/webp"]);
|
|
38
|
+
var resolveImageOptimizationDimensions = ({
|
|
39
|
+
height,
|
|
40
|
+
maxHeight,
|
|
41
|
+
maxWidth,
|
|
42
|
+
width
|
|
43
|
+
}) => {
|
|
44
|
+
const scale = Math.min(1, maxWidth / width, maxHeight / height);
|
|
45
|
+
return {
|
|
46
|
+
height: Math.max(1, Math.round(height * scale)),
|
|
47
|
+
width: Math.max(1, Math.round(width * scale))
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
var resolveOptimizedImageFileName = (fileName) => fileName.replace(/\.[^./]+$/, "") + ".webp";
|
|
51
|
+
var loadImageElement = (file, errorLabel) => new Promise((resolve, reject) => {
|
|
52
|
+
const objectUrl = URL.createObjectURL(file);
|
|
53
|
+
const image = new Image();
|
|
54
|
+
image.onload = () => {
|
|
55
|
+
URL.revokeObjectURL(objectUrl);
|
|
56
|
+
resolve(image);
|
|
57
|
+
};
|
|
58
|
+
image.onerror = () => {
|
|
59
|
+
URL.revokeObjectURL(objectUrl);
|
|
60
|
+
reject(new Error(`[${errorLabel}] Failed to decode the image.`));
|
|
61
|
+
};
|
|
62
|
+
image.src = objectUrl;
|
|
63
|
+
});
|
|
64
|
+
var convertCanvasToBlob = ({
|
|
65
|
+
canvas,
|
|
66
|
+
errorLabel,
|
|
67
|
+
outputQuality
|
|
68
|
+
}) => new Promise((resolve, reject) => {
|
|
69
|
+
canvas.toBlob(
|
|
70
|
+
(blob) => {
|
|
71
|
+
if (!blob) {
|
|
72
|
+
reject(new Error(`[${errorLabel}] Failed to convert the canvas to a blob.`));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
resolve(blob);
|
|
76
|
+
},
|
|
77
|
+
OUTPUT_TYPE,
|
|
78
|
+
outputQuality
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
var optimizeImageFile = async ({
|
|
82
|
+
errorLabel,
|
|
83
|
+
file,
|
|
84
|
+
maxHeight,
|
|
85
|
+
maxWidth,
|
|
86
|
+
outputQuality
|
|
87
|
+
}) => {
|
|
88
|
+
if (!OPTIMIZABLE_IMAGE_TYPES.has(file.type)) {
|
|
89
|
+
return file;
|
|
90
|
+
}
|
|
91
|
+
const image = await loadImageElement(file, errorLabel);
|
|
92
|
+
const { height, width } = resolveImageOptimizationDimensions({
|
|
93
|
+
height: image.naturalHeight,
|
|
94
|
+
maxHeight,
|
|
95
|
+
maxWidth,
|
|
96
|
+
width: image.naturalWidth
|
|
97
|
+
});
|
|
98
|
+
const canvas = document.createElement("canvas");
|
|
99
|
+
const context = canvas.getContext("2d");
|
|
100
|
+
if (!context) {
|
|
101
|
+
throw new Error(`[${errorLabel}] Failed to create a canvas context.`);
|
|
102
|
+
}
|
|
103
|
+
canvas.width = width;
|
|
104
|
+
canvas.height = height;
|
|
105
|
+
context.drawImage(image, 0, 0, width, height);
|
|
106
|
+
const optimizedBlob = await convertCanvasToBlob({
|
|
107
|
+
canvas,
|
|
108
|
+
errorLabel,
|
|
109
|
+
outputQuality
|
|
110
|
+
});
|
|
111
|
+
if (optimizedBlob.size >= file.size) {
|
|
112
|
+
return file;
|
|
113
|
+
}
|
|
114
|
+
return new File([optimizedBlob], resolveOptimizedImageFileName(file.name), {
|
|
115
|
+
lastModified: file.lastModified,
|
|
116
|
+
type: OUTPUT_TYPE
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// src/shared/lib/image/optimize-content-image-file.ts
|
|
121
|
+
var CONTENT_MAX_WIDTH = 1600;
|
|
122
|
+
var CONTENT_MAX_HEIGHT = 1600;
|
|
123
|
+
var CONTENT_OUTPUT_QUALITY = 0.84;
|
|
124
|
+
var optimizeContentImageFile = (file) => optimizeImageFile({
|
|
125
|
+
errorLabel: "content-image-optimization",
|
|
126
|
+
file,
|
|
127
|
+
maxHeight: CONTENT_MAX_HEIGHT,
|
|
128
|
+
maxWidth: CONTENT_MAX_WIDTH,
|
|
129
|
+
outputQuality: CONTENT_OUTPUT_QUALITY
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// src/shared/lib/image/optimize-thumbnail-image-file.ts
|
|
133
|
+
var THUMBNAIL_MAX_WIDTH = 800;
|
|
134
|
+
var THUMBNAIL_MAX_HEIGHT = 800;
|
|
135
|
+
var THUMBNAIL_OUTPUT_QUALITY = 0.82;
|
|
136
|
+
var optimizeThumbnailImageFile = (file) => optimizeImageFile({
|
|
137
|
+
errorLabel: "thumbnail-optimization",
|
|
138
|
+
file,
|
|
139
|
+
maxHeight: THUMBNAIL_MAX_HEIGHT,
|
|
140
|
+
maxWidth: THUMBNAIL_MAX_WIDTH,
|
|
141
|
+
outputQuality: THUMBNAIL_OUTPUT_QUALITY
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// src/integrations/default-host/api/upload-editor-image.ts
|
|
145
|
+
var uploadEditorImage = async ({
|
|
146
|
+
contentType,
|
|
147
|
+
file,
|
|
148
|
+
imageKind
|
|
149
|
+
}) => {
|
|
150
|
+
const optimizedFile = imageKind === "thumbnail" ? await optimizeThumbnailImageFile(file) : await optimizeContentImageFile(file);
|
|
151
|
+
const formData = new FormData();
|
|
152
|
+
formData.set("contentType", contentType);
|
|
153
|
+
formData.set("file", optimizedFile);
|
|
154
|
+
formData.set("imageKind", imageKind);
|
|
155
|
+
const response = await fetch("/api/images", {
|
|
156
|
+
body: formData,
|
|
157
|
+
method: "POST"
|
|
158
|
+
});
|
|
159
|
+
let body = {};
|
|
160
|
+
try {
|
|
161
|
+
body = await response.json();
|
|
162
|
+
} catch {
|
|
163
|
+
body = {
|
|
164
|
+
error: response.ok ? "Image response parse failed" : "Image upload failed",
|
|
165
|
+
message: response.statusText || void 0
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
if (!response.ok || !body.url) {
|
|
169
|
+
throw new Error(body.error ?? body.message ?? "Image upload failed");
|
|
170
|
+
}
|
|
171
|
+
return body.url;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// src/integrations/default-host/api/upload-editor-video.ts
|
|
175
|
+
var createAbortUploadError = () => {
|
|
176
|
+
const error = new Error("Video upload aborted");
|
|
177
|
+
error.name = "AbortError";
|
|
178
|
+
return error;
|
|
179
|
+
};
|
|
180
|
+
var parseUploadResponse = (responseText) => {
|
|
181
|
+
try {
|
|
182
|
+
return JSON.parse(responseText);
|
|
183
|
+
} catch {
|
|
184
|
+
return {};
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
var uploadEditorVideo = async ({
|
|
188
|
+
contentType,
|
|
189
|
+
file,
|
|
190
|
+
onProgress,
|
|
191
|
+
signal
|
|
192
|
+
}) => {
|
|
193
|
+
const formData = new FormData();
|
|
194
|
+
formData.set("contentType", contentType);
|
|
195
|
+
formData.set("file", file);
|
|
196
|
+
return await new Promise((resolve, reject) => {
|
|
197
|
+
const xhr = new XMLHttpRequest();
|
|
198
|
+
const cleanup = () => {
|
|
199
|
+
xhr.upload.onprogress = null;
|
|
200
|
+
xhr.onload = null;
|
|
201
|
+
xhr.onerror = null;
|
|
202
|
+
xhr.onabort = null;
|
|
203
|
+
signal?.removeEventListener("abort", handleAbortSignal);
|
|
204
|
+
};
|
|
205
|
+
const handleAbortSignal = () => {
|
|
206
|
+
xhr.abort();
|
|
207
|
+
};
|
|
208
|
+
xhr.open("POST", "/api/videos");
|
|
209
|
+
xhr.upload.onprogress = (event) => {
|
|
210
|
+
if (!event.lengthComputable) return;
|
|
211
|
+
onProgress?.(Math.min(100, Math.round(event.loaded / event.total * 100)));
|
|
212
|
+
};
|
|
213
|
+
xhr.onload = () => {
|
|
214
|
+
cleanup();
|
|
215
|
+
const body = parseUploadResponse(xhr.responseText);
|
|
216
|
+
if (xhr.status < 200 || xhr.status >= 300 || !body.url) {
|
|
217
|
+
reject(new Error(body.error ?? body.message ?? "Video upload failed"));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
resolve(body.url);
|
|
221
|
+
};
|
|
222
|
+
xhr.onerror = () => {
|
|
223
|
+
cleanup();
|
|
224
|
+
reject(new Error("Video upload failed"));
|
|
225
|
+
};
|
|
226
|
+
xhr.onabort = () => {
|
|
227
|
+
cleanup();
|
|
228
|
+
reject(createAbortUploadError());
|
|
229
|
+
};
|
|
230
|
+
if (signal) {
|
|
231
|
+
if (signal.aborted) {
|
|
232
|
+
xhr.abort();
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
signal.addEventListener("abort", handleAbortSignal, { once: true });
|
|
236
|
+
}
|
|
237
|
+
xhr.send(formData);
|
|
238
|
+
});
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
exports.uploadEditorFile = uploadEditorFile;
|
|
242
|
+
exports.uploadEditorImage = uploadEditorImage;
|
|
243
|
+
exports.uploadEditorVideo = uploadEditorVideo;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { a as EditorContentType, E as EditorAttachment, b as EditorImageUploadKind } from './image-upload-kind-BJqItE_C.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Uploads an attachment through the default host HTTP endpoint.
|
|
5
|
+
*
|
|
6
|
+
* @param options Attachment upload options.
|
|
7
|
+
* @param options.contentType Logical content type that scopes the upload target.
|
|
8
|
+
* @param options.file File selected by the user.
|
|
9
|
+
* @returns Uploaded attachment metadata normalized for editor insertion.
|
|
10
|
+
* @throws When the upload fails or the response payload is invalid.
|
|
11
|
+
*/
|
|
12
|
+
declare const uploadEditorFile: ({ contentType, file, }: {
|
|
13
|
+
contentType: EditorContentType;
|
|
14
|
+
file: File;
|
|
15
|
+
}) => Promise<EditorAttachment>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Uploads an editor image through the default host HTTP endpoint.
|
|
19
|
+
*
|
|
20
|
+
* @param options Image upload options.
|
|
21
|
+
* @param options.contentType Logical content type that scopes the upload target.
|
|
22
|
+
* @param options.file Original file selected by the user.
|
|
23
|
+
* @param options.imageKind Upload intent used to choose the image optimization path.
|
|
24
|
+
* @returns The final public image URL returned by the host.
|
|
25
|
+
* @throws When the response is not successful or does not include a public URL.
|
|
26
|
+
*/
|
|
27
|
+
declare const uploadEditorImage: ({ contentType, file, imageKind, }: {
|
|
28
|
+
contentType: EditorContentType;
|
|
29
|
+
file: File;
|
|
30
|
+
imageKind: EditorImageUploadKind;
|
|
31
|
+
}) => Promise<string>;
|
|
32
|
+
|
|
33
|
+
type UploadEditorVideoOptions = {
|
|
34
|
+
contentType: EditorContentType;
|
|
35
|
+
file: File;
|
|
36
|
+
onProgress?: (percentage: number) => void;
|
|
37
|
+
signal?: AbortSignal;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Uploads a video through the default host HTTP endpoint and reports progress through XHR.
|
|
41
|
+
*
|
|
42
|
+
* @param options Video upload options.
|
|
43
|
+
* @param options.contentType Logical content type that scopes the upload target.
|
|
44
|
+
* @param options.file Video file selected by the user.
|
|
45
|
+
* @param options.onProgress Optional upload progress callback that receives a 0-100 percentage.
|
|
46
|
+
* @param options.signal Optional abort signal forwarded to the underlying XHR request.
|
|
47
|
+
* @returns The uploaded public video URL.
|
|
48
|
+
* @throws When the upload fails or does not return a URL.
|
|
49
|
+
*/
|
|
50
|
+
declare const uploadEditorVideo: ({ contentType, file, onProgress, signal, }: UploadEditorVideoOptions) => Promise<string>;
|
|
51
|
+
|
|
52
|
+
export { uploadEditorFile, uploadEditorImage, uploadEditorVideo };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { a as EditorContentType, E as EditorAttachment, b as EditorImageUploadKind } from './image-upload-kind-BJqItE_C.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Uploads an attachment through the default host HTTP endpoint.
|
|
5
|
+
*
|
|
6
|
+
* @param options Attachment upload options.
|
|
7
|
+
* @param options.contentType Logical content type that scopes the upload target.
|
|
8
|
+
* @param options.file File selected by the user.
|
|
9
|
+
* @returns Uploaded attachment metadata normalized for editor insertion.
|
|
10
|
+
* @throws When the upload fails or the response payload is invalid.
|
|
11
|
+
*/
|
|
12
|
+
declare const uploadEditorFile: ({ contentType, file, }: {
|
|
13
|
+
contentType: EditorContentType;
|
|
14
|
+
file: File;
|
|
15
|
+
}) => Promise<EditorAttachment>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Uploads an editor image through the default host HTTP endpoint.
|
|
19
|
+
*
|
|
20
|
+
* @param options Image upload options.
|
|
21
|
+
* @param options.contentType Logical content type that scopes the upload target.
|
|
22
|
+
* @param options.file Original file selected by the user.
|
|
23
|
+
* @param options.imageKind Upload intent used to choose the image optimization path.
|
|
24
|
+
* @returns The final public image URL returned by the host.
|
|
25
|
+
* @throws When the response is not successful or does not include a public URL.
|
|
26
|
+
*/
|
|
27
|
+
declare const uploadEditorImage: ({ contentType, file, imageKind, }: {
|
|
28
|
+
contentType: EditorContentType;
|
|
29
|
+
file: File;
|
|
30
|
+
imageKind: EditorImageUploadKind;
|
|
31
|
+
}) => Promise<string>;
|
|
32
|
+
|
|
33
|
+
type UploadEditorVideoOptions = {
|
|
34
|
+
contentType: EditorContentType;
|
|
35
|
+
file: File;
|
|
36
|
+
onProgress?: (percentage: number) => void;
|
|
37
|
+
signal?: AbortSignal;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Uploads a video through the default host HTTP endpoint and reports progress through XHR.
|
|
41
|
+
*
|
|
42
|
+
* @param options Video upload options.
|
|
43
|
+
* @param options.contentType Logical content type that scopes the upload target.
|
|
44
|
+
* @param options.file Video file selected by the user.
|
|
45
|
+
* @param options.onProgress Optional upload progress callback that receives a 0-100 percentage.
|
|
46
|
+
* @param options.signal Optional abort signal forwarded to the underlying XHR request.
|
|
47
|
+
* @returns The uploaded public video URL.
|
|
48
|
+
* @throws When the upload fails or does not return a URL.
|
|
49
|
+
*/
|
|
50
|
+
declare const uploadEditorVideo: ({ contentType, file, onProgress, signal, }: UploadEditorVideoOptions) => Promise<string>;
|
|
51
|
+
|
|
52
|
+
export { uploadEditorFile, uploadEditorImage, uploadEditorVideo };
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// src/integrations/default-host/api/upload-editor-file.ts
|
|
2
|
+
var uploadEditorFile = async ({
|
|
3
|
+
contentType,
|
|
4
|
+
file
|
|
5
|
+
}) => {
|
|
6
|
+
const formData = new FormData();
|
|
7
|
+
formData.set("contentType", contentType);
|
|
8
|
+
formData.set("file", file);
|
|
9
|
+
const response = await fetch("/api/attachments", {
|
|
10
|
+
body: formData,
|
|
11
|
+
method: "POST"
|
|
12
|
+
});
|
|
13
|
+
let body = {};
|
|
14
|
+
try {
|
|
15
|
+
body = await response.json();
|
|
16
|
+
} catch {
|
|
17
|
+
body = {
|
|
18
|
+
error: response.ok ? "Attachment response parse failed" : "Attachment upload failed",
|
|
19
|
+
message: response.statusText || void 0
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (!response.ok || !body.url || !body.fileName || typeof body.fileSize !== "number" || !body.contentType) {
|
|
23
|
+
throw new Error(body.error ?? body.message ?? "Attachment upload failed");
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
contentType: body.contentType,
|
|
27
|
+
fileName: body.fileName,
|
|
28
|
+
fileSize: body.fileSize,
|
|
29
|
+
url: body.url
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// src/shared/lib/image/optimize-image-file.ts
|
|
34
|
+
var OUTPUT_TYPE = "image/webp";
|
|
35
|
+
var OPTIMIZABLE_IMAGE_TYPES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/webp"]);
|
|
36
|
+
var resolveImageOptimizationDimensions = ({
|
|
37
|
+
height,
|
|
38
|
+
maxHeight,
|
|
39
|
+
maxWidth,
|
|
40
|
+
width
|
|
41
|
+
}) => {
|
|
42
|
+
const scale = Math.min(1, maxWidth / width, maxHeight / height);
|
|
43
|
+
return {
|
|
44
|
+
height: Math.max(1, Math.round(height * scale)),
|
|
45
|
+
width: Math.max(1, Math.round(width * scale))
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
var resolveOptimizedImageFileName = (fileName) => fileName.replace(/\.[^./]+$/, "") + ".webp";
|
|
49
|
+
var loadImageElement = (file, errorLabel) => new Promise((resolve, reject) => {
|
|
50
|
+
const objectUrl = URL.createObjectURL(file);
|
|
51
|
+
const image = new Image();
|
|
52
|
+
image.onload = () => {
|
|
53
|
+
URL.revokeObjectURL(objectUrl);
|
|
54
|
+
resolve(image);
|
|
55
|
+
};
|
|
56
|
+
image.onerror = () => {
|
|
57
|
+
URL.revokeObjectURL(objectUrl);
|
|
58
|
+
reject(new Error(`[${errorLabel}] Failed to decode the image.`));
|
|
59
|
+
};
|
|
60
|
+
image.src = objectUrl;
|
|
61
|
+
});
|
|
62
|
+
var convertCanvasToBlob = ({
|
|
63
|
+
canvas,
|
|
64
|
+
errorLabel,
|
|
65
|
+
outputQuality
|
|
66
|
+
}) => new Promise((resolve, reject) => {
|
|
67
|
+
canvas.toBlob(
|
|
68
|
+
(blob) => {
|
|
69
|
+
if (!blob) {
|
|
70
|
+
reject(new Error(`[${errorLabel}] Failed to convert the canvas to a blob.`));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
resolve(blob);
|
|
74
|
+
},
|
|
75
|
+
OUTPUT_TYPE,
|
|
76
|
+
outputQuality
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
var optimizeImageFile = async ({
|
|
80
|
+
errorLabel,
|
|
81
|
+
file,
|
|
82
|
+
maxHeight,
|
|
83
|
+
maxWidth,
|
|
84
|
+
outputQuality
|
|
85
|
+
}) => {
|
|
86
|
+
if (!OPTIMIZABLE_IMAGE_TYPES.has(file.type)) {
|
|
87
|
+
return file;
|
|
88
|
+
}
|
|
89
|
+
const image = await loadImageElement(file, errorLabel);
|
|
90
|
+
const { height, width } = resolveImageOptimizationDimensions({
|
|
91
|
+
height: image.naturalHeight,
|
|
92
|
+
maxHeight,
|
|
93
|
+
maxWidth,
|
|
94
|
+
width: image.naturalWidth
|
|
95
|
+
});
|
|
96
|
+
const canvas = document.createElement("canvas");
|
|
97
|
+
const context = canvas.getContext("2d");
|
|
98
|
+
if (!context) {
|
|
99
|
+
throw new Error(`[${errorLabel}] Failed to create a canvas context.`);
|
|
100
|
+
}
|
|
101
|
+
canvas.width = width;
|
|
102
|
+
canvas.height = height;
|
|
103
|
+
context.drawImage(image, 0, 0, width, height);
|
|
104
|
+
const optimizedBlob = await convertCanvasToBlob({
|
|
105
|
+
canvas,
|
|
106
|
+
errorLabel,
|
|
107
|
+
outputQuality
|
|
108
|
+
});
|
|
109
|
+
if (optimizedBlob.size >= file.size) {
|
|
110
|
+
return file;
|
|
111
|
+
}
|
|
112
|
+
return new File([optimizedBlob], resolveOptimizedImageFileName(file.name), {
|
|
113
|
+
lastModified: file.lastModified,
|
|
114
|
+
type: OUTPUT_TYPE
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// src/shared/lib/image/optimize-content-image-file.ts
|
|
119
|
+
var CONTENT_MAX_WIDTH = 1600;
|
|
120
|
+
var CONTENT_MAX_HEIGHT = 1600;
|
|
121
|
+
var CONTENT_OUTPUT_QUALITY = 0.84;
|
|
122
|
+
var optimizeContentImageFile = (file) => optimizeImageFile({
|
|
123
|
+
errorLabel: "content-image-optimization",
|
|
124
|
+
file,
|
|
125
|
+
maxHeight: CONTENT_MAX_HEIGHT,
|
|
126
|
+
maxWidth: CONTENT_MAX_WIDTH,
|
|
127
|
+
outputQuality: CONTENT_OUTPUT_QUALITY
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// src/shared/lib/image/optimize-thumbnail-image-file.ts
|
|
131
|
+
var THUMBNAIL_MAX_WIDTH = 800;
|
|
132
|
+
var THUMBNAIL_MAX_HEIGHT = 800;
|
|
133
|
+
var THUMBNAIL_OUTPUT_QUALITY = 0.82;
|
|
134
|
+
var optimizeThumbnailImageFile = (file) => optimizeImageFile({
|
|
135
|
+
errorLabel: "thumbnail-optimization",
|
|
136
|
+
file,
|
|
137
|
+
maxHeight: THUMBNAIL_MAX_HEIGHT,
|
|
138
|
+
maxWidth: THUMBNAIL_MAX_WIDTH,
|
|
139
|
+
outputQuality: THUMBNAIL_OUTPUT_QUALITY
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// src/integrations/default-host/api/upload-editor-image.ts
|
|
143
|
+
var uploadEditorImage = async ({
|
|
144
|
+
contentType,
|
|
145
|
+
file,
|
|
146
|
+
imageKind
|
|
147
|
+
}) => {
|
|
148
|
+
const optimizedFile = imageKind === "thumbnail" ? await optimizeThumbnailImageFile(file) : await optimizeContentImageFile(file);
|
|
149
|
+
const formData = new FormData();
|
|
150
|
+
formData.set("contentType", contentType);
|
|
151
|
+
formData.set("file", optimizedFile);
|
|
152
|
+
formData.set("imageKind", imageKind);
|
|
153
|
+
const response = await fetch("/api/images", {
|
|
154
|
+
body: formData,
|
|
155
|
+
method: "POST"
|
|
156
|
+
});
|
|
157
|
+
let body = {};
|
|
158
|
+
try {
|
|
159
|
+
body = await response.json();
|
|
160
|
+
} catch {
|
|
161
|
+
body = {
|
|
162
|
+
error: response.ok ? "Image response parse failed" : "Image upload failed",
|
|
163
|
+
message: response.statusText || void 0
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (!response.ok || !body.url) {
|
|
167
|
+
throw new Error(body.error ?? body.message ?? "Image upload failed");
|
|
168
|
+
}
|
|
169
|
+
return body.url;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// src/integrations/default-host/api/upload-editor-video.ts
|
|
173
|
+
var createAbortUploadError = () => {
|
|
174
|
+
const error = new Error("Video upload aborted");
|
|
175
|
+
error.name = "AbortError";
|
|
176
|
+
return error;
|
|
177
|
+
};
|
|
178
|
+
var parseUploadResponse = (responseText) => {
|
|
179
|
+
try {
|
|
180
|
+
return JSON.parse(responseText);
|
|
181
|
+
} catch {
|
|
182
|
+
return {};
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
var uploadEditorVideo = async ({
|
|
186
|
+
contentType,
|
|
187
|
+
file,
|
|
188
|
+
onProgress,
|
|
189
|
+
signal
|
|
190
|
+
}) => {
|
|
191
|
+
const formData = new FormData();
|
|
192
|
+
formData.set("contentType", contentType);
|
|
193
|
+
formData.set("file", file);
|
|
194
|
+
return await new Promise((resolve, reject) => {
|
|
195
|
+
const xhr = new XMLHttpRequest();
|
|
196
|
+
const cleanup = () => {
|
|
197
|
+
xhr.upload.onprogress = null;
|
|
198
|
+
xhr.onload = null;
|
|
199
|
+
xhr.onerror = null;
|
|
200
|
+
xhr.onabort = null;
|
|
201
|
+
signal?.removeEventListener("abort", handleAbortSignal);
|
|
202
|
+
};
|
|
203
|
+
const handleAbortSignal = () => {
|
|
204
|
+
xhr.abort();
|
|
205
|
+
};
|
|
206
|
+
xhr.open("POST", "/api/videos");
|
|
207
|
+
xhr.upload.onprogress = (event) => {
|
|
208
|
+
if (!event.lengthComputable) return;
|
|
209
|
+
onProgress?.(Math.min(100, Math.round(event.loaded / event.total * 100)));
|
|
210
|
+
};
|
|
211
|
+
xhr.onload = () => {
|
|
212
|
+
cleanup();
|
|
213
|
+
const body = parseUploadResponse(xhr.responseText);
|
|
214
|
+
if (xhr.status < 200 || xhr.status >= 300 || !body.url) {
|
|
215
|
+
reject(new Error(body.error ?? body.message ?? "Video upload failed"));
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
resolve(body.url);
|
|
219
|
+
};
|
|
220
|
+
xhr.onerror = () => {
|
|
221
|
+
cleanup();
|
|
222
|
+
reject(new Error("Video upload failed"));
|
|
223
|
+
};
|
|
224
|
+
xhr.onabort = () => {
|
|
225
|
+
cleanup();
|
|
226
|
+
reject(createAbortUploadError());
|
|
227
|
+
};
|
|
228
|
+
if (signal) {
|
|
229
|
+
if (signal.aborted) {
|
|
230
|
+
xhr.abort();
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
signal.addEventListener("abort", handleAbortSignal, { once: true });
|
|
234
|
+
}
|
|
235
|
+
xhr.send(formData);
|
|
236
|
+
});
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
export { uploadEditorFile, uploadEditorImage, uploadEditorVideo };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { R as ResolvedMarkdownPrimitiveRegistry } from './markdown-primitive-contract-BXsqbKwY.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns the package's default primitive registry, currently backed by Panda-based components.
|
|
5
|
+
*/
|
|
6
|
+
declare const createDefaultMarkdownPrimitiveRegistry: () => ResolvedMarkdownPrimitiveRegistry;
|
|
7
|
+
/**
|
|
8
|
+
* Returns the currently bundled Panda-backed primitive registry for hosts that want the concrete default shell.
|
|
9
|
+
*/
|
|
10
|
+
declare const createPandaMarkdownPrimitiveRegistry: () => ResolvedMarkdownPrimitiveRegistry;
|
|
11
|
+
|
|
12
|
+
export { createPandaMarkdownPrimitiveRegistry as a, createDefaultMarkdownPrimitiveRegistry as c };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { R as ResolvedMarkdownPrimitiveRegistry } from './markdown-primitive-contract-BXsqbKwY.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns the package's default primitive registry, currently backed by Panda-based components.
|
|
5
|
+
*/
|
|
6
|
+
declare const createDefaultMarkdownPrimitiveRegistry: () => ResolvedMarkdownPrimitiveRegistry;
|
|
7
|
+
/**
|
|
8
|
+
* Returns the currently bundled Panda-backed primitive registry for hosts that want the concrete default shell.
|
|
9
|
+
*/
|
|
10
|
+
declare const createPandaMarkdownPrimitiveRegistry: () => ResolvedMarkdownPrimitiveRegistry;
|
|
11
|
+
|
|
12
|
+
export { createPandaMarkdownPrimitiveRegistry as a, createDefaultMarkdownPrimitiveRegistry as c };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type EditorContentType = 'article' | 'project' | 'resume';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Public metadata for an attachment embedded in editor content.
|
|
5
|
+
*/
|
|
6
|
+
type EditorAttachment = {
|
|
7
|
+
contentType: string;
|
|
8
|
+
fileName: string;
|
|
9
|
+
fileSize: number;
|
|
10
|
+
url: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Distinguishes the supported image upload intents.
|
|
15
|
+
*/
|
|
16
|
+
type EditorImageUploadKind = 'content' | 'thumbnail';
|
|
17
|
+
|
|
18
|
+
export type { EditorAttachment as E, EditorContentType as a, EditorImageUploadKind as b };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type EditorContentType = 'article' | 'project' | 'resume';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Public metadata for an attachment embedded in editor content.
|
|
5
|
+
*/
|
|
6
|
+
type EditorAttachment = {
|
|
7
|
+
contentType: string;
|
|
8
|
+
fileName: string;
|
|
9
|
+
fileSize: number;
|
|
10
|
+
url: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Distinguishes the supported image upload intents.
|
|
15
|
+
*/
|
|
16
|
+
type EditorImageUploadKind = 'content' | 'thumbnail';
|
|
17
|
+
|
|
18
|
+
export type { EditorAttachment as E, EditorContentType as a, EditorImageUploadKind as b };
|