@gallop.software/studio 0.1.88 → 0.1.90
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/{StudioUI-Y35A2T7S.js → StudioUI-HWUO2H6J.js} +228 -37
- package/dist/StudioUI-HWUO2H6J.js.map +1 -0
- package/dist/{StudioUI-6CQ7MX7R.mjs → StudioUI-LWHNOTSN.mjs} +221 -30
- package/dist/StudioUI-LWHNOTSN.mjs.map +1 -0
- package/dist/{chunk-CN5NRNWB.js → chunk-JWAAU3NN.js} +1 -1
- package/dist/chunk-JWAAU3NN.js.map +1 -0
- package/dist/{chunk-3RI33B7A.mjs → chunk-ZGXOYJKZ.mjs} +1 -1
- package/dist/chunk-ZGXOYJKZ.mjs.map +1 -0
- package/dist/handlers/index.d.mts +1 -1
- package/dist/handlers/index.d.ts +1 -1
- package/dist/handlers/index.js +447 -329
- package/dist/handlers/index.js.map +1 -1
- package/dist/handlers/index.mjs +457 -339
- package/dist/handlers/index.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -3
- package/dist/index.mjs +2 -2
- package/dist/{types-C9CMIJLW.d.mts → types-DzM_J-55.d.mts} +11 -8
- package/dist/{types-C9CMIJLW.d.ts → types-DzM_J-55.d.ts} +11 -8
- package/package.json +1 -1
- package/dist/StudioUI-6CQ7MX7R.mjs.map +0 -1
- package/dist/StudioUI-Y35A2T7S.js.map +0 -1
- package/dist/chunk-3RI33B7A.mjs.map +0 -1
- package/dist/chunk-CN5NRNWB.js.map +0 -1
package/dist/handlers/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
var _chunkJWAAU3NNjs = require('../chunk-JWAAU3NN.js');
|
|
4
5
|
|
|
5
6
|
// src/handlers/index.ts
|
|
6
7
|
var _server = require('next/server');
|
|
@@ -9,7 +10,6 @@ var _server = require('next/server');
|
|
|
9
10
|
|
|
10
11
|
var _fs = require('fs');
|
|
11
12
|
var _path = require('path'); var _path2 = _interopRequireDefault(_path);
|
|
12
|
-
var _sharp = require('sharp'); var _sharp2 = _interopRequireDefault(_sharp);
|
|
13
13
|
|
|
14
14
|
// src/handlers/utils/meta.ts
|
|
15
15
|
|
|
@@ -32,7 +32,6 @@ async function saveMeta(meta) {
|
|
|
32
32
|
|
|
33
33
|
// src/handlers/utils/files.ts
|
|
34
34
|
|
|
35
|
-
|
|
36
35
|
function isImageFile(filename) {
|
|
37
36
|
const ext = _path2.default.extname(filename).toLowerCase();
|
|
38
37
|
return [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".ico", ".bmp", ".tiff", ".tif"].includes(ext);
|
|
@@ -63,34 +62,11 @@ function getContentType(filePath) {
|
|
|
63
62
|
return "application/octet-stream";
|
|
64
63
|
}
|
|
65
64
|
}
|
|
66
|
-
async function getFolderStats(folderPath) {
|
|
67
|
-
let fileCount = 0;
|
|
68
|
-
let totalSize = 0;
|
|
69
|
-
async function scanFolder(dir) {
|
|
70
|
-
try {
|
|
71
|
-
const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
|
|
72
|
-
for (const entry of entries) {
|
|
73
|
-
if (entry.name.startsWith(".")) continue;
|
|
74
|
-
const fullPath = _path2.default.join(dir, entry.name);
|
|
75
|
-
if (entry.isDirectory()) {
|
|
76
|
-
await scanFolder(fullPath);
|
|
77
|
-
} else if (isMediaFile(entry.name)) {
|
|
78
|
-
fileCount++;
|
|
79
|
-
const stats = await _fs.promises.stat(fullPath);
|
|
80
|
-
totalSize += stats.size;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
} catch (e2) {
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
await scanFolder(folderPath);
|
|
87
|
-
return { fileCount, totalSize };
|
|
88
|
-
}
|
|
89
65
|
|
|
90
66
|
// src/handlers/utils/thumbnails.ts
|
|
91
67
|
|
|
92
68
|
|
|
93
|
-
|
|
69
|
+
var _sharp = require('sharp'); var _sharp2 = _interopRequireDefault(_sharp);
|
|
94
70
|
var _blurhash = require('blurhash');
|
|
95
71
|
var DEFAULT_SIZES = {
|
|
96
72
|
small: { width: 300, suffix: "-sm" },
|
|
@@ -138,7 +114,7 @@ async function processImage(buffer, imageKey) {
|
|
|
138
114
|
return {
|
|
139
115
|
w: originalWidth,
|
|
140
116
|
h: originalHeight,
|
|
141
|
-
|
|
117
|
+
b: blurhash
|
|
142
118
|
};
|
|
143
119
|
}
|
|
144
120
|
|
|
@@ -180,7 +156,7 @@ async function uploadToCdn(imageKey) {
|
|
|
180
156
|
const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;
|
|
181
157
|
if (!bucketName) throw new Error("R2 bucket not configured");
|
|
182
158
|
const r2 = getR2Client();
|
|
183
|
-
for (const thumbPath of
|
|
159
|
+
for (const thumbPath of _chunkJWAAU3NNjs.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
184
160
|
const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
|
|
185
161
|
try {
|
|
186
162
|
const fileBuffer = await _fs.promises.readFile(localPath);
|
|
@@ -192,16 +168,16 @@ async function uploadToCdn(imageKey) {
|
|
|
192
168
|
ContentType: getContentType(thumbPath)
|
|
193
169
|
})
|
|
194
170
|
);
|
|
195
|
-
} catch (
|
|
171
|
+
} catch (e2) {
|
|
196
172
|
}
|
|
197
173
|
}
|
|
198
174
|
}
|
|
199
175
|
async function deleteLocalThumbnails(imageKey) {
|
|
200
|
-
for (const thumbPath of
|
|
176
|
+
for (const thumbPath of _chunkJWAAU3NNjs.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
201
177
|
const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
|
|
202
178
|
try {
|
|
203
179
|
await _fs.promises.unlink(localPath);
|
|
204
|
-
} catch (
|
|
180
|
+
} catch (e3) {
|
|
205
181
|
}
|
|
206
182
|
}
|
|
207
183
|
}
|
|
@@ -211,70 +187,85 @@ async function handleList(request) {
|
|
|
211
187
|
const searchParams = request.nextUrl.searchParams;
|
|
212
188
|
const requestedPath = searchParams.get("path") || "public";
|
|
213
189
|
try {
|
|
214
|
-
const
|
|
215
|
-
const
|
|
216
|
-
if (
|
|
217
|
-
return _server.NextResponse.json({
|
|
190
|
+
const meta = await loadMeta();
|
|
191
|
+
const metaKeys = Object.keys(meta);
|
|
192
|
+
if (metaKeys.length === 0) {
|
|
193
|
+
return _server.NextResponse.json({ items: [], isEmpty: true });
|
|
218
194
|
}
|
|
195
|
+
const relativePath = requestedPath.replace(/^public\/?/, "");
|
|
196
|
+
const pathPrefix = relativePath ? `/${relativePath}/` : "/";
|
|
219
197
|
const items = [];
|
|
220
|
-
const
|
|
221
|
-
for (const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
198
|
+
const seenFolders = /* @__PURE__ */ new Set();
|
|
199
|
+
for (const key of metaKeys) {
|
|
200
|
+
const entry = meta[key];
|
|
201
|
+
if (!key.startsWith(pathPrefix) && pathPrefix !== "/") continue;
|
|
202
|
+
if (pathPrefix === "/" && !key.startsWith("/")) continue;
|
|
203
|
+
const remaining = pathPrefix === "/" ? key.slice(1) : key.slice(pathPrefix.length);
|
|
204
|
+
if (!remaining) continue;
|
|
205
|
+
const slashIndex = remaining.indexOf("/");
|
|
206
|
+
if (slashIndex !== -1) {
|
|
207
|
+
const folderName = remaining.slice(0, slashIndex);
|
|
208
|
+
if (!seenFolders.has(folderName)) {
|
|
209
|
+
seenFolders.add(folderName);
|
|
210
|
+
const folderPrefix = pathPrefix === "/" ? `/${folderName}/` : `${pathPrefix}${folderName}/`;
|
|
211
|
+
let fileCount = 0;
|
|
212
|
+
for (const k of metaKeys) {
|
|
213
|
+
if (k.startsWith(folderPrefix)) fileCount++;
|
|
214
|
+
}
|
|
215
|
+
items.push({
|
|
216
|
+
name: folderName,
|
|
217
|
+
path: relativePath ? `public/${relativePath}/${folderName}` : `public/${folderName}`,
|
|
218
|
+
type: "folder",
|
|
219
|
+
fileCount
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
} else {
|
|
223
|
+
const fileName = remaining;
|
|
224
|
+
const isImage = isImageFile(fileName);
|
|
225
|
+
const isSynced = entry.c === 1;
|
|
237
226
|
let thumbnail;
|
|
238
227
|
let hasThumbnail = false;
|
|
239
|
-
let
|
|
240
|
-
if (isImage) {
|
|
241
|
-
const
|
|
242
|
-
if (
|
|
243
|
-
|
|
244
|
-
|
|
228
|
+
let fileSize;
|
|
229
|
+
if (isImage && (entry.w || entry.b)) {
|
|
230
|
+
const thumbPath = _chunkJWAAU3NNjs.getThumbnailPath.call(void 0, key, "sm");
|
|
231
|
+
if (isSynced) {
|
|
232
|
+
const cdnUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL || process.env.NEXT_PUBLIC_CLOUDFLARE_R2_PUBLIC_URL;
|
|
233
|
+
if (cdnUrl) {
|
|
234
|
+
thumbnail = `${cdnUrl}${thumbPath}`;
|
|
235
|
+
hasThumbnail = true;
|
|
236
|
+
}
|
|
245
237
|
} else {
|
|
246
|
-
const
|
|
247
|
-
const baseName = _path2.default.basename(entry.name, ext);
|
|
248
|
-
const thumbnailDir = relativePath ? `images/${relativePath}` : "images";
|
|
249
|
-
const thumbnailName = `${baseName}-sm${ext === ".png" ? ".png" : ".jpg"}`;
|
|
250
|
-
const thumbnailPath = _path2.default.join(process.cwd(), "public", thumbnailDir, thumbnailName);
|
|
238
|
+
const localThumbPath = _path2.default.join(process.cwd(), "public", thumbPath);
|
|
251
239
|
try {
|
|
252
|
-
await _fs.promises.access(
|
|
253
|
-
thumbnail =
|
|
240
|
+
await _fs.promises.access(localThumbPath);
|
|
241
|
+
thumbnail = thumbPath;
|
|
254
242
|
hasThumbnail = true;
|
|
255
|
-
} catch (
|
|
256
|
-
thumbnail =
|
|
243
|
+
} catch (e4) {
|
|
244
|
+
thumbnail = key;
|
|
257
245
|
hasThumbnail = false;
|
|
258
246
|
}
|
|
259
247
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
248
|
+
} else if (isImage) {
|
|
249
|
+
thumbnail = key;
|
|
250
|
+
hasThumbnail = false;
|
|
251
|
+
}
|
|
252
|
+
if (!isSynced) {
|
|
253
|
+
try {
|
|
254
|
+
const filePath = _path2.default.join(process.cwd(), "public", key);
|
|
255
|
+
const stats = await _fs.promises.stat(filePath);
|
|
256
|
+
fileSize = stats.size;
|
|
257
|
+
} catch (e5) {
|
|
268
258
|
}
|
|
269
259
|
}
|
|
270
260
|
items.push({
|
|
271
|
-
name:
|
|
272
|
-
path:
|
|
261
|
+
name: fileName,
|
|
262
|
+
path: relativePath ? `public/${relativePath}/${fileName}` : `public/${fileName}`,
|
|
273
263
|
type: "file",
|
|
274
|
-
size:
|
|
264
|
+
size: fileSize,
|
|
275
265
|
thumbnail,
|
|
276
266
|
hasThumbnail,
|
|
277
|
-
|
|
267
|
+
cdnSynced: isSynced,
|
|
268
|
+
dimensions: entry.w && entry.h ? { width: entry.w, height: entry.h } : void 0
|
|
278
269
|
});
|
|
279
270
|
}
|
|
280
271
|
}
|
|
@@ -291,62 +282,49 @@ async function handleSearch(request) {
|
|
|
291
282
|
return _server.NextResponse.json({ items: [] });
|
|
292
283
|
}
|
|
293
284
|
try {
|
|
285
|
+
const meta = await loadMeta();
|
|
294
286
|
const items = [];
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
hasThumbnail = true;
|
|
321
|
-
} catch (e7) {
|
|
322
|
-
thumbnail = `/${itemRelPath}`;
|
|
323
|
-
hasThumbnail = false;
|
|
324
|
-
}
|
|
325
|
-
if (!entry.name.toLowerCase().endsWith(".svg")) {
|
|
326
|
-
try {
|
|
327
|
-
const metadata = await _sharp2.default.call(void 0, fullPath).metadata();
|
|
328
|
-
if (metadata.width && metadata.height) {
|
|
329
|
-
dimensions = { width: metadata.width, height: metadata.height };
|
|
330
|
-
}
|
|
331
|
-
} catch (e8) {
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
items.push({
|
|
335
|
-
name: entry.name,
|
|
336
|
-
path: itemPath,
|
|
337
|
-
type: "file",
|
|
338
|
-
size: stats.size,
|
|
339
|
-
thumbnail,
|
|
340
|
-
hasThumbnail,
|
|
341
|
-
dimensions
|
|
342
|
-
});
|
|
343
|
-
}
|
|
287
|
+
for (const [key, entry] of Object.entries(meta)) {
|
|
288
|
+
if (!key.toLowerCase().includes(query)) continue;
|
|
289
|
+
const fileName = _path2.default.basename(key);
|
|
290
|
+
const relativePath = key.slice(1);
|
|
291
|
+
const isImage = isImageFile(fileName);
|
|
292
|
+
const isSynced = entry.c === 1;
|
|
293
|
+
let thumbnail;
|
|
294
|
+
let hasThumbnail = false;
|
|
295
|
+
if (isImage && (entry.w || entry.b)) {
|
|
296
|
+
const thumbPath = _chunkJWAAU3NNjs.getThumbnailPath.call(void 0, key, "sm");
|
|
297
|
+
if (isSynced) {
|
|
298
|
+
const cdnUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL || process.env.NEXT_PUBLIC_CLOUDFLARE_R2_PUBLIC_URL;
|
|
299
|
+
if (cdnUrl) {
|
|
300
|
+
thumbnail = `${cdnUrl}${thumbPath}`;
|
|
301
|
+
hasThumbnail = true;
|
|
302
|
+
}
|
|
303
|
+
} else {
|
|
304
|
+
const localThumbPath = _path2.default.join(process.cwd(), "public", thumbPath);
|
|
305
|
+
try {
|
|
306
|
+
await _fs.promises.access(localThumbPath);
|
|
307
|
+
thumbnail = thumbPath;
|
|
308
|
+
hasThumbnail = true;
|
|
309
|
+
} catch (e6) {
|
|
310
|
+
thumbnail = key;
|
|
311
|
+
hasThumbnail = false;
|
|
344
312
|
}
|
|
345
313
|
}
|
|
346
|
-
}
|
|
314
|
+
} else if (isImage) {
|
|
315
|
+
thumbnail = key;
|
|
316
|
+
hasThumbnail = false;
|
|
347
317
|
}
|
|
318
|
+
items.push({
|
|
319
|
+
name: fileName,
|
|
320
|
+
path: `public/${relativePath}`,
|
|
321
|
+
type: "file",
|
|
322
|
+
thumbnail,
|
|
323
|
+
hasThumbnail,
|
|
324
|
+
cdnSynced: isSynced,
|
|
325
|
+
dimensions: entry.w && entry.h ? { width: entry.w, height: entry.h } : void 0
|
|
326
|
+
});
|
|
348
327
|
}
|
|
349
|
-
await searchDir(publicDir, "");
|
|
350
328
|
return _server.NextResponse.json({ items });
|
|
351
329
|
} catch (error) {
|
|
352
330
|
console.error("Failed to search:", error);
|
|
@@ -355,27 +333,28 @@ async function handleSearch(request) {
|
|
|
355
333
|
}
|
|
356
334
|
async function handleListFolders() {
|
|
357
335
|
try {
|
|
358
|
-
const
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
const folderRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
367
|
-
folders.push({
|
|
368
|
-
path: `public/${folderRelativePath}`,
|
|
369
|
-
name: entry.name,
|
|
370
|
-
depth
|
|
371
|
-
});
|
|
372
|
-
await scanDir(_path2.default.join(dir, entry.name), folderRelativePath, depth + 1);
|
|
373
|
-
}
|
|
374
|
-
} catch (e10) {
|
|
336
|
+
const meta = await loadMeta();
|
|
337
|
+
const folderSet = /* @__PURE__ */ new Set();
|
|
338
|
+
for (const key of Object.keys(meta)) {
|
|
339
|
+
const parts = key.split("/");
|
|
340
|
+
let current = "";
|
|
341
|
+
for (let i = 1; i < parts.length - 1; i++) {
|
|
342
|
+
current = current ? `${current}/${parts[i]}` : parts[i];
|
|
343
|
+
folderSet.add(current);
|
|
375
344
|
}
|
|
376
345
|
}
|
|
346
|
+
const folders = [];
|
|
377
347
|
folders.push({ path: "public", name: "public", depth: 0 });
|
|
378
|
-
|
|
348
|
+
const sortedFolders = Array.from(folderSet).sort();
|
|
349
|
+
for (const folderPath of sortedFolders) {
|
|
350
|
+
const depth = folderPath.split("/").length;
|
|
351
|
+
const name = folderPath.split("/").pop() || folderPath;
|
|
352
|
+
folders.push({
|
|
353
|
+
path: `public/${folderPath}`,
|
|
354
|
+
name,
|
|
355
|
+
depth
|
|
356
|
+
});
|
|
357
|
+
}
|
|
379
358
|
return _server.NextResponse.json({ folders });
|
|
380
359
|
} catch (error) {
|
|
381
360
|
console.error("Failed to list folders:", error);
|
|
@@ -384,26 +363,14 @@ async function handleListFolders() {
|
|
|
384
363
|
}
|
|
385
364
|
async function handleCountImages() {
|
|
386
365
|
try {
|
|
366
|
+
const meta = await loadMeta();
|
|
387
367
|
const allImages = [];
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
if (entry.name.startsWith(".")) continue;
|
|
393
|
-
const fullPath = _path2.default.join(dir, entry.name);
|
|
394
|
-
const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
395
|
-
if (relPath === "images" || relPath.startsWith("images/")) continue;
|
|
396
|
-
if (entry.isDirectory()) {
|
|
397
|
-
await scanPublicFolder(fullPath, relPath);
|
|
398
|
-
} else if (isImageFile(entry.name)) {
|
|
399
|
-
allImages.push(relPath);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
} catch (e11) {
|
|
368
|
+
for (const key of Object.keys(meta)) {
|
|
369
|
+
const fileName = _path2.default.basename(key);
|
|
370
|
+
if (isImageFile(fileName)) {
|
|
371
|
+
allImages.push(key.slice(1));
|
|
403
372
|
}
|
|
404
373
|
}
|
|
405
|
-
const publicDir = _path2.default.join(process.cwd(), "public");
|
|
406
|
-
await scanPublicFolder(publicDir);
|
|
407
374
|
return _server.NextResponse.json({
|
|
408
375
|
count: allImages.length,
|
|
409
376
|
images: allImages
|
|
@@ -421,29 +388,22 @@ async function handleFolderImages(request) {
|
|
|
421
388
|
return _server.NextResponse.json({ error: "No folders provided" }, { status: 400 });
|
|
422
389
|
}
|
|
423
390
|
const folders = foldersParam.split(",");
|
|
391
|
+
const meta = await loadMeta();
|
|
424
392
|
const allImages = [];
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
}
|
|
393
|
+
const prefixes = folders.map((f) => {
|
|
394
|
+
const rel = f.replace(/^public\/?/, "");
|
|
395
|
+
return rel ? `/${rel}/` : "/";
|
|
396
|
+
});
|
|
397
|
+
for (const key of Object.keys(meta)) {
|
|
398
|
+
const fileName = _path2.default.basename(key);
|
|
399
|
+
if (!isImageFile(fileName)) continue;
|
|
400
|
+
for (const prefix of prefixes) {
|
|
401
|
+
if (key.startsWith(prefix) || prefix === "/" && key.startsWith("/")) {
|
|
402
|
+
allImages.push(key.slice(1));
|
|
403
|
+
break;
|
|
437
404
|
}
|
|
438
|
-
} catch (e12) {
|
|
439
405
|
}
|
|
440
406
|
}
|
|
441
|
-
for (const folder of folders) {
|
|
442
|
-
const relativePath = folder.replace(/^public\/?/, "");
|
|
443
|
-
if (relativePath === "images" || relativePath.startsWith("images/")) continue;
|
|
444
|
-
const folderPath = _path2.default.join(process.cwd(), folder);
|
|
445
|
-
await scanFolder(folderPath, relativePath);
|
|
446
|
-
}
|
|
447
407
|
return _server.NextResponse.json({
|
|
448
408
|
count: allImages.length,
|
|
449
409
|
images: allImages
|
|
@@ -459,7 +419,6 @@ async function handleFolderImages(request) {
|
|
|
459
419
|
|
|
460
420
|
|
|
461
421
|
|
|
462
|
-
|
|
463
422
|
async function handleUpload(request) {
|
|
464
423
|
try {
|
|
465
424
|
const formData = await request.formData();
|
|
@@ -471,11 +430,9 @@ async function handleUpload(request) {
|
|
|
471
430
|
const bytes = await file.arrayBuffer();
|
|
472
431
|
const buffer = Buffer.from(bytes);
|
|
473
432
|
const fileName = file.name;
|
|
474
|
-
const baseName = _path2.default.basename(fileName, _path2.default.extname(fileName));
|
|
475
433
|
const ext = _path2.default.extname(fileName).toLowerCase();
|
|
476
434
|
const isImage = isImageFile(fileName);
|
|
477
|
-
const
|
|
478
|
-
const isProcessableImage = isImage && !isSvg;
|
|
435
|
+
const isMedia = isMediaFile(fileName);
|
|
479
436
|
const meta = await loadMeta();
|
|
480
437
|
let relativeDir = "";
|
|
481
438
|
if (targetPath === "public") {
|
|
@@ -489,71 +446,49 @@ async function handleUpload(request) {
|
|
|
489
446
|
{ status: 400 }
|
|
490
447
|
);
|
|
491
448
|
}
|
|
449
|
+
let imageKey = "/" + (relativeDir ? `${relativeDir}/${fileName}` : fileName);
|
|
450
|
+
if (meta[imageKey]) {
|
|
451
|
+
const baseName = _path2.default.basename(fileName, ext);
|
|
452
|
+
let counter = 1;
|
|
453
|
+
let newFileName = `${baseName}-${counter}${ext}`;
|
|
454
|
+
let newKey = "/" + (relativeDir ? `${relativeDir}/${newFileName}` : newFileName);
|
|
455
|
+
while (meta[newKey]) {
|
|
456
|
+
counter++;
|
|
457
|
+
newFileName = `${baseName}-${counter}${ext}`;
|
|
458
|
+
newKey = "/" + (relativeDir ? `${relativeDir}/${newFileName}` : newFileName);
|
|
459
|
+
}
|
|
460
|
+
imageKey = newKey;
|
|
461
|
+
}
|
|
462
|
+
const actualFileName = _path2.default.basename(imageKey);
|
|
492
463
|
const uploadDir = _path2.default.join(process.cwd(), "public", relativeDir);
|
|
493
464
|
await _fs.promises.mkdir(uploadDir, { recursive: true });
|
|
494
|
-
await _fs.promises.writeFile(_path2.default.join(uploadDir,
|
|
495
|
-
if (!
|
|
465
|
+
await _fs.promises.writeFile(_path2.default.join(uploadDir, actualFileName), buffer);
|
|
466
|
+
if (!isMedia) {
|
|
496
467
|
return _server.NextResponse.json({
|
|
497
468
|
success: true,
|
|
498
|
-
message: "File uploaded
|
|
499
|
-
path: `public/${relativeDir ? relativeDir + "/" : ""}${
|
|
469
|
+
message: "File uploaded (not a media file)",
|
|
470
|
+
path: `public/${relativeDir ? relativeDir + "/" : ""}${actualFileName}`
|
|
500
471
|
});
|
|
501
472
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
let originalWidth = 0;
|
|
512
|
-
let originalHeight = 0;
|
|
513
|
-
let blurhash = "";
|
|
514
|
-
const originalPath = `/${relativeDir ? relativeDir + "/" : ""}${fileName}`;
|
|
515
|
-
if (isSvg) {
|
|
516
|
-
const fullPath = _path2.default.join(imagesPath, fileName);
|
|
517
|
-
await _fs.promises.writeFile(fullPath, buffer);
|
|
518
|
-
} else if (isProcessableImage) {
|
|
519
|
-
const sharpInstance = _sharp2.default.call(void 0, buffer);
|
|
520
|
-
const metadata = await sharpInstance.metadata();
|
|
521
|
-
originalWidth = metadata.width || 0;
|
|
522
|
-
originalHeight = metadata.height || 0;
|
|
523
|
-
const outputExt = ext === ".png" ? ".png" : ".jpg";
|
|
524
|
-
const fullFileName = `${baseName}${outputExt}`;
|
|
525
|
-
const fullPath = _path2.default.join(imagesPath, fullFileName);
|
|
526
|
-
if (ext === ".png") {
|
|
527
|
-
await _sharp2.default.call(void 0, buffer).png({ quality: 85 }).toFile(fullPath);
|
|
528
|
-
} else {
|
|
529
|
-
await _sharp2.default.call(void 0, buffer).jpeg({ quality: 85 }).toFile(fullPath);
|
|
530
|
-
}
|
|
531
|
-
for (const [, sizeConfig] of Object.entries(DEFAULT_SIZES)) {
|
|
532
|
-
const { width: maxWidth, suffix } = sizeConfig;
|
|
533
|
-
if (originalWidth <= maxWidth) {
|
|
534
|
-
continue;
|
|
535
|
-
}
|
|
536
|
-
const ratio = originalHeight / originalWidth;
|
|
537
|
-
const newHeight = Math.round(maxWidth * ratio);
|
|
538
|
-
const sizeFileName = `${baseName}${suffix}${outputExt}`;
|
|
539
|
-
const sizePath = _path2.default.join(imagesPath, sizeFileName);
|
|
540
|
-
if (ext === ".png") {
|
|
541
|
-
await _sharp2.default.call(void 0, buffer).resize(maxWidth, newHeight).png({ quality: 80 }).toFile(sizePath);
|
|
542
|
-
} else {
|
|
543
|
-
await _sharp2.default.call(void 0, buffer).resize(maxWidth, newHeight).jpeg({ quality: 80 }).toFile(sizePath);
|
|
544
|
-
}
|
|
473
|
+
if (isImage && ext !== ".svg") {
|
|
474
|
+
try {
|
|
475
|
+
const metadata = await _sharp2.default.call(void 0, buffer).metadata();
|
|
476
|
+
meta[imageKey] = {
|
|
477
|
+
w: metadata.width || 0,
|
|
478
|
+
h: metadata.height || 0
|
|
479
|
+
};
|
|
480
|
+
} catch (e7) {
|
|
481
|
+
meta[imageKey] = { w: 0, h: 0 };
|
|
545
482
|
}
|
|
546
|
-
|
|
547
|
-
|
|
483
|
+
} else {
|
|
484
|
+
meta[imageKey] = {};
|
|
548
485
|
}
|
|
549
|
-
const entry = {
|
|
550
|
-
w: originalWidth,
|
|
551
|
-
h: originalHeight,
|
|
552
|
-
blur: blurhash
|
|
553
|
-
};
|
|
554
|
-
meta[originalPath] = entry;
|
|
555
486
|
await saveMeta(meta);
|
|
556
|
-
return _server.NextResponse.json({
|
|
487
|
+
return _server.NextResponse.json({
|
|
488
|
+
success: true,
|
|
489
|
+
imageKey,
|
|
490
|
+
message: 'File uploaded. Run "Process Images" to generate thumbnails.'
|
|
491
|
+
});
|
|
557
492
|
} catch (error) {
|
|
558
493
|
console.error("Failed to upload:", error);
|
|
559
494
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -576,31 +511,61 @@ async function handleDelete(request) {
|
|
|
576
511
|
continue;
|
|
577
512
|
}
|
|
578
513
|
const absolutePath = _path2.default.join(process.cwd(), itemPath);
|
|
579
|
-
const
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
514
|
+
const imageKey = "/" + itemPath.replace(/^public\//, "");
|
|
515
|
+
const entry = meta[imageKey];
|
|
516
|
+
const isSynced = _optionalChain([entry, 'optionalAccess', _5 => _5.c]) === 1;
|
|
517
|
+
try {
|
|
518
|
+
const stats = await _fs.promises.stat(absolutePath);
|
|
519
|
+
if (stats.isDirectory()) {
|
|
520
|
+
await _fs.promises.rm(absolutePath, { recursive: true });
|
|
521
|
+
const prefix = imageKey + "/";
|
|
522
|
+
for (const key of Object.keys(meta)) {
|
|
523
|
+
if (key.startsWith(prefix) || key === imageKey) {
|
|
524
|
+
if (!meta[key].c) {
|
|
525
|
+
for (const thumbPath of _chunkJWAAU3NNjs.getAllThumbnailPaths.call(void 0, key)) {
|
|
526
|
+
const absoluteThumbPath = _path2.default.join(process.cwd(), "public", thumbPath);
|
|
527
|
+
try {
|
|
528
|
+
await _fs.promises.unlink(absoluteThumbPath);
|
|
529
|
+
} catch (e8) {
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
delete meta[key];
|
|
534
|
+
}
|
|
586
535
|
}
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
} catch (e13) {
|
|
536
|
+
} else {
|
|
537
|
+
await _fs.promises.unlink(absolutePath);
|
|
538
|
+
const isInImagesFolder = itemPath.startsWith("public/images/");
|
|
539
|
+
if (!isInImagesFolder && entry) {
|
|
540
|
+
if (!isSynced) {
|
|
541
|
+
for (const thumbPath of _chunkJWAAU3NNjs.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
542
|
+
const absoluteThumbPath = _path2.default.join(process.cwd(), "public", thumbPath);
|
|
543
|
+
try {
|
|
544
|
+
await _fs.promises.unlink(absoluteThumbPath);
|
|
545
|
+
} catch (e9) {
|
|
546
|
+
}
|
|
599
547
|
}
|
|
600
548
|
}
|
|
601
549
|
delete meta[imageKey];
|
|
602
550
|
}
|
|
603
551
|
}
|
|
552
|
+
} catch (e10) {
|
|
553
|
+
if (entry) {
|
|
554
|
+
delete meta[imageKey];
|
|
555
|
+
} else {
|
|
556
|
+
const prefix = imageKey + "/";
|
|
557
|
+
let foundAny = false;
|
|
558
|
+
for (const key of Object.keys(meta)) {
|
|
559
|
+
if (key.startsWith(prefix)) {
|
|
560
|
+
delete meta[key];
|
|
561
|
+
foundAny = true;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
if (!foundAny) {
|
|
565
|
+
errors.push(`Not found: ${itemPath}`);
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
604
569
|
}
|
|
605
570
|
deleted.push(itemPath);
|
|
606
571
|
} catch (error) {
|
|
@@ -637,7 +602,7 @@ async function handleCreateFolder(request) {
|
|
|
637
602
|
try {
|
|
638
603
|
await _fs.promises.access(folderPath);
|
|
639
604
|
return _server.NextResponse.json({ error: "A folder with this name already exists" }, { status: 400 });
|
|
640
|
-
} catch (
|
|
605
|
+
} catch (e11) {
|
|
641
606
|
}
|
|
642
607
|
await _fs.promises.mkdir(folderPath, { recursive: true });
|
|
643
608
|
return _server.NextResponse.json({ success: true, path: _path2.default.join(safePath, sanitizedName) });
|
|
@@ -665,13 +630,13 @@ async function handleRename(request) {
|
|
|
665
630
|
}
|
|
666
631
|
try {
|
|
667
632
|
await _fs.promises.access(absoluteOldPath);
|
|
668
|
-
} catch (
|
|
633
|
+
} catch (e12) {
|
|
669
634
|
return _server.NextResponse.json({ error: "File or folder not found" }, { status: 404 });
|
|
670
635
|
}
|
|
671
636
|
try {
|
|
672
637
|
await _fs.promises.access(absoluteNewPath);
|
|
673
638
|
return _server.NextResponse.json({ error: "An item with this name already exists" }, { status: 400 });
|
|
674
|
-
} catch (
|
|
639
|
+
} catch (e13) {
|
|
675
640
|
}
|
|
676
641
|
const stats = await _fs.promises.stat(absoluteOldPath);
|
|
677
642
|
const isFile = stats.isFile();
|
|
@@ -685,15 +650,15 @@ async function handleRename(request) {
|
|
|
685
650
|
const newKey = "/" + newRelativePath;
|
|
686
651
|
if (meta[oldKey]) {
|
|
687
652
|
const entry = meta[oldKey];
|
|
688
|
-
const oldThumbPaths =
|
|
689
|
-
const newThumbPaths =
|
|
653
|
+
const oldThumbPaths = _chunkJWAAU3NNjs.getAllThumbnailPaths.call(void 0, oldKey);
|
|
654
|
+
const newThumbPaths = _chunkJWAAU3NNjs.getAllThumbnailPaths.call(void 0, newKey);
|
|
690
655
|
for (let i = 0; i < oldThumbPaths.length; i++) {
|
|
691
656
|
const oldThumbPath = _path2.default.join(process.cwd(), "public", oldThumbPaths[i]);
|
|
692
657
|
const newThumbPath = _path2.default.join(process.cwd(), "public", newThumbPaths[i]);
|
|
693
658
|
await _fs.promises.mkdir(_path2.default.dirname(newThumbPath), { recursive: true });
|
|
694
659
|
try {
|
|
695
660
|
await _fs.promises.rename(oldThumbPath, newThumbPath);
|
|
696
|
-
} catch (
|
|
661
|
+
} catch (e14) {
|
|
697
662
|
}
|
|
698
663
|
}
|
|
699
664
|
delete meta[oldKey];
|
|
@@ -727,7 +692,7 @@ async function handleMove(request) {
|
|
|
727
692
|
if (!destStats.isDirectory()) {
|
|
728
693
|
return _server.NextResponse.json({ error: "Destination is not a folder" }, { status: 400 });
|
|
729
694
|
}
|
|
730
|
-
} catch (
|
|
695
|
+
} catch (e15) {
|
|
731
696
|
return _server.NextResponse.json({ error: "Destination folder not found" }, { status: 404 });
|
|
732
697
|
}
|
|
733
698
|
const moved = [];
|
|
@@ -745,7 +710,7 @@ async function handleMove(request) {
|
|
|
745
710
|
}
|
|
746
711
|
try {
|
|
747
712
|
await _fs.promises.access(absolutePath);
|
|
748
|
-
} catch (
|
|
713
|
+
} catch (e16) {
|
|
749
714
|
errors.push(`${itemName} not found`);
|
|
750
715
|
continue;
|
|
751
716
|
}
|
|
@@ -753,7 +718,7 @@ async function handleMove(request) {
|
|
|
753
718
|
await _fs.promises.access(newAbsolutePath);
|
|
754
719
|
errors.push(`${itemName} already exists in destination`);
|
|
755
720
|
continue;
|
|
756
|
-
} catch (
|
|
721
|
+
} catch (e17) {
|
|
757
722
|
}
|
|
758
723
|
try {
|
|
759
724
|
await _fs.promises.rename(absolutePath, newAbsolutePath);
|
|
@@ -765,15 +730,15 @@ async function handleMove(request) {
|
|
|
765
730
|
const newKey = "/" + newRelativePath;
|
|
766
731
|
if (meta[oldKey]) {
|
|
767
732
|
const entry = meta[oldKey];
|
|
768
|
-
const oldThumbPaths =
|
|
769
|
-
const newThumbPaths =
|
|
733
|
+
const oldThumbPaths = _chunkJWAAU3NNjs.getAllThumbnailPaths.call(void 0, oldKey);
|
|
734
|
+
const newThumbPaths = _chunkJWAAU3NNjs.getAllThumbnailPaths.call(void 0, newKey);
|
|
770
735
|
for (let i = 0; i < oldThumbPaths.length; i++) {
|
|
771
736
|
const oldThumbPath = _path2.default.join(process.cwd(), "public", oldThumbPaths[i]);
|
|
772
737
|
const newThumbPath = _path2.default.join(process.cwd(), "public", newThumbPaths[i]);
|
|
773
738
|
await _fs.promises.mkdir(_path2.default.dirname(newThumbPath), { recursive: true });
|
|
774
739
|
try {
|
|
775
740
|
await _fs.promises.rename(oldThumbPath, newThumbPath);
|
|
776
|
-
} catch (
|
|
741
|
+
} catch (e18) {
|
|
777
742
|
}
|
|
778
743
|
}
|
|
779
744
|
delete meta[oldKey];
|
|
@@ -782,7 +747,7 @@ async function handleMove(request) {
|
|
|
782
747
|
}
|
|
783
748
|
}
|
|
784
749
|
moved.push(itemPath);
|
|
785
|
-
} catch (
|
|
750
|
+
} catch (e19) {
|
|
786
751
|
errors.push(`Failed to move ${itemName}`);
|
|
787
752
|
}
|
|
788
753
|
}
|
|
@@ -833,15 +798,30 @@ async function handleSync(request) {
|
|
|
833
798
|
for (const imageKey of imageKeys) {
|
|
834
799
|
const entry = meta[imageKey];
|
|
835
800
|
if (!entry) {
|
|
836
|
-
errors.push(`Image not found in meta: ${imageKey}
|
|
801
|
+
errors.push(`Image not found in meta: ${imageKey}. Run Scan first.`);
|
|
837
802
|
continue;
|
|
838
803
|
}
|
|
839
|
-
if (entry.
|
|
804
|
+
if (entry.c) {
|
|
840
805
|
synced.push(imageKey);
|
|
841
806
|
continue;
|
|
842
807
|
}
|
|
843
808
|
try {
|
|
844
|
-
|
|
809
|
+
const originalLocalPath = _path2.default.join(process.cwd(), "public", imageKey);
|
|
810
|
+
try {
|
|
811
|
+
const originalBuffer = await _fs.promises.readFile(originalLocalPath);
|
|
812
|
+
await r2.send(
|
|
813
|
+
new (0, _clients3.PutObjectCommand)({
|
|
814
|
+
Bucket: bucketName,
|
|
815
|
+
Key: imageKey.replace(/^\//, ""),
|
|
816
|
+
Body: originalBuffer,
|
|
817
|
+
ContentType: getContentType(imageKey)
|
|
818
|
+
})
|
|
819
|
+
);
|
|
820
|
+
} catch (err) {
|
|
821
|
+
errors.push(`Original file not found: ${imageKey}`);
|
|
822
|
+
continue;
|
|
823
|
+
}
|
|
824
|
+
for (const thumbPath of _chunkJWAAU3NNjs.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
845
825
|
const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
|
|
846
826
|
try {
|
|
847
827
|
const fileBuffer = await _fs.promises.readFile(localPath);
|
|
@@ -853,21 +833,25 @@ async function handleSync(request) {
|
|
|
853
833
|
ContentType: getContentType(thumbPath)
|
|
854
834
|
})
|
|
855
835
|
);
|
|
856
|
-
} catch (
|
|
836
|
+
} catch (e20) {
|
|
857
837
|
}
|
|
858
838
|
}
|
|
859
|
-
entry.
|
|
860
|
-
for (const thumbPath of
|
|
839
|
+
entry.c = 1;
|
|
840
|
+
for (const thumbPath of _chunkJWAAU3NNjs.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
861
841
|
const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
|
|
862
842
|
try {
|
|
863
843
|
await _fs.promises.unlink(localPath);
|
|
864
|
-
} catch (
|
|
844
|
+
} catch (e21) {
|
|
865
845
|
}
|
|
866
846
|
}
|
|
847
|
+
try {
|
|
848
|
+
await _fs.promises.unlink(originalLocalPath);
|
|
849
|
+
} catch (e22) {
|
|
850
|
+
}
|
|
867
851
|
synced.push(imageKey);
|
|
868
852
|
} catch (error) {
|
|
869
853
|
console.error(`Failed to sync ${imageKey}:`, error);
|
|
870
|
-
errors.push(imageKey);
|
|
854
|
+
errors.push(`Failed to sync: ${imageKey}`);
|
|
871
855
|
}
|
|
872
856
|
}
|
|
873
857
|
await saveMeta(meta);
|
|
@@ -894,21 +878,29 @@ async function handleReprocess(request) {
|
|
|
894
878
|
try {
|
|
895
879
|
let buffer;
|
|
896
880
|
const entry = meta[imageKey];
|
|
881
|
+
const isSynced = _optionalChain([entry, 'optionalAccess', _6 => _6.c]) === 1;
|
|
897
882
|
const originalPath = _path2.default.join(process.cwd(), "public", imageKey);
|
|
898
883
|
try {
|
|
899
884
|
buffer = await _fs.promises.readFile(originalPath);
|
|
900
|
-
} catch (
|
|
901
|
-
if (
|
|
885
|
+
} catch (e23) {
|
|
886
|
+
if (isSynced) {
|
|
902
887
|
buffer = await downloadFromCdn(imageKey);
|
|
888
|
+
const dir = _path2.default.dirname(originalPath);
|
|
889
|
+
await _fs.promises.mkdir(dir, { recursive: true });
|
|
890
|
+
await _fs.promises.writeFile(originalPath, buffer);
|
|
903
891
|
} else {
|
|
904
892
|
throw new Error(`File not found: ${imageKey}`);
|
|
905
893
|
}
|
|
906
894
|
}
|
|
907
895
|
const updatedEntry = await processImage(buffer, imageKey);
|
|
908
|
-
if (
|
|
909
|
-
updatedEntry.
|
|
896
|
+
if (isSynced) {
|
|
897
|
+
updatedEntry.c = 1;
|
|
910
898
|
await uploadToCdn(imageKey);
|
|
911
899
|
await deleteLocalThumbnails(imageKey);
|
|
900
|
+
try {
|
|
901
|
+
await _fs.promises.unlink(originalPath);
|
|
902
|
+
} catch (e24) {
|
|
903
|
+
}
|
|
912
904
|
}
|
|
913
905
|
meta[imageKey] = updatedEntry;
|
|
914
906
|
processed.push(imageKey);
|
|
@@ -942,73 +934,61 @@ async function handleProcessAllStream() {
|
|
|
942
934
|
const processed = [];
|
|
943
935
|
const errors = [];
|
|
944
936
|
const orphansRemoved = [];
|
|
945
|
-
const
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
953
|
-
if (relPath === "images" || relPath.startsWith("images/")) continue;
|
|
954
|
-
if (entry.isDirectory()) {
|
|
955
|
-
await scanPublicFolder(fullPath, relPath);
|
|
956
|
-
} else if (isImageFile(entry.name)) {
|
|
957
|
-
allImages.push({ key: relPath, fullPath });
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
} catch (e26) {
|
|
937
|
+
const imagesToProcess = [];
|
|
938
|
+
for (const [key, entry] of Object.entries(meta)) {
|
|
939
|
+
if (entry.c) continue;
|
|
940
|
+
const fileName = _path2.default.basename(key);
|
|
941
|
+
if (!isImageFile(fileName)) continue;
|
|
942
|
+
if (!entry.b) {
|
|
943
|
+
imagesToProcess.push({ key, entry });
|
|
961
944
|
}
|
|
962
945
|
}
|
|
963
|
-
const
|
|
964
|
-
await scanPublicFolder(publicDir);
|
|
965
|
-
const total = allImages.length;
|
|
946
|
+
const total = imagesToProcess.length;
|
|
966
947
|
sendEvent({ type: "start", total });
|
|
967
|
-
for (let i = 0; i <
|
|
968
|
-
const { key
|
|
969
|
-
const
|
|
948
|
+
for (let i = 0; i < imagesToProcess.length; i++) {
|
|
949
|
+
const { key } = imagesToProcess[i];
|
|
950
|
+
const fullPath = _path2.default.join(process.cwd(), "public", key);
|
|
970
951
|
sendEvent({
|
|
971
952
|
type: "progress",
|
|
972
953
|
current: i + 1,
|
|
973
954
|
total,
|
|
974
955
|
percent: Math.round((i + 1) / total * 100),
|
|
975
|
-
currentFile: key
|
|
956
|
+
currentFile: key.slice(1)
|
|
957
|
+
// Remove leading /
|
|
976
958
|
});
|
|
977
959
|
try {
|
|
978
960
|
const buffer = await _fs.promises.readFile(fullPath);
|
|
979
961
|
const ext = _path2.default.extname(key).toLowerCase();
|
|
980
962
|
const isSvg = ext === ".svg";
|
|
981
963
|
if (isSvg) {
|
|
982
|
-
const imageDir = _path2.default.dirname(key);
|
|
964
|
+
const imageDir = _path2.default.dirname(key.slice(1));
|
|
983
965
|
const imagesPath = _path2.default.join(process.cwd(), "public", "images", imageDir === "." ? "" : imageDir);
|
|
984
966
|
await _fs.promises.mkdir(imagesPath, { recursive: true });
|
|
985
967
|
const fileName = _path2.default.basename(key);
|
|
986
968
|
const destPath = _path2.default.join(imagesPath, fileName);
|
|
987
969
|
await _fs.promises.writeFile(destPath, buffer);
|
|
988
|
-
meta[
|
|
970
|
+
meta[key] = {
|
|
989
971
|
w: 0,
|
|
990
972
|
h: 0,
|
|
991
|
-
|
|
973
|
+
b: ""
|
|
992
974
|
};
|
|
993
975
|
} else {
|
|
994
|
-
const
|
|
995
|
-
|
|
996
|
-
if (_optionalChain([existingEntry, 'optionalAccess', _7 => _7.s])) {
|
|
997
|
-
processedEntry.s = 1;
|
|
998
|
-
}
|
|
999
|
-
meta[imageKey] = processedEntry;
|
|
976
|
+
const processedEntry = await processImage(buffer, key);
|
|
977
|
+
meta[key] = processedEntry;
|
|
1000
978
|
}
|
|
1001
|
-
processed.push(key);
|
|
979
|
+
processed.push(key.slice(1));
|
|
1002
980
|
} catch (error) {
|
|
1003
981
|
console.error(`Failed to process ${key}:`, error);
|
|
1004
|
-
errors.push(key);
|
|
982
|
+
errors.push(key.slice(1));
|
|
1005
983
|
}
|
|
1006
984
|
}
|
|
1007
985
|
sendEvent({ type: "cleanup", message: "Removing orphaned thumbnails..." });
|
|
1008
986
|
const trackedPaths = /* @__PURE__ */ new Set();
|
|
1009
987
|
for (const imageKey of Object.keys(meta)) {
|
|
1010
|
-
|
|
1011
|
-
|
|
988
|
+
if (!meta[imageKey].c) {
|
|
989
|
+
for (const thumbPath of _chunkJWAAU3NNjs.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
990
|
+
trackedPaths.add(thumbPath);
|
|
991
|
+
}
|
|
1012
992
|
}
|
|
1013
993
|
}
|
|
1014
994
|
async function findOrphans(dir, relativePath = "") {
|
|
@@ -1032,11 +1012,14 @@ async function handleProcessAllStream() {
|
|
|
1032
1012
|
}
|
|
1033
1013
|
}
|
|
1034
1014
|
}
|
|
1035
|
-
} catch (
|
|
1015
|
+
} catch (e25) {
|
|
1036
1016
|
}
|
|
1037
1017
|
}
|
|
1038
1018
|
const imagesDir = _path2.default.join(process.cwd(), "public", "images");
|
|
1039
|
-
|
|
1019
|
+
try {
|
|
1020
|
+
await findOrphans(imagesDir);
|
|
1021
|
+
} catch (e26) {
|
|
1022
|
+
}
|
|
1040
1023
|
async function removeEmptyDirs(dir) {
|
|
1041
1024
|
try {
|
|
1042
1025
|
const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
|
|
@@ -1053,11 +1036,14 @@ async function handleProcessAllStream() {
|
|
|
1053
1036
|
await _fs.promises.rmdir(dir);
|
|
1054
1037
|
}
|
|
1055
1038
|
return isEmpty;
|
|
1056
|
-
} catch (
|
|
1039
|
+
} catch (e27) {
|
|
1057
1040
|
return true;
|
|
1058
1041
|
}
|
|
1059
1042
|
}
|
|
1060
|
-
|
|
1043
|
+
try {
|
|
1044
|
+
await removeEmptyDirs(imagesDir);
|
|
1045
|
+
} catch (e28) {
|
|
1046
|
+
}
|
|
1061
1047
|
await saveMeta(meta);
|
|
1062
1048
|
sendEvent({
|
|
1063
1049
|
type: "complete",
|
|
@@ -1082,6 +1068,135 @@ async function handleProcessAllStream() {
|
|
|
1082
1068
|
});
|
|
1083
1069
|
}
|
|
1084
1070
|
|
|
1071
|
+
// src/handlers/scan.ts
|
|
1072
|
+
|
|
1073
|
+
|
|
1074
|
+
|
|
1075
|
+
async function handleScanStream() {
|
|
1076
|
+
const encoder = new TextEncoder();
|
|
1077
|
+
const stream = new ReadableStream({
|
|
1078
|
+
async start(controller) {
|
|
1079
|
+
const sendEvent = (data) => {
|
|
1080
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
|
|
1081
|
+
|
|
1082
|
+
`));
|
|
1083
|
+
};
|
|
1084
|
+
try {
|
|
1085
|
+
const meta = await loadMeta();
|
|
1086
|
+
const existingKeys = new Set(Object.keys(meta));
|
|
1087
|
+
const added = [];
|
|
1088
|
+
const renamed = [];
|
|
1089
|
+
const errors = [];
|
|
1090
|
+
const allFiles = [];
|
|
1091
|
+
async function scanDir(dir, relativePath = "") {
|
|
1092
|
+
try {
|
|
1093
|
+
const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
|
|
1094
|
+
for (const entry of entries) {
|
|
1095
|
+
if (entry.name.startsWith(".")) continue;
|
|
1096
|
+
const fullPath = _path2.default.join(dir, entry.name);
|
|
1097
|
+
const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
1098
|
+
if (relPath === "images" || relPath.startsWith("images/")) continue;
|
|
1099
|
+
if (entry.isDirectory()) {
|
|
1100
|
+
await scanDir(fullPath, relPath);
|
|
1101
|
+
} else if (isMediaFile(entry.name)) {
|
|
1102
|
+
allFiles.push({ relativePath: relPath, fullPath });
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
} catch (e29) {
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
const publicDir = _path2.default.join(process.cwd(), "public");
|
|
1109
|
+
await scanDir(publicDir);
|
|
1110
|
+
const total = allFiles.length;
|
|
1111
|
+
sendEvent({ type: "start", total });
|
|
1112
|
+
for (let i = 0; i < allFiles.length; i++) {
|
|
1113
|
+
let { relativePath, fullPath } = allFiles[i];
|
|
1114
|
+
let imageKey = "/" + relativePath;
|
|
1115
|
+
sendEvent({
|
|
1116
|
+
type: "progress",
|
|
1117
|
+
current: i + 1,
|
|
1118
|
+
total,
|
|
1119
|
+
percent: Math.round((i + 1) / total * 100),
|
|
1120
|
+
currentFile: relativePath
|
|
1121
|
+
});
|
|
1122
|
+
if (existingKeys.has(imageKey)) {
|
|
1123
|
+
continue;
|
|
1124
|
+
}
|
|
1125
|
+
if (meta[imageKey]) {
|
|
1126
|
+
const ext = _path2.default.extname(relativePath);
|
|
1127
|
+
const baseName = relativePath.slice(0, -ext.length);
|
|
1128
|
+
let counter = 1;
|
|
1129
|
+
let newKey = `/${baseName}-${counter}${ext}`;
|
|
1130
|
+
while (meta[newKey]) {
|
|
1131
|
+
counter++;
|
|
1132
|
+
newKey = `/${baseName}-${counter}${ext}`;
|
|
1133
|
+
}
|
|
1134
|
+
const newRelativePath = `${baseName}-${counter}${ext}`;
|
|
1135
|
+
const newFullPath = _path2.default.join(process.cwd(), "public", newRelativePath);
|
|
1136
|
+
try {
|
|
1137
|
+
await _fs.promises.rename(fullPath, newFullPath);
|
|
1138
|
+
renamed.push({ from: relativePath, to: newRelativePath });
|
|
1139
|
+
relativePath = newRelativePath;
|
|
1140
|
+
fullPath = newFullPath;
|
|
1141
|
+
imageKey = newKey;
|
|
1142
|
+
} catch (err) {
|
|
1143
|
+
console.error(`Failed to rename ${relativePath}:`, err);
|
|
1144
|
+
errors.push(`Failed to rename ${relativePath}`);
|
|
1145
|
+
continue;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
try {
|
|
1149
|
+
const isImage = isImageFile(relativePath);
|
|
1150
|
+
if (isImage) {
|
|
1151
|
+
const ext = _path2.default.extname(relativePath).toLowerCase();
|
|
1152
|
+
if (ext === ".svg") {
|
|
1153
|
+
meta[imageKey] = { w: 0, h: 0 };
|
|
1154
|
+
} else {
|
|
1155
|
+
try {
|
|
1156
|
+
const metadata = await _sharp2.default.call(void 0, fullPath).metadata();
|
|
1157
|
+
meta[imageKey] = {
|
|
1158
|
+
w: metadata.width || 0,
|
|
1159
|
+
h: metadata.height || 0
|
|
1160
|
+
};
|
|
1161
|
+
} catch (e30) {
|
|
1162
|
+
meta[imageKey] = { w: 0, h: 0 };
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
} else {
|
|
1166
|
+
meta[imageKey] = {};
|
|
1167
|
+
}
|
|
1168
|
+
existingKeys.add(imageKey);
|
|
1169
|
+
added.push(imageKey);
|
|
1170
|
+
} catch (error) {
|
|
1171
|
+
console.error(`Failed to process ${relativePath}:`, error);
|
|
1172
|
+
errors.push(relativePath);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
await saveMeta(meta);
|
|
1176
|
+
sendEvent({
|
|
1177
|
+
type: "complete",
|
|
1178
|
+
added: added.length,
|
|
1179
|
+
renamed: renamed.length,
|
|
1180
|
+
errors: errors.length,
|
|
1181
|
+
renamedFiles: renamed
|
|
1182
|
+
});
|
|
1183
|
+
} catch (error) {
|
|
1184
|
+
console.error("Scan failed:", error);
|
|
1185
|
+
sendEvent({ type: "error", message: "Scan failed" });
|
|
1186
|
+
} finally {
|
|
1187
|
+
controller.close();
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
});
|
|
1191
|
+
return new Response(stream, {
|
|
1192
|
+
headers: {
|
|
1193
|
+
"Content-Type": "text/event-stream",
|
|
1194
|
+
"Cache-Control": "no-cache",
|
|
1195
|
+
"Connection": "keep-alive"
|
|
1196
|
+
}
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1085
1200
|
// src/handlers/index.ts
|
|
1086
1201
|
async function GET(request) {
|
|
1087
1202
|
if (process.env.NODE_ENV !== "development") {
|
|
@@ -1136,6 +1251,9 @@ async function POST(request) {
|
|
|
1136
1251
|
if (route === "move") {
|
|
1137
1252
|
return handleMove(request);
|
|
1138
1253
|
}
|
|
1254
|
+
if (route === "scan") {
|
|
1255
|
+
return handleScanStream();
|
|
1256
|
+
}
|
|
1139
1257
|
return _server.NextResponse.json({ error: "Not found" }, { status: 404 });
|
|
1140
1258
|
}
|
|
1141
1259
|
async function DELETE(request) {
|