@open-slide/core 1.7.0 → 1.9.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/dist/{build-tLrkKUHr.js → build-ZM7IfDO-.js} +1 -1
- package/dist/cli/bin.js +3 -3
- package/dist/{config-PwUHqZ_X.js → config-BAZeaz2P.js} +289 -246
- package/dist/{config-CfMThYN9.d.ts → config-D_5nlXFU.d.ts} +6 -1
- package/dist/{dev-DpCIRbhT.js → dev-BQkNTG_t.js} +1 -1
- package/dist/format-CYOb2cAQ.js +1573 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +38 -4
- package/dist/locale/index.d.ts +1 -1
- package/dist/locale/index.js +1 -1144
- package/dist/{preview-BSGlM6Se.js → preview-D8hUtbRA.js} +1 -1
- package/dist/{types-B-KrjgX8.d.ts → types-AalTbxMj.d.ts} +17 -3
- package/dist/vite/index.d.ts +2 -2
- package/dist/vite/index.js +1 -1
- package/package.json +2 -1
- package/skills/create-theme/SKILL.md +1 -1
- package/src/app/components/inspector/comment-widget.tsx +16 -2
- package/src/app/components/language-toggle.tsx +39 -0
- package/src/app/components/player.tsx +12 -17
- package/src/app/components/pptx-progress-toast.tsx +32 -0
- package/src/app/components/sidebar/folder-item.tsx +7 -2
- package/src/app/components/sidebar/sidebar-footer.tsx +51 -0
- package/src/app/components/sidebar/sidebar.tsx +95 -17
- package/src/app/lib/design-presets.ts +1 -1
- package/src/app/lib/export-pptx.ts +284 -0
- package/src/app/lib/folders.ts +28 -0
- package/src/app/lib/inspector/fiber.test.ts +154 -0
- package/src/app/lib/inspector/fiber.ts +12 -1
- package/src/app/lib/locale-store.ts +67 -0
- package/src/app/lib/use-click-page-navigation.ts +52 -0
- package/src/app/lib/use-is-mobile.ts +21 -0
- package/src/app/lib/use-locale.ts +4 -16
- package/src/app/routes/home-shell.tsx +8 -0
- package/src/app/routes/home.tsx +1 -1
- package/src/app/routes/slide.tsx +145 -53
- package/src/app/virtual.d.ts +1 -0
- package/src/locale/en.ts +18 -3
- package/src/locale/ja.ts +19 -3
- package/src/locale/types.ts +18 -3
- package/src/locale/zh-cn.ts +17 -3
- package/src/locale/zh-tw.ts +17 -3
- package/dist/en-BDnM5zKJ.js +0 -378
- package/src/app/components/click-nav-zones.tsx +0 -36
|
@@ -3,7 +3,7 @@ import fs from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
6
|
-
import { existsSync } from "node:fs";
|
|
6
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
7
7
|
import tailwindcss from "@tailwindcss/vite";
|
|
8
8
|
import react from "@vitejs/plugin-react";
|
|
9
9
|
import * as t$4 from "@babel/types";
|
|
@@ -1157,21 +1157,20 @@ function findReferencedAssets(source, assetPaths) {
|
|
|
1157
1157
|
if (!ast) return referenced;
|
|
1158
1158
|
const wanted = new Set(assetPaths);
|
|
1159
1159
|
const identToPath = new Map();
|
|
1160
|
+
const importLocals = new Set();
|
|
1160
1161
|
for (const imp of findImports$1(ast)) {
|
|
1161
1162
|
if (!imp.defaultIdent) continue;
|
|
1162
|
-
if (wanted.has(imp.source))
|
|
1163
|
+
if (!wanted.has(imp.source)) continue;
|
|
1164
|
+
identToPath.set(imp.defaultIdent, imp.source);
|
|
1165
|
+
for (const spec of imp.node.specifiers) if (t$3.isImportDefaultSpecifier(spec) && spec.local.name === imp.defaultIdent) importLocals.add(spec.local);
|
|
1163
1166
|
}
|
|
1164
1167
|
if (identToPath.size === 0) return referenced;
|
|
1165
|
-
|
|
1166
|
-
if (!t$3.
|
|
1167
|
-
const
|
|
1168
|
-
if (!
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
const expr = src.value.expression;
|
|
1172
|
-
if (!t$3.isIdentifier(expr)) return;
|
|
1173
|
-
const p = identToPath.get(expr.name);
|
|
1174
|
-
if (p) referenced.add(p);
|
|
1168
|
+
walkAll(ast, (n) => {
|
|
1169
|
+
if (!t$3.isIdentifier(n)) return;
|
|
1170
|
+
const p = identToPath.get(n.name);
|
|
1171
|
+
if (!p) return;
|
|
1172
|
+
if (importLocals.has(n)) return;
|
|
1173
|
+
referenced.add(p);
|
|
1175
1174
|
});
|
|
1176
1175
|
return referenced;
|
|
1177
1176
|
}
|
|
@@ -1245,7 +1244,7 @@ function applyRevertAsset(source, assetPath) {
|
|
|
1245
1244
|
|
|
1246
1245
|
//#endregion
|
|
1247
1246
|
//#region src/editing/slide-ops.ts
|
|
1248
|
-
const SLIDE_ID_RE
|
|
1247
|
+
const SLIDE_ID_RE = /^[a-z0-9_-]+$/i;
|
|
1249
1248
|
function validateSlideName(v) {
|
|
1250
1249
|
if (typeof v !== "string") return null;
|
|
1251
1250
|
const trimmed = v.trim();
|
|
@@ -1309,7 +1308,7 @@ function readMetaTitleInSource(source) {
|
|
|
1309
1308
|
return { kind: "missing" };
|
|
1310
1309
|
}
|
|
1311
1310
|
async function rmSlideDir(slidesRoot, slideId) {
|
|
1312
|
-
if (!SLIDE_ID_RE
|
|
1311
|
+
if (!SLIDE_ID_RE.test(slideId)) return false;
|
|
1313
1312
|
const dir = path.resolve(slidesRoot, slideId);
|
|
1314
1313
|
if (!dir.startsWith(slidesRoot + path.sep)) return false;
|
|
1315
1314
|
try {
|
|
@@ -1323,7 +1322,7 @@ async function rmSlideDir(slidesRoot, slideId) {
|
|
|
1323
1322
|
}
|
|
1324
1323
|
}
|
|
1325
1324
|
async function duplicateSlideDir(slidesRoot, slideId, desiredId) {
|
|
1326
|
-
if (!SLIDE_ID_RE
|
|
1325
|
+
if (!SLIDE_ID_RE.test(slideId)) return {
|
|
1327
1326
|
ok: false,
|
|
1328
1327
|
status: 400,
|
|
1329
1328
|
error: "invalid slideId"
|
|
@@ -1346,7 +1345,7 @@ async function duplicateSlideDir(slidesRoot, slideId, desiredId) {
|
|
|
1346
1345
|
}
|
|
1347
1346
|
let newId;
|
|
1348
1347
|
if (desiredId !== void 0) {
|
|
1349
|
-
if (!SLIDE_ID_RE
|
|
1348
|
+
if (!SLIDE_ID_RE.test(desiredId)) return {
|
|
1350
1349
|
ok: false,
|
|
1351
1350
|
status: 400,
|
|
1352
1351
|
error: "invalid newId"
|
|
@@ -1434,7 +1433,7 @@ async function duplicateSlideDir(slidesRoot, slideId, desiredId) {
|
|
|
1434
1433
|
}
|
|
1435
1434
|
}
|
|
1436
1435
|
function resolveSlideEntry(slidesRoot, slideId) {
|
|
1437
|
-
if (!SLIDE_ID_RE
|
|
1436
|
+
if (!SLIDE_ID_RE.test(slideId)) return null;
|
|
1438
1437
|
const dir = path.resolve(slidesRoot, slideId);
|
|
1439
1438
|
if (!dir.startsWith(slidesRoot + path.sep)) return null;
|
|
1440
1439
|
return path.join(dir, "index.tsx");
|
|
@@ -1771,7 +1770,7 @@ function validateAssetName(v) {
|
|
|
1771
1770
|
return trimmed;
|
|
1772
1771
|
}
|
|
1773
1772
|
function resolveAssetsDir(slidesRoot, slideId) {
|
|
1774
|
-
if (!SLIDE_ID_RE
|
|
1773
|
+
if (!SLIDE_ID_RE.test(slideId)) return null;
|
|
1775
1774
|
const slideDir = path.resolve(slidesRoot, slideId);
|
|
1776
1775
|
if (!slideDir.startsWith(slidesRoot + path.sep)) return null;
|
|
1777
1776
|
const assetsDir = path.resolve(slideDir, "assets");
|
|
@@ -1884,10 +1883,11 @@ function makeContext(opts) {
|
|
|
1884
1883
|
slidesDir,
|
|
1885
1884
|
slidesRoot,
|
|
1886
1885
|
globalAssetsRoot,
|
|
1887
|
-
manifestPath
|
|
1886
|
+
manifestPath,
|
|
1887
|
+
coreVersion: opts.coreVersion
|
|
1888
1888
|
};
|
|
1889
1889
|
}
|
|
1890
|
-
async function readBody
|
|
1890
|
+
async function readBody(req) {
|
|
1891
1891
|
return await new Promise((resolve, reject) => {
|
|
1892
1892
|
const chunks = [];
|
|
1893
1893
|
req.on("data", (c) => chunks.push(c));
|
|
@@ -1903,17 +1903,21 @@ async function readBody$2(req) {
|
|
|
1903
1903
|
req.on("error", reject);
|
|
1904
1904
|
});
|
|
1905
1905
|
}
|
|
1906
|
-
function json
|
|
1906
|
+
function json(res, status, body) {
|
|
1907
1907
|
res.statusCode = status;
|
|
1908
1908
|
res.setHeader("content-type", "application/json");
|
|
1909
1909
|
res.end(JSON.stringify(body));
|
|
1910
1910
|
}
|
|
1911
|
-
function
|
|
1912
|
-
if (!SLIDE_ID_RE
|
|
1913
|
-
const
|
|
1914
|
-
|
|
1911
|
+
function resolveSlidePath(userCwd, slidesDir, slideId) {
|
|
1912
|
+
if (!SLIDE_ID_RE.test(slideId)) return null;
|
|
1913
|
+
const slidesRoot = path.resolve(userCwd, slidesDir);
|
|
1914
|
+
const full = path.resolve(slidesRoot, slideId, "index.tsx");
|
|
1915
|
+
if (!full.startsWith(slidesRoot + path.sep)) return null;
|
|
1915
1916
|
return full;
|
|
1916
1917
|
}
|
|
1918
|
+
function resolveSlideEntryPath(ctx, slideId) {
|
|
1919
|
+
return resolveSlidePath(ctx.userCwd, ctx.slidesDir, slideId);
|
|
1920
|
+
}
|
|
1917
1921
|
|
|
1918
1922
|
//#endregion
|
|
1919
1923
|
//#region src/vite/routes/assets.ts
|
|
@@ -1928,18 +1932,18 @@ function registerAssetRoutes(server, ctx) {
|
|
|
1928
1932
|
if (usagesMatch && method === "GET") {
|
|
1929
1933
|
const scope = usagesMatch[1];
|
|
1930
1934
|
const filename = decodeURIComponent(usagesMatch[2]);
|
|
1931
|
-
if (!validateAssetName(filename)) return json
|
|
1935
|
+
if (!validateAssetName(filename)) return json(res, 400, { error: "invalid path" });
|
|
1932
1936
|
const isGlobal = scope === GLOBAL_SCOPE;
|
|
1933
1937
|
const assetPath = isGlobal ? `@assets/${filename}` : `./assets/${filename}`;
|
|
1934
1938
|
let slideIds;
|
|
1935
1939
|
if (isGlobal) try {
|
|
1936
1940
|
const entries = await fs.readdir(ctx.slidesRoot, { withFileTypes: true });
|
|
1937
|
-
slideIds = entries.filter((e) => e.isDirectory() && SLIDE_ID_RE
|
|
1941
|
+
slideIds = entries.filter((e) => e.isDirectory() && SLIDE_ID_RE.test(e.name)).map((e) => e.name);
|
|
1938
1942
|
} catch {
|
|
1939
1943
|
slideIds = [];
|
|
1940
1944
|
}
|
|
1941
1945
|
else {
|
|
1942
|
-
if (!SLIDE_ID_RE
|
|
1946
|
+
if (!SLIDE_ID_RE.test(scope)) return json(res, 400, { error: "invalid slideId" });
|
|
1943
1947
|
slideIds = [scope];
|
|
1944
1948
|
}
|
|
1945
1949
|
const usages = [];
|
|
@@ -1962,7 +1966,7 @@ function registerAssetRoutes(server, ctx) {
|
|
|
1962
1966
|
totalCount += count;
|
|
1963
1967
|
}
|
|
1964
1968
|
}
|
|
1965
|
-
return json
|
|
1969
|
+
return json(res, 200, {
|
|
1966
1970
|
usages,
|
|
1967
1971
|
totalCount
|
|
1968
1972
|
});
|
|
@@ -1970,12 +1974,12 @@ function registerAssetRoutes(server, ctx) {
|
|
|
1970
1974
|
if (listMatch && method === "GET") {
|
|
1971
1975
|
const slideId = listMatch[1];
|
|
1972
1976
|
const scopedDir = resolveScopedAssetsDir(ctx.slidesRoot, ctx.globalAssetsRoot, slideId);
|
|
1973
|
-
if (!scopedDir) return json
|
|
1977
|
+
if (!scopedDir) return json(res, 400, { error: "invalid slideId" });
|
|
1974
1978
|
let entries;
|
|
1975
1979
|
try {
|
|
1976
1980
|
entries = await fs.readdir(scopedDir);
|
|
1977
1981
|
} catch (err) {
|
|
1978
|
-
if (err.code === "ENOENT") return json
|
|
1982
|
+
if (err.code === "ENOENT") return json(res, 200, { assets: [] });
|
|
1979
1983
|
throw err;
|
|
1980
1984
|
}
|
|
1981
1985
|
const assets = [];
|
|
@@ -1998,11 +2002,11 @@ function registerAssetRoutes(server, ctx) {
|
|
|
1998
2002
|
let scanIds;
|
|
1999
2003
|
if (isGlobal) try {
|
|
2000
2004
|
const dirs = await fs.readdir(ctx.slidesRoot, { withFileTypes: true });
|
|
2001
|
-
scanIds = dirs.filter((e) => e.isDirectory() && SLIDE_ID_RE
|
|
2005
|
+
scanIds = dirs.filter((e) => e.isDirectory() && SLIDE_ID_RE.test(e.name)).map((e) => e.name);
|
|
2002
2006
|
} catch {
|
|
2003
2007
|
scanIds = [];
|
|
2004
2008
|
}
|
|
2005
|
-
else scanIds = SLIDE_ID_RE
|
|
2009
|
+
else scanIds = SLIDE_ID_RE.test(slideId) ? [slideId] : [];
|
|
2006
2010
|
const paths = assets.map((a) => isGlobal ? `@assets/${a.name}` : `./assets/${a.name}`);
|
|
2007
2011
|
const pathToAsset = new Map(paths.map((p, i) => [p, assets[i]]));
|
|
2008
2012
|
for (const sid of scanIds) {
|
|
@@ -2020,13 +2024,13 @@ function registerAssetRoutes(server, ctx) {
|
|
|
2020
2024
|
}
|
|
2021
2025
|
}
|
|
2022
2026
|
}
|
|
2023
|
-
return json
|
|
2027
|
+
return json(res, 200, { assets });
|
|
2024
2028
|
}
|
|
2025
2029
|
if (fileMatch) {
|
|
2026
2030
|
const slideId = fileMatch[1];
|
|
2027
2031
|
const filename = decodeURIComponent(fileMatch[2]);
|
|
2028
2032
|
const file = resolveScopedAssetFile(ctx.slidesRoot, ctx.globalAssetsRoot, slideId, filename);
|
|
2029
|
-
if (!file) return json
|
|
2033
|
+
if (!file) return json(res, 400, { error: "invalid path" });
|
|
2030
2034
|
if (method === "GET") try {
|
|
2031
2035
|
const buf = await fs.readFile(file);
|
|
2032
2036
|
res.statusCode = 200;
|
|
@@ -2035,22 +2039,22 @@ function registerAssetRoutes(server, ctx) {
|
|
|
2035
2039
|
res.end(buf);
|
|
2036
2040
|
return;
|
|
2037
2041
|
} catch (err) {
|
|
2038
|
-
if (err.code === "ENOENT") return json
|
|
2042
|
+
if (err.code === "ENOENT") return json(res, 404, { error: "asset not found" });
|
|
2039
2043
|
throw err;
|
|
2040
2044
|
}
|
|
2041
2045
|
if (method === "POST") {
|
|
2042
2046
|
const requestCheck = validateMutationRequest(req);
|
|
2043
|
-
if (!requestCheck.ok) return json
|
|
2047
|
+
if (!requestCheck.ok) return json(res, requestCheck.status, { error: requestCheck.error });
|
|
2044
2048
|
const overwrite = url.searchParams.get("overwrite") === "1";
|
|
2045
2049
|
const lenHeader = req.headers["content-length"];
|
|
2046
2050
|
const len = typeof lenHeader === "string" ? Number(lenHeader) : NaN;
|
|
2047
|
-
if (Number.isFinite(len) && len > ASSET_MAX_BYTES) return json
|
|
2051
|
+
if (Number.isFinite(len) && len > ASSET_MAX_BYTES) return json(res, 413, { error: "file too large" });
|
|
2048
2052
|
if (!overwrite) try {
|
|
2049
2053
|
await fs.access(file);
|
|
2050
|
-
return json
|
|
2054
|
+
return json(res, 409, { error: "asset exists" });
|
|
2051
2055
|
} catch {}
|
|
2052
2056
|
const scopedDir = resolveScopedAssetsDir(ctx.slidesRoot, ctx.globalAssetsRoot, slideId);
|
|
2053
|
-
if (!scopedDir) return json
|
|
2057
|
+
if (!scopedDir) return json(res, 400, { error: "invalid slideId" });
|
|
2054
2058
|
await fs.mkdir(scopedDir, { recursive: true });
|
|
2055
2059
|
const chunks = [];
|
|
2056
2060
|
let total = 0;
|
|
@@ -2068,9 +2072,9 @@ function registerAssetRoutes(server, ctx) {
|
|
|
2068
2072
|
req.on("end", () => resolve());
|
|
2069
2073
|
req.on("error", reject);
|
|
2070
2074
|
});
|
|
2071
|
-
if (oversized) return json
|
|
2075
|
+
if (oversized) return json(res, 413, { error: "file too large" });
|
|
2072
2076
|
await fs.writeFile(file, Buffer.concat(chunks));
|
|
2073
|
-
return json
|
|
2077
|
+
return json(res, 200, {
|
|
2074
2078
|
ok: true,
|
|
2075
2079
|
name: filename,
|
|
2076
2080
|
size: total,
|
|
@@ -2080,46 +2084,46 @@ function registerAssetRoutes(server, ctx) {
|
|
|
2080
2084
|
}
|
|
2081
2085
|
if (method === "PATCH") {
|
|
2082
2086
|
const requestCheck = validateMutationRequest(req, { requireJsonBody: true });
|
|
2083
|
-
if (!requestCheck.ok) return json
|
|
2084
|
-
const body = await readBody
|
|
2087
|
+
if (!requestCheck.ok) return json(res, requestCheck.status, { error: requestCheck.error });
|
|
2088
|
+
const body = await readBody(req);
|
|
2085
2089
|
const target = validateAssetName(body.name);
|
|
2086
|
-
if (!target) return json
|
|
2087
|
-
if (target === filename) return json
|
|
2090
|
+
if (!target) return json(res, 400, { error: "invalid name" });
|
|
2091
|
+
if (target === filename) return json(res, 200, {
|
|
2088
2092
|
ok: true,
|
|
2089
2093
|
name: filename
|
|
2090
2094
|
});
|
|
2091
2095
|
const dest = resolveScopedAssetFile(ctx.slidesRoot, ctx.globalAssetsRoot, slideId, target);
|
|
2092
|
-
if (!dest) return json
|
|
2096
|
+
if (!dest) return json(res, 400, { error: "invalid name" });
|
|
2093
2097
|
try {
|
|
2094
2098
|
await fs.access(dest);
|
|
2095
|
-
return json
|
|
2099
|
+
return json(res, 409, { error: "target exists" });
|
|
2096
2100
|
} catch {}
|
|
2097
2101
|
try {
|
|
2098
2102
|
await fs.rename(file, dest);
|
|
2099
2103
|
} catch (err) {
|
|
2100
|
-
if (err.code === "ENOENT") return json
|
|
2104
|
+
if (err.code === "ENOENT") return json(res, 404, { error: "asset not found" });
|
|
2101
2105
|
throw err;
|
|
2102
2106
|
}
|
|
2103
|
-
return json
|
|
2107
|
+
return json(res, 200, {
|
|
2104
2108
|
ok: true,
|
|
2105
2109
|
name: target
|
|
2106
2110
|
});
|
|
2107
2111
|
}
|
|
2108
2112
|
if (method === "DELETE") {
|
|
2109
2113
|
const requestCheck = validateMutationRequest(req);
|
|
2110
|
-
if (!requestCheck.ok) return json
|
|
2114
|
+
if (!requestCheck.ok) return json(res, requestCheck.status, { error: requestCheck.error });
|
|
2111
2115
|
try {
|
|
2112
2116
|
await fs.unlink(file);
|
|
2113
2117
|
} catch (err) {
|
|
2114
|
-
if (err.code === "ENOENT") return json
|
|
2118
|
+
if (err.code === "ENOENT") return json(res, 404, { error: "asset not found" });
|
|
2115
2119
|
throw err;
|
|
2116
2120
|
}
|
|
2117
|
-
return json
|
|
2121
|
+
return json(res, 200, { ok: true });
|
|
2118
2122
|
}
|
|
2119
2123
|
}
|
|
2120
2124
|
return next();
|
|
2121
2125
|
} catch (err) {
|
|
2122
|
-
json
|
|
2126
|
+
json(res, 500, { error: String(err.message ?? err) });
|
|
2123
2127
|
}
|
|
2124
2128
|
});
|
|
2125
2129
|
}
|
|
@@ -2239,32 +2243,32 @@ function registerCommentRoutes(server, ctx) {
|
|
|
2239
2243
|
if (method === "GET" && url.pathname === "/") {
|
|
2240
2244
|
const slideId = url.searchParams.get("slideId") ?? "";
|
|
2241
2245
|
const file = resolveSlideEntryPath(ctx, slideId);
|
|
2242
|
-
if (!file) return json
|
|
2246
|
+
if (!file) return json(res, 400, { error: "invalid slideId" });
|
|
2243
2247
|
let source;
|
|
2244
2248
|
try {
|
|
2245
2249
|
source = await fs.readFile(file, "utf8");
|
|
2246
2250
|
} catch {
|
|
2247
|
-
return json
|
|
2251
|
+
return json(res, 404, { error: "slide not found" });
|
|
2248
2252
|
}
|
|
2249
|
-
return json
|
|
2253
|
+
return json(res, 200, { comments: parseMarkers(source) });
|
|
2250
2254
|
}
|
|
2251
2255
|
if (method === "POST" && url.pathname === "/add") {
|
|
2252
2256
|
const requestCheck = validateMutationRequest(req, { requireJsonBody: true });
|
|
2253
|
-
if (!requestCheck.ok) return json
|
|
2254
|
-
const body = await readBody
|
|
2257
|
+
if (!requestCheck.ok) return json(res, requestCheck.status, { error: requestCheck.error });
|
|
2258
|
+
const body = await readBody(req);
|
|
2255
2259
|
const slideId = body.slideId ?? "";
|
|
2256
2260
|
const file = resolveSlideEntryPath(ctx, slideId);
|
|
2257
|
-
if (!file) return json
|
|
2258
|
-
if (!body.line || body.line < 1) return json
|
|
2259
|
-
if (!body.text || typeof body.text !== "string") return json
|
|
2261
|
+
if (!file) return json(res, 400, { error: "invalid slideId" });
|
|
2262
|
+
if (!body.line || body.line < 1) return json(res, 400, { error: "invalid line" });
|
|
2263
|
+
if (!body.text || typeof body.text !== "string") return json(res, 400, { error: "missing text" });
|
|
2260
2264
|
let source;
|
|
2261
2265
|
try {
|
|
2262
2266
|
source = await fs.readFile(file, "utf8");
|
|
2263
2267
|
} catch {
|
|
2264
|
-
return json
|
|
2268
|
+
return json(res, 404, { error: "slide not found" });
|
|
2265
2269
|
}
|
|
2266
2270
|
const plan = findInsertion(source, body.line, body.column);
|
|
2267
|
-
if (!plan) return json
|
|
2271
|
+
if (!plan) return json(res, 422, { error: `could not find a JSX container around line ${body.line}. Try clicking a different element.` });
|
|
2268
2272
|
const id = newCommentId();
|
|
2269
2273
|
const ts = new Date().toISOString();
|
|
2270
2274
|
const payload = b64urlEncode(JSON.stringify({
|
|
@@ -2275,36 +2279,36 @@ function registerCommentRoutes(server, ctx) {
|
|
|
2275
2279
|
const next$1 = source.slice(0, plan.offset) + marker + source.slice(plan.offset);
|
|
2276
2280
|
await fs.writeFile(file, next$1, "utf8");
|
|
2277
2281
|
const markerLine = offsetToLine(next$1, plan.offset + 1);
|
|
2278
|
-
return json
|
|
2282
|
+
return json(res, 200, {
|
|
2279
2283
|
id,
|
|
2280
2284
|
line: markerLine
|
|
2281
2285
|
});
|
|
2282
2286
|
}
|
|
2283
2287
|
if (method === "DELETE" && url.pathname.startsWith("/")) {
|
|
2284
2288
|
const requestCheck = validateMutationRequest(req);
|
|
2285
|
-
if (!requestCheck.ok) return json
|
|
2289
|
+
if (!requestCheck.ok) return json(res, requestCheck.status, { error: requestCheck.error });
|
|
2286
2290
|
const id = url.pathname.slice(1);
|
|
2287
|
-
if (!/^c-[a-f0-9]+$/.test(id)) return json
|
|
2291
|
+
if (!/^c-[a-f0-9]+$/.test(id)) return json(res, 400, { error: "invalid id" });
|
|
2288
2292
|
const slideId = url.searchParams.get("slideId") ?? "";
|
|
2289
2293
|
const file = resolveSlideEntryPath(ctx, slideId);
|
|
2290
|
-
if (!file) return json
|
|
2294
|
+
if (!file) return json(res, 400, { error: "invalid slideId" });
|
|
2291
2295
|
let source;
|
|
2292
2296
|
try {
|
|
2293
2297
|
source = await fs.readFile(file, "utf8");
|
|
2294
2298
|
} catch {
|
|
2295
|
-
return json
|
|
2299
|
+
return json(res, 404, { error: "slide not found" });
|
|
2296
2300
|
}
|
|
2297
2301
|
const lines = source.split("\n");
|
|
2298
2302
|
const idRe = markerDeleteRegex(id);
|
|
2299
2303
|
const hit = lines.findIndex((l) => idRe.test(l));
|
|
2300
|
-
if (hit === -1) return json
|
|
2304
|
+
if (hit === -1) return json(res, 404, { error: "marker not found" });
|
|
2301
2305
|
lines.splice(hit, 1);
|
|
2302
2306
|
await fs.writeFile(file, lines.join("\n"), "utf8");
|
|
2303
|
-
return json
|
|
2307
|
+
return json(res, 200, { ok: true });
|
|
2304
2308
|
}
|
|
2305
2309
|
next();
|
|
2306
2310
|
} catch (err) {
|
|
2307
|
-
json
|
|
2311
|
+
json(res, 500, { error: String(err.message ?? err) });
|
|
2308
2312
|
}
|
|
2309
2313
|
});
|
|
2310
2314
|
}
|
|
@@ -2317,64 +2321,64 @@ function registerEditRoutes(server, ctx) {
|
|
|
2317
2321
|
const method = req.method ?? "GET";
|
|
2318
2322
|
if (method !== "POST") return next();
|
|
2319
2323
|
const requestCheck = validateMutationRequest(req, { requireJsonBody: true });
|
|
2320
|
-
if (!requestCheck.ok) return json
|
|
2324
|
+
if (!requestCheck.ok) return json(res, requestCheck.status, { error: requestCheck.error });
|
|
2321
2325
|
try {
|
|
2322
2326
|
if (url.pathname === "/") {
|
|
2323
|
-
const body = await readBody
|
|
2327
|
+
const body = await readBody(req);
|
|
2324
2328
|
const slideId = body.slideId ?? "";
|
|
2325
2329
|
const file = resolveSlideEntryPath(ctx, slideId);
|
|
2326
|
-
if (!file) return json
|
|
2327
|
-
if (!body.line || body.line < 1) return json
|
|
2328
|
-
if (!Array.isArray(body.ops)) return json
|
|
2330
|
+
if (!file) return json(res, 400, { error: "invalid slideId" });
|
|
2331
|
+
if (!body.line || body.line < 1) return json(res, 400, { error: "invalid line" });
|
|
2332
|
+
if (!Array.isArray(body.ops)) return json(res, 400, { error: "missing ops" });
|
|
2329
2333
|
let source;
|
|
2330
2334
|
try {
|
|
2331
2335
|
source = await fs.readFile(file, "utf8");
|
|
2332
2336
|
} catch {
|
|
2333
|
-
return json
|
|
2337
|
+
return json(res, 404, { error: "slide not found" });
|
|
2334
2338
|
}
|
|
2335
2339
|
const result = applyEdit(source, body.line, body.column ?? 0, body.ops);
|
|
2336
|
-
if (!result.ok) return json
|
|
2340
|
+
if (!result.ok) return json(res, result.status, { error: result.error });
|
|
2337
2341
|
const changed = result.source !== source;
|
|
2338
2342
|
if (changed) await fs.writeFile(file, result.source, "utf8");
|
|
2339
|
-
return json
|
|
2343
|
+
return json(res, 200, {
|
|
2340
2344
|
ok: true,
|
|
2341
2345
|
changed
|
|
2342
2346
|
});
|
|
2343
2347
|
}
|
|
2344
2348
|
if (url.pathname === "/revert-asset") {
|
|
2345
|
-
const body = await readBody
|
|
2349
|
+
const body = await readBody(req);
|
|
2346
2350
|
const slideId = body.slideId ?? "";
|
|
2347
2351
|
const assetPath = body.assetPath;
|
|
2348
2352
|
const file = resolveSlideEntryPath(ctx, slideId);
|
|
2349
|
-
if (!file) return json
|
|
2350
|
-
if (typeof assetPath !== "string" || !assetPath) return json
|
|
2351
|
-
if (!assetPath.startsWith("./assets/") && !assetPath.startsWith("@assets/")) return json
|
|
2353
|
+
if (!file) return json(res, 400, { error: "invalid slideId" });
|
|
2354
|
+
if (typeof assetPath !== "string" || !assetPath) return json(res, 400, { error: "missing assetPath" });
|
|
2355
|
+
if (!assetPath.startsWith("./assets/") && !assetPath.startsWith("@assets/")) return json(res, 400, { error: "asset path must start with ./assets/ or @assets/" });
|
|
2352
2356
|
let source;
|
|
2353
2357
|
try {
|
|
2354
2358
|
source = await fs.readFile(file, "utf8");
|
|
2355
2359
|
} catch {
|
|
2356
|
-
return json
|
|
2360
|
+
return json(res, 404, { error: "slide not found" });
|
|
2357
2361
|
}
|
|
2358
2362
|
const result = applyRevertAsset(source, assetPath);
|
|
2359
|
-
if (!result.ok) return json
|
|
2363
|
+
if (!result.ok) return json(res, result.status, { error: result.error });
|
|
2360
2364
|
const changed = result.source !== source;
|
|
2361
2365
|
if (changed) await fs.writeFile(file, result.source, "utf8");
|
|
2362
|
-
return json
|
|
2366
|
+
return json(res, 200, {
|
|
2363
2367
|
ok: true,
|
|
2364
2368
|
changed
|
|
2365
2369
|
});
|
|
2366
2370
|
}
|
|
2367
2371
|
if (url.pathname === "/batch") {
|
|
2368
|
-
const body = await readBody
|
|
2372
|
+
const body = await readBody(req);
|
|
2369
2373
|
const slideId = body.slideId ?? "";
|
|
2370
2374
|
const file = resolveSlideEntryPath(ctx, slideId);
|
|
2371
|
-
if (!file) return json
|
|
2372
|
-
if (!Array.isArray(body.edits)) return json
|
|
2375
|
+
if (!file) return json(res, 400, { error: "invalid slideId" });
|
|
2376
|
+
if (!Array.isArray(body.edits)) return json(res, 400, { error: "missing edits" });
|
|
2373
2377
|
let source;
|
|
2374
2378
|
try {
|
|
2375
2379
|
source = await fs.readFile(file, "utf8");
|
|
2376
2380
|
} catch {
|
|
2377
|
-
return json
|
|
2381
|
+
return json(res, 404, { error: "slide not found" });
|
|
2378
2382
|
}
|
|
2379
2383
|
const original = source;
|
|
2380
2384
|
const results = [];
|
|
@@ -2397,7 +2401,7 @@ function registerEditRoutes(server, ctx) {
|
|
|
2397
2401
|
}
|
|
2398
2402
|
const changed = source !== original;
|
|
2399
2403
|
if (changed) await fs.writeFile(file, source, "utf8");
|
|
2400
|
-
return json
|
|
2404
|
+
return json(res, 200, {
|
|
2401
2405
|
ok: true,
|
|
2402
2406
|
changed,
|
|
2403
2407
|
results
|
|
@@ -2405,7 +2409,7 @@ function registerEditRoutes(server, ctx) {
|
|
|
2405
2409
|
}
|
|
2406
2410
|
return next();
|
|
2407
2411
|
} catch (err) {
|
|
2408
|
-
json
|
|
2412
|
+
json(res, 500, { error: String(err.message ?? err) });
|
|
2409
2413
|
}
|
|
2410
2414
|
});
|
|
2411
2415
|
}
|
|
@@ -2446,6 +2450,19 @@ function validateName(v) {
|
|
|
2446
2450
|
if (trimmed.length < 1 || trimmed.length > 40) return null;
|
|
2447
2451
|
return trimmed;
|
|
2448
2452
|
}
|
|
2453
|
+
function validateReorder(v, current) {
|
|
2454
|
+
if (!Array.isArray(v) || v.length !== current.length) return null;
|
|
2455
|
+
const known = new Set(current.map((f) => f.id));
|
|
2456
|
+
const seen = new Set();
|
|
2457
|
+
const out = [];
|
|
2458
|
+
for (const id of v) {
|
|
2459
|
+
if (typeof id !== "string" || !FOLDER_ID_RE.test(id)) return null;
|
|
2460
|
+
if (!known.has(id) || seen.has(id)) return null;
|
|
2461
|
+
seen.add(id);
|
|
2462
|
+
out.push(id);
|
|
2463
|
+
}
|
|
2464
|
+
return out;
|
|
2465
|
+
}
|
|
2449
2466
|
function validateIcon(v) {
|
|
2450
2467
|
if (!v || typeof v !== "object") return null;
|
|
2451
2468
|
const icon = v;
|
|
@@ -2476,16 +2493,16 @@ function registerFolderRoutes(server, ctx) {
|
|
|
2476
2493
|
try {
|
|
2477
2494
|
if (method === "GET" && url.pathname === "/") {
|
|
2478
2495
|
const manifest = await readManifest(ctx.manifestPath);
|
|
2479
|
-
return json
|
|
2496
|
+
return json(res, 200, manifest);
|
|
2480
2497
|
}
|
|
2481
2498
|
if (method === "POST" && url.pathname === "/") {
|
|
2482
2499
|
const requestCheck = validateMutationRequest(req, { requireJsonBody: true });
|
|
2483
|
-
if (!requestCheck.ok) return json
|
|
2484
|
-
const body = await readBody
|
|
2500
|
+
if (!requestCheck.ok) return json(res, requestCheck.status, { error: requestCheck.error });
|
|
2501
|
+
const body = await readBody(req);
|
|
2485
2502
|
const name = validateName(body.name);
|
|
2486
|
-
if (!name) return json
|
|
2503
|
+
if (!name) return json(res, 400, { error: "invalid name" });
|
|
2487
2504
|
const icon = validateIcon(body.icon);
|
|
2488
|
-
if (!icon) return json
|
|
2505
|
+
if (!icon) return json(res, 400, { error: "invalid icon" });
|
|
2489
2506
|
const manifest = await readManifest(ctx.manifestPath);
|
|
2490
2507
|
const folder = {
|
|
2491
2508
|
id: newFolderId(),
|
|
@@ -2494,64 +2511,76 @@ function registerFolderRoutes(server, ctx) {
|
|
|
2494
2511
|
};
|
|
2495
2512
|
manifest.folders.push(folder);
|
|
2496
2513
|
await writeManifest(ctx.manifestPath, manifest);
|
|
2497
|
-
return json
|
|
2514
|
+
return json(res, 200, folder);
|
|
2498
2515
|
}
|
|
2499
2516
|
if (method === "PUT" && url.pathname === "/assign") {
|
|
2500
2517
|
const requestCheck = validateMutationRequest(req, { requireJsonBody: true });
|
|
2501
|
-
if (!requestCheck.ok) return json
|
|
2502
|
-
const body = await readBody
|
|
2503
|
-
if (typeof body.slideId !== "string" || !SLIDE_ID_RE
|
|
2518
|
+
if (!requestCheck.ok) return json(res, requestCheck.status, { error: requestCheck.error });
|
|
2519
|
+
const body = await readBody(req);
|
|
2520
|
+
if (typeof body.slideId !== "string" || !SLIDE_ID_RE.test(body.slideId)) return json(res, 400, { error: "invalid slideId" });
|
|
2504
2521
|
const slideId = body.slideId;
|
|
2505
2522
|
let folderId;
|
|
2506
2523
|
if (body.folderId === null) folderId = null;
|
|
2507
2524
|
else if (typeof body.folderId === "string" && FOLDER_ID_RE.test(body.folderId)) folderId = body.folderId;
|
|
2508
|
-
else return json
|
|
2525
|
+
else return json(res, 400, { error: "invalid folderId" });
|
|
2509
2526
|
const manifest = await readManifest(ctx.manifestPath);
|
|
2510
|
-
if (folderId && !manifest.folders.some((f) => f.id === folderId)) return json
|
|
2527
|
+
if (folderId && !manifest.folders.some((f) => f.id === folderId)) return json(res, 404, { error: "folder not found" });
|
|
2511
2528
|
if (folderId === null) delete manifest.assignments[slideId];
|
|
2512
2529
|
else manifest.assignments[slideId] = folderId;
|
|
2513
2530
|
await writeManifest(ctx.manifestPath, manifest);
|
|
2514
|
-
return json
|
|
2531
|
+
return json(res, 200, { ok: true });
|
|
2532
|
+
}
|
|
2533
|
+
if (method === "PUT" && url.pathname === "/reorder") {
|
|
2534
|
+
const requestCheck = validateMutationRequest(req, { requireJsonBody: true });
|
|
2535
|
+
if (!requestCheck.ok) return json(res, requestCheck.status, { error: requestCheck.error });
|
|
2536
|
+
const body = await readBody(req);
|
|
2537
|
+
const manifest = await readManifest(ctx.manifestPath);
|
|
2538
|
+
const ids = validateReorder(body.ids, manifest.folders);
|
|
2539
|
+
if (!ids) return json(res, 400, { error: "invalid ids" });
|
|
2540
|
+
const byId = new Map(manifest.folders.map((f) => [f.id, f]));
|
|
2541
|
+
manifest.folders = ids.map((id) => byId.get(id));
|
|
2542
|
+
await writeManifest(ctx.manifestPath, manifest);
|
|
2543
|
+
return json(res, 200, { ok: true });
|
|
2515
2544
|
}
|
|
2516
2545
|
const idMatch = url.pathname.match(/^\/([^/]+)$/);
|
|
2517
2546
|
if (idMatch) {
|
|
2518
2547
|
const id = idMatch[1];
|
|
2519
|
-
if (!FOLDER_ID_RE.test(id)) return json
|
|
2548
|
+
if (!FOLDER_ID_RE.test(id)) return json(res, 400, { error: "invalid id" });
|
|
2520
2549
|
if (method === "PATCH") {
|
|
2521
2550
|
const requestCheck = validateMutationRequest(req, { requireJsonBody: true });
|
|
2522
|
-
if (!requestCheck.ok) return json
|
|
2523
|
-
const body = await readBody
|
|
2551
|
+
if (!requestCheck.ok) return json(res, requestCheck.status, { error: requestCheck.error });
|
|
2552
|
+
const body = await readBody(req);
|
|
2524
2553
|
const manifest = await readManifest(ctx.manifestPath);
|
|
2525
2554
|
const folder = manifest.folders.find((f) => f.id === id);
|
|
2526
|
-
if (!folder) return json
|
|
2555
|
+
if (!folder) return json(res, 404, { error: "folder not found" });
|
|
2527
2556
|
if (body.name !== void 0) {
|
|
2528
2557
|
const name = validateName(body.name);
|
|
2529
|
-
if (!name) return json
|
|
2558
|
+
if (!name) return json(res, 400, { error: "invalid name" });
|
|
2530
2559
|
folder.name = name;
|
|
2531
2560
|
}
|
|
2532
2561
|
if (body.icon !== void 0) {
|
|
2533
2562
|
const icon = validateIcon(body.icon);
|
|
2534
|
-
if (!icon) return json
|
|
2563
|
+
if (!icon) return json(res, 400, { error: "invalid icon" });
|
|
2535
2564
|
folder.icon = icon;
|
|
2536
2565
|
}
|
|
2537
2566
|
await writeManifest(ctx.manifestPath, manifest);
|
|
2538
|
-
return json
|
|
2567
|
+
return json(res, 200, folder);
|
|
2539
2568
|
}
|
|
2540
2569
|
if (method === "DELETE") {
|
|
2541
2570
|
const requestCheck = validateMutationRequest(req);
|
|
2542
|
-
if (!requestCheck.ok) return json
|
|
2571
|
+
if (!requestCheck.ok) return json(res, requestCheck.status, { error: requestCheck.error });
|
|
2543
2572
|
const manifest = await readManifest(ctx.manifestPath);
|
|
2544
2573
|
const before = manifest.folders.length;
|
|
2545
2574
|
manifest.folders = manifest.folders.filter((f) => f.id !== id);
|
|
2546
|
-
if (manifest.folders.length === before) return json
|
|
2575
|
+
if (manifest.folders.length === before) return json(res, 404, { error: "folder not found" });
|
|
2547
2576
|
for (const [slideId, folderId] of Object.entries(manifest.assignments)) if (folderId === id) delete manifest.assignments[slideId];
|
|
2548
2577
|
await writeManifest(ctx.manifestPath, manifest);
|
|
2549
|
-
return json
|
|
2578
|
+
return json(res, 200, { ok: true });
|
|
2550
2579
|
}
|
|
2551
2580
|
}
|
|
2552
2581
|
next();
|
|
2553
2582
|
} catch (err) {
|
|
2554
|
-
json
|
|
2583
|
+
json(res, 500, { error: String(err.message ?? err) });
|
|
2555
2584
|
}
|
|
2556
2585
|
});
|
|
2557
2586
|
}
|
|
@@ -2566,30 +2595,30 @@ function registerSlideRoutes(server, ctx) {
|
|
|
2566
2595
|
const reorderMatch = url.pathname.match(/^\/([^/]+)\/reorder$/);
|
|
2567
2596
|
if (reorderMatch && method === "PUT") {
|
|
2568
2597
|
const requestCheck = validateMutationRequest(req, { requireJsonBody: true });
|
|
2569
|
-
if (!requestCheck.ok) return json
|
|
2598
|
+
if (!requestCheck.ok) return json(res, requestCheck.status, { error: requestCheck.error });
|
|
2570
2599
|
const slideId$1 = reorderMatch[1];
|
|
2571
|
-
if (!SLIDE_ID_RE
|
|
2572
|
-
const body = await readBody
|
|
2573
|
-
if (!Array.isArray(body.order)) return json
|
|
2600
|
+
if (!SLIDE_ID_RE.test(slideId$1)) return json(res, 400, { error: "invalid slideId" });
|
|
2601
|
+
const body = await readBody(req);
|
|
2602
|
+
if (!Array.isArray(body.order)) return json(res, 400, { error: "invalid order" });
|
|
2574
2603
|
const order = [];
|
|
2575
2604
|
for (const v of body.order) {
|
|
2576
|
-
if (!Number.isInteger(v)) return json
|
|
2605
|
+
if (!Number.isInteger(v)) return json(res, 400, { error: "invalid order" });
|
|
2577
2606
|
order.push(v);
|
|
2578
2607
|
}
|
|
2579
2608
|
const entry = resolveSlideEntry(ctx.slidesRoot, slideId$1);
|
|
2580
|
-
if (!entry) return json
|
|
2609
|
+
if (!entry) return json(res, 400, { error: "invalid slideId" });
|
|
2581
2610
|
let source;
|
|
2582
2611
|
try {
|
|
2583
2612
|
source = await fs.readFile(entry, "utf8");
|
|
2584
2613
|
} catch {
|
|
2585
|
-
return json
|
|
2614
|
+
return json(res, 404, { error: "slide not found" });
|
|
2586
2615
|
}
|
|
2587
2616
|
const reordered = reorderDefaultExportPagesInSource(source, order);
|
|
2588
|
-
if (reordered === null) return json
|
|
2617
|
+
if (reordered === null) return json(res, 422, { error: "could not reorder pages — order must be a permutation of the existing array" });
|
|
2589
2618
|
const withNotes = reorderNotesArrayInSource(reordered, order);
|
|
2590
|
-
if (withNotes === null) return json
|
|
2619
|
+
if (withNotes === null) return json(res, 422, { error: "could not reorder pages — `notes` export has an unexpected shape" });
|
|
2591
2620
|
if (withNotes !== source) await fs.writeFile(entry, withNotes, "utf8");
|
|
2592
|
-
return json
|
|
2621
|
+
return json(res, 200, {
|
|
2593
2622
|
ok: true,
|
|
2594
2623
|
slideId: slideId$1,
|
|
2595
2624
|
order
|
|
@@ -2600,25 +2629,25 @@ function registerSlideRoutes(server, ctx) {
|
|
|
2600
2629
|
const slideId$1 = pageOpMatch[1];
|
|
2601
2630
|
const pageIndex = Number.parseInt(pageOpMatch[2], 10);
|
|
2602
2631
|
const op = pageOpMatch[3];
|
|
2603
|
-
if (!SLIDE_ID_RE
|
|
2604
|
-
if (!Number.isInteger(pageIndex) || pageIndex < 0) return json
|
|
2632
|
+
if (!SLIDE_ID_RE.test(slideId$1)) return json(res, 400, { error: "invalid slideId" });
|
|
2633
|
+
if (!Number.isInteger(pageIndex) || pageIndex < 0) return json(res, 400, { error: "invalid page index" });
|
|
2605
2634
|
const isDelete = method === "DELETE" && !op;
|
|
2606
2635
|
const isDuplicate = method === "POST" && op === "duplicate";
|
|
2607
2636
|
if (!isDelete && !isDuplicate) return next();
|
|
2608
2637
|
const requestCheck = validateMutationRequest(req);
|
|
2609
|
-
if (!requestCheck.ok) return json
|
|
2638
|
+
if (!requestCheck.ok) return json(res, requestCheck.status, { error: requestCheck.error });
|
|
2610
2639
|
const entry = resolveSlideEntry(ctx.slidesRoot, slideId$1);
|
|
2611
|
-
if (!entry) return json
|
|
2640
|
+
if (!entry) return json(res, 400, { error: "invalid slideId" });
|
|
2612
2641
|
let source;
|
|
2613
2642
|
try {
|
|
2614
2643
|
source = await fs.readFile(entry, "utf8");
|
|
2615
2644
|
} catch {
|
|
2616
|
-
return json
|
|
2645
|
+
return json(res, 404, { error: "slide not found" });
|
|
2617
2646
|
}
|
|
2618
2647
|
const updated = isDelete ? removePageFromDefaultExportInSource(source, pageIndex) : duplicatePageInDefaultExportInSource(source, pageIndex);
|
|
2619
|
-
if (updated === null) return json
|
|
2648
|
+
if (updated === null) return json(res, 422, { error: isDelete ? "could not delete page — index out of range or default export is not an array" : "could not duplicate page — index out of range or default export is not an array" });
|
|
2620
2649
|
if (updated !== source) await fs.writeFile(entry, updated, "utf8");
|
|
2621
|
-
return json
|
|
2650
|
+
return json(res, 200, {
|
|
2622
2651
|
ok: true,
|
|
2623
2652
|
slideId: slideId$1,
|
|
2624
2653
|
index: pageIndex
|
|
@@ -2627,20 +2656,20 @@ function registerSlideRoutes(server, ctx) {
|
|
|
2627
2656
|
const duplicateMatch = url.pathname.match(/^\/([^/]+)\/duplicate$/);
|
|
2628
2657
|
if (duplicateMatch && method === "POST") {
|
|
2629
2658
|
const requestCheck = validateMutationRequest(req);
|
|
2630
|
-
if (!requestCheck.ok) return json
|
|
2659
|
+
if (!requestCheck.ok) return json(res, requestCheck.status, { error: requestCheck.error });
|
|
2631
2660
|
const slideId$1 = duplicateMatch[1];
|
|
2632
|
-
if (!SLIDE_ID_RE
|
|
2633
|
-
const body = await readBody
|
|
2634
|
-
if (body.newId !== void 0 && typeof body.newId !== "string") return json
|
|
2661
|
+
if (!SLIDE_ID_RE.test(slideId$1)) return json(res, 400, { error: "invalid slideId" });
|
|
2662
|
+
const body = await readBody(req);
|
|
2663
|
+
if (body.newId !== void 0 && typeof body.newId !== "string") return json(res, 400, { error: "invalid newId" });
|
|
2635
2664
|
const duplicated = await duplicateSlideDir(ctx.slidesRoot, slideId$1, body.newId);
|
|
2636
|
-
if (!duplicated.ok) return json
|
|
2665
|
+
if (!duplicated.ok) return json(res, duplicated.status, { error: duplicated.error });
|
|
2637
2666
|
const manifest = await readManifest(ctx.manifestPath);
|
|
2638
2667
|
const folderId = manifest.assignments[slideId$1];
|
|
2639
2668
|
if (folderId) {
|
|
2640
2669
|
manifest.assignments[duplicated.slideId] = folderId;
|
|
2641
2670
|
await writeManifest(ctx.manifestPath, manifest);
|
|
2642
2671
|
}
|
|
2643
|
-
return json
|
|
2672
|
+
return json(res, 200, {
|
|
2644
2673
|
ok: true,
|
|
2645
2674
|
slideId: duplicated.slideId
|
|
2646
2675
|
});
|
|
@@ -2648,26 +2677,26 @@ function registerSlideRoutes(server, ctx) {
|
|
|
2648
2677
|
const idMatch = url.pathname.match(/^\/([^/]+)$/);
|
|
2649
2678
|
if (!idMatch) return next();
|
|
2650
2679
|
const slideId = idMatch[1];
|
|
2651
|
-
if (!SLIDE_ID_RE
|
|
2680
|
+
if (!SLIDE_ID_RE.test(slideId)) return json(res, 400, { error: "invalid slideId" });
|
|
2652
2681
|
if (method === "PATCH") {
|
|
2653
2682
|
const requestCheck = validateMutationRequest(req, { requireJsonBody: true });
|
|
2654
|
-
if (!requestCheck.ok) return json
|
|
2655
|
-
const body = await readBody
|
|
2683
|
+
if (!requestCheck.ok) return json(res, requestCheck.status, { error: requestCheck.error });
|
|
2684
|
+
const body = await readBody(req);
|
|
2656
2685
|
const name = validateSlideName(body.name);
|
|
2657
|
-
if (!name) return json
|
|
2686
|
+
if (!name) return json(res, 400, { error: "invalid name" });
|
|
2658
2687
|
const entry = resolveSlideEntry(ctx.slidesRoot, slideId);
|
|
2659
|
-
if (!entry) return json
|
|
2688
|
+
if (!entry) return json(res, 400, { error: "invalid slideId" });
|
|
2660
2689
|
let source;
|
|
2661
2690
|
try {
|
|
2662
2691
|
source = await fs.readFile(entry, "utf8");
|
|
2663
2692
|
} catch {
|
|
2664
|
-
return json
|
|
2693
|
+
return json(res, 404, { error: "slide not found" });
|
|
2665
2694
|
}
|
|
2666
2695
|
const updated = updateMetaTitleInSource(source, name);
|
|
2667
|
-
if (updated === null) return json
|
|
2696
|
+
if (updated === null) return json(res, 422, { error: "could not locate a safe place to write meta.title in index.tsx" });
|
|
2668
2697
|
if (updated !== source) await fs.writeFile(entry, updated, "utf8");
|
|
2669
2698
|
server.ws.send({ type: "full-reload" });
|
|
2670
|
-
return json
|
|
2699
|
+
return json(res, 200, {
|
|
2671
2700
|
ok: true,
|
|
2672
2701
|
slideId,
|
|
2673
2702
|
name
|
|
@@ -2675,17 +2704,17 @@ function registerSlideRoutes(server, ctx) {
|
|
|
2675
2704
|
}
|
|
2676
2705
|
if (method === "DELETE") {
|
|
2677
2706
|
const requestCheck = validateMutationRequest(req);
|
|
2678
|
-
if (!requestCheck.ok) return json
|
|
2707
|
+
if (!requestCheck.ok) return json(res, requestCheck.status, { error: requestCheck.error });
|
|
2679
2708
|
const removed = await rmSlideDir(ctx.slidesRoot, slideId);
|
|
2680
|
-
if (!removed) return json
|
|
2709
|
+
if (!removed) return json(res, 404, { error: "slide not found" });
|
|
2681
2710
|
const manifest = await readManifest(ctx.manifestPath);
|
|
2682
2711
|
delete manifest.assignments[slideId];
|
|
2683
2712
|
await writeManifest(ctx.manifestPath, manifest);
|
|
2684
|
-
return json
|
|
2713
|
+
return json(res, 200, { ok: true });
|
|
2685
2714
|
}
|
|
2686
2715
|
return next();
|
|
2687
2716
|
} catch (err) {
|
|
2688
|
-
json
|
|
2717
|
+
json(res, 500, { error: String(err.message ?? err) });
|
|
2689
2718
|
}
|
|
2690
2719
|
});
|
|
2691
2720
|
}
|
|
@@ -2709,16 +2738,16 @@ function registerSvglRoutes(server) {
|
|
|
2709
2738
|
target = `https://api.svgl.app/${qs ? `?${qs}` : ""}`;
|
|
2710
2739
|
} else if (reqUrl.pathname === "/svg") {
|
|
2711
2740
|
const u = reqUrl.searchParams.get("u");
|
|
2712
|
-
if (!u) return json
|
|
2741
|
+
if (!u) return json(res, 400, { error: "missing u" });
|
|
2713
2742
|
let parsed;
|
|
2714
2743
|
try {
|
|
2715
2744
|
parsed = new URL(u);
|
|
2716
2745
|
} catch {
|
|
2717
|
-
return json
|
|
2746
|
+
return json(res, 400, { error: "invalid u" });
|
|
2718
2747
|
}
|
|
2719
|
-
if (parsed.protocol !== "https:") return json
|
|
2748
|
+
if (parsed.protocol !== "https:") return json(res, 400, { error: "https only" });
|
|
2720
2749
|
const host = parsed.hostname.toLowerCase();
|
|
2721
|
-
if (host !== "svgl.app" && !host.endsWith(".svgl.app")) return json
|
|
2750
|
+
if (host !== "svgl.app" && !host.endsWith(".svgl.app")) return json(res, 400, { error: "host not allowed" });
|
|
2722
2751
|
target = parsed.toString();
|
|
2723
2752
|
} else return next();
|
|
2724
2753
|
const upstream = await fetch(target);
|
|
@@ -2729,11 +2758,68 @@ function registerSvglRoutes(server) {
|
|
|
2729
2758
|
const buf = Buffer.from(await upstream.arrayBuffer());
|
|
2730
2759
|
res.end(buf);
|
|
2731
2760
|
} catch (err) {
|
|
2732
|
-
json
|
|
2761
|
+
json(res, 502, { error: String(err.message ?? err) });
|
|
2733
2762
|
}
|
|
2734
2763
|
});
|
|
2735
2764
|
}
|
|
2736
2765
|
|
|
2766
|
+
//#endregion
|
|
2767
|
+
//#region src/vite/routes/update.ts
|
|
2768
|
+
const PKG = "@open-slide/core";
|
|
2769
|
+
const CACHE_TTL_MS = 10 * 60 * 1e3;
|
|
2770
|
+
let cache = null;
|
|
2771
|
+
function parseSemver(v) {
|
|
2772
|
+
const m = /^v?(\d+)\.(\d+)\.(\d+)/.exec(v.trim());
|
|
2773
|
+
if (!m) return null;
|
|
2774
|
+
return [
|
|
2775
|
+
Number(m[1]),
|
|
2776
|
+
Number(m[2]),
|
|
2777
|
+
Number(m[3])
|
|
2778
|
+
];
|
|
2779
|
+
}
|
|
2780
|
+
function isOutdated(current, latest) {
|
|
2781
|
+
const a = parseSemver(current);
|
|
2782
|
+
const b = parseSemver(latest);
|
|
2783
|
+
if (!a || !b) return false;
|
|
2784
|
+
for (let i = 0; i < 3; i++) {
|
|
2785
|
+
if (b[i] > a[i]) return true;
|
|
2786
|
+
if (b[i] < a[i]) return false;
|
|
2787
|
+
}
|
|
2788
|
+
return false;
|
|
2789
|
+
}
|
|
2790
|
+
async function fetchLatest(now) {
|
|
2791
|
+
if (cache && now - cache.at < CACHE_TTL_MS) return cache.latest;
|
|
2792
|
+
try {
|
|
2793
|
+
const res = await fetch(`https://registry.npmjs.org/${PKG}/latest`, {
|
|
2794
|
+
signal: AbortSignal.timeout(3e3),
|
|
2795
|
+
headers: { accept: "application/json" }
|
|
2796
|
+
});
|
|
2797
|
+
if (!res.ok) throw new Error(`registry ${res.status}`);
|
|
2798
|
+
const body = await res.json();
|
|
2799
|
+
const latest = typeof body.version === "string" ? body.version : null;
|
|
2800
|
+
cache = {
|
|
2801
|
+
at: now,
|
|
2802
|
+
latest
|
|
2803
|
+
};
|
|
2804
|
+
return latest;
|
|
2805
|
+
} catch {
|
|
2806
|
+
return cache?.latest ?? null;
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
function registerUpdateRoutes(server, current) {
|
|
2810
|
+
server.middlewares.use("/__update-check", async (req, res, next) => {
|
|
2811
|
+
if ((req.method ?? "GET") !== "GET") return next();
|
|
2812
|
+
const latest = await fetchLatest(Date.now());
|
|
2813
|
+
const result = {
|
|
2814
|
+
current,
|
|
2815
|
+
latest,
|
|
2816
|
+
outdated: latest ? isOutdated(current, latest) : false
|
|
2817
|
+
};
|
|
2818
|
+
res.setHeader("cache-control", "no-store");
|
|
2819
|
+
json(res, 200, result);
|
|
2820
|
+
});
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2737
2823
|
//#endregion
|
|
2738
2824
|
//#region src/vite/routes/watchers.ts
|
|
2739
2825
|
function registerWatchers(server, ctx) {
|
|
@@ -2759,7 +2845,7 @@ function registerWatchers(server, ctx) {
|
|
|
2759
2845
|
const parts = rel.split(path.sep);
|
|
2760
2846
|
if (parts.length < 3 || parts[1] !== "assets") return;
|
|
2761
2847
|
const slideId = parts[0];
|
|
2762
|
-
if (!SLIDE_ID_RE
|
|
2848
|
+
if (!SLIDE_ID_RE.test(slideId)) return;
|
|
2763
2849
|
server.ws.send({
|
|
2764
2850
|
type: "custom",
|
|
2765
2851
|
event: "open-slide:assets-changed",
|
|
@@ -2786,13 +2872,13 @@ function apiPlugin(opts) {
|
|
|
2786
2872
|
registerAssetRoutes(server, ctx);
|
|
2787
2873
|
registerSvglRoutes(server);
|
|
2788
2874
|
registerFolderRoutes(server, ctx);
|
|
2875
|
+
registerUpdateRoutes(server, ctx.coreVersion);
|
|
2789
2876
|
}
|
|
2790
2877
|
};
|
|
2791
2878
|
}
|
|
2792
2879
|
|
|
2793
2880
|
//#endregion
|
|
2794
2881
|
//#region src/vite/current-plugin.ts
|
|
2795
|
-
const SLIDE_ID_RE$2 = /^[a-z0-9_-]+$/i;
|
|
2796
2882
|
const TEXT_SNIPPET_MAX = 120;
|
|
2797
2883
|
function parseSelection(raw) {
|
|
2798
2884
|
if (raw == null || typeof raw !== "object") return null;
|
|
@@ -2831,7 +2917,7 @@ function currentPlugin(opts) {
|
|
|
2831
2917
|
selection: null
|
|
2832
2918
|
};
|
|
2833
2919
|
if (typeof raw?.slideId === "string") {
|
|
2834
|
-
if (!SLIDE_ID_RE
|
|
2920
|
+
if (!SLIDE_ID_RE.test(raw.slideId)) return;
|
|
2835
2921
|
const totalPages = typeof raw.totalPages === "number" && Number.isFinite(raw.totalPages) && raw.totalPages > 0 ? Math.floor(raw.totalPages) : 1;
|
|
2836
2922
|
const rawIndex = typeof raw.pageIndex === "number" && Number.isFinite(raw.pageIndex) ? Math.floor(raw.pageIndex) : 0;
|
|
2837
2923
|
const pageIndex = Math.max(0, Math.min(totalPages - 1, rawIndex));
|
|
@@ -2866,35 +2952,6 @@ function currentPlugin(opts) {
|
|
|
2866
2952
|
|
|
2867
2953
|
//#endregion
|
|
2868
2954
|
//#region src/vite/design-plugin.ts
|
|
2869
|
-
const SLIDE_ID_RE$1 = /^[a-z0-9_-]+$/i;
|
|
2870
|
-
async function readBody$1(req) {
|
|
2871
|
-
return await new Promise((resolve, reject) => {
|
|
2872
|
-
const chunks = [];
|
|
2873
|
-
req.on("data", (c) => chunks.push(c));
|
|
2874
|
-
req.on("end", () => {
|
|
2875
|
-
const raw = Buffer.concat(chunks).toString("utf8");
|
|
2876
|
-
if (!raw) return resolve({});
|
|
2877
|
-
try {
|
|
2878
|
-
resolve(JSON.parse(raw));
|
|
2879
|
-
} catch (e) {
|
|
2880
|
-
reject(e);
|
|
2881
|
-
}
|
|
2882
|
-
});
|
|
2883
|
-
req.on("error", reject);
|
|
2884
|
-
});
|
|
2885
|
-
}
|
|
2886
|
-
function json$1(res, status, body) {
|
|
2887
|
-
res.statusCode = status;
|
|
2888
|
-
res.setHeader("content-type", "application/json");
|
|
2889
|
-
res.end(JSON.stringify(body));
|
|
2890
|
-
}
|
|
2891
|
-
function resolveSlidePath$1(userCwd, slidesDir, slideId) {
|
|
2892
|
-
if (!SLIDE_ID_RE$1.test(slideId)) return null;
|
|
2893
|
-
const slidesRoot = path.resolve(userCwd, slidesDir);
|
|
2894
|
-
const full = path.resolve(slidesRoot, slideId, "index.tsx");
|
|
2895
|
-
if (!full.startsWith(`${slidesRoot}${path.sep}`)) return null;
|
|
2896
|
-
return full;
|
|
2897
|
-
}
|
|
2898
2955
|
function parseSource$1(source) {
|
|
2899
2956
|
try {
|
|
2900
2957
|
return parse(source, {
|
|
@@ -3217,28 +3274,28 @@ function designPlugin(opts) {
|
|
|
3217
3274
|
const url = new URL(req.url ?? "/", "http://local");
|
|
3218
3275
|
const method = req.method ?? "GET";
|
|
3219
3276
|
const slideId = url.searchParams.get("slideId") ?? "";
|
|
3220
|
-
const file = resolveSlidePath
|
|
3221
|
-
if (!file) return json
|
|
3277
|
+
const file = resolveSlidePath(userCwd, slidesDir, slideId);
|
|
3278
|
+
if (!file) return json(res, 400, { error: "invalid slideId" });
|
|
3222
3279
|
try {
|
|
3223
3280
|
if (method === "GET" && url.pathname === "/") {
|
|
3224
3281
|
let source;
|
|
3225
3282
|
try {
|
|
3226
3283
|
source = await fs.readFile(file, "utf8");
|
|
3227
3284
|
} catch {
|
|
3228
|
-
return json
|
|
3285
|
+
return json(res, 404, { error: "slide not found" });
|
|
3229
3286
|
}
|
|
3230
3287
|
const parsed = parseSlideDesign(source);
|
|
3231
|
-
if (parsed.ok) return json
|
|
3288
|
+
if (parsed.ok) return json(res, 200, {
|
|
3232
3289
|
design: parsed.design,
|
|
3233
3290
|
exists: true,
|
|
3234
3291
|
warning: null
|
|
3235
3292
|
});
|
|
3236
|
-
if (parsed.exists === false) return json
|
|
3293
|
+
if (parsed.exists === false) return json(res, 200, {
|
|
3237
3294
|
design: defaultDesign,
|
|
3238
3295
|
exists: false,
|
|
3239
3296
|
warning: null
|
|
3240
3297
|
});
|
|
3241
|
-
return json
|
|
3298
|
+
return json(res, 200, {
|
|
3242
3299
|
design: defaultDesign,
|
|
3243
3300
|
exists: true,
|
|
3244
3301
|
warning: parsed.error
|
|
@@ -3246,24 +3303,24 @@ function designPlugin(opts) {
|
|
|
3246
3303
|
}
|
|
3247
3304
|
if (method === "PUT" && url.pathname === "/") {
|
|
3248
3305
|
const requestCheck = validateMutationRequest(req, { requireJsonBody: true });
|
|
3249
|
-
if (!requestCheck.ok) return json
|
|
3250
|
-
const body = await readBody
|
|
3306
|
+
if (!requestCheck.ok) return json(res, requestCheck.status, { error: requestCheck.error });
|
|
3307
|
+
const body = await readBody(req);
|
|
3251
3308
|
const patch = body.patch;
|
|
3252
|
-
if (!patch || typeof patch !== "object") return json
|
|
3309
|
+
if (!patch || typeof patch !== "object") return json(res, 400, { error: "missing patch object" });
|
|
3253
3310
|
let source;
|
|
3254
3311
|
try {
|
|
3255
3312
|
source = await fs.readFile(file, "utf8");
|
|
3256
3313
|
} catch {
|
|
3257
|
-
return json
|
|
3314
|
+
return json(res, 404, { error: "slide not found" });
|
|
3258
3315
|
}
|
|
3259
3316
|
const parsed = parseSlideDesign(source);
|
|
3260
3317
|
const baseDesign = parsed.ok ? parsed.design : defaultDesign;
|
|
3261
|
-
if (!parsed.ok && parsed.exists) return json
|
|
3318
|
+
if (!parsed.ok && parsed.exists) return json(res, 422, { error: parsed.error });
|
|
3262
3319
|
const merged = mergeDesign(baseDesign, patch);
|
|
3263
3320
|
const written = applyDesignWrite(source, merged);
|
|
3264
|
-
if (!written.ok) return json
|
|
3321
|
+
if (!written.ok) return json(res, written.status, { error: written.error });
|
|
3265
3322
|
if (written.source !== source) await fs.writeFile(file, written.source, "utf8");
|
|
3266
|
-
return json
|
|
3323
|
+
return json(res, 200, {
|
|
3267
3324
|
ok: true,
|
|
3268
3325
|
design: merged,
|
|
3269
3326
|
created: written.created
|
|
@@ -3271,17 +3328,17 @@ function designPlugin(opts) {
|
|
|
3271
3328
|
}
|
|
3272
3329
|
if (method === "POST" && url.pathname === "/reset") {
|
|
3273
3330
|
const requestCheck = validateMutationRequest(req);
|
|
3274
|
-
if (!requestCheck.ok) return json
|
|
3331
|
+
if (!requestCheck.ok) return json(res, requestCheck.status, { error: requestCheck.error });
|
|
3275
3332
|
let source;
|
|
3276
3333
|
try {
|
|
3277
3334
|
source = await fs.readFile(file, "utf8");
|
|
3278
3335
|
} catch {
|
|
3279
|
-
return json
|
|
3336
|
+
return json(res, 404, { error: "slide not found" });
|
|
3280
3337
|
}
|
|
3281
3338
|
const written = applyDesignWrite(source, defaultDesign);
|
|
3282
|
-
if (!written.ok) return json
|
|
3339
|
+
if (!written.ok) return json(res, written.status, { error: written.error });
|
|
3283
3340
|
if (written.source !== source) await fs.writeFile(file, written.source, "utf8");
|
|
3284
|
-
return json
|
|
3341
|
+
return json(res, 200, {
|
|
3285
3342
|
ok: true,
|
|
3286
3343
|
design: defaultDesign,
|
|
3287
3344
|
created: written.created
|
|
@@ -3289,7 +3346,7 @@ function designPlugin(opts) {
|
|
|
3289
3346
|
}
|
|
3290
3347
|
return next();
|
|
3291
3348
|
} catch (err) {
|
|
3292
|
-
json
|
|
3349
|
+
json(res, 500, { error: String(err.message ?? err) });
|
|
3293
3350
|
}
|
|
3294
3351
|
});
|
|
3295
3352
|
}
|
|
@@ -3334,19 +3391,22 @@ function injectLocTags(code) {
|
|
|
3334
3391
|
for (const ins of insertions) next = next.slice(0, ins.offset) + ins.text + next.slice(ins.offset);
|
|
3335
3392
|
return next;
|
|
3336
3393
|
}
|
|
3394
|
+
function isSlideSourceFile(id, slidesRootPosix) {
|
|
3395
|
+
const filePath = id.split(/[?#]/)[0].replace(/\\/g, "/");
|
|
3396
|
+
if (!filePath.startsWith(`${slidesRootPosix}/`)) return false;
|
|
3397
|
+
if (!filePath.endsWith(".tsx")) return false;
|
|
3398
|
+
if (filePath.endsWith(".d.ts") || filePath.endsWith(".test.tsx")) return false;
|
|
3399
|
+
const rel = filePath.slice(slidesRootPosix.length + 1);
|
|
3400
|
+
return rel.includes("/");
|
|
3401
|
+
}
|
|
3337
3402
|
function locTagsPlugin(opts) {
|
|
3338
|
-
const slidesRoot = path.resolve(opts.userCwd, opts.slidesDir ?? "slides");
|
|
3403
|
+
const slidesRoot = path.resolve(opts.userCwd, opts.slidesDir ?? "slides").replace(/\\/g, "/");
|
|
3339
3404
|
return {
|
|
3340
3405
|
name: "open-slide:loc-tags",
|
|
3341
3406
|
apply: "serve",
|
|
3342
3407
|
enforce: "pre",
|
|
3343
3408
|
transform(code, id) {
|
|
3344
|
-
|
|
3345
|
-
if (!filePath.startsWith(slidesRoot + path.sep)) return null;
|
|
3346
|
-
if (!filePath.endsWith(".tsx")) return null;
|
|
3347
|
-
if (filePath.endsWith(".d.ts") || filePath.endsWith(".test.tsx")) return null;
|
|
3348
|
-
const rel = filePath.slice(slidesRoot.length + path.sep.length);
|
|
3349
|
-
if (!rel.includes(path.sep)) return null;
|
|
3409
|
+
if (!isSlideSourceFile(id, slidesRoot)) return null;
|
|
3350
3410
|
const next = injectLocTags(code);
|
|
3351
3411
|
if (next === null) return null;
|
|
3352
3412
|
return {
|
|
@@ -3359,35 +3419,6 @@ function locTagsPlugin(opts) {
|
|
|
3359
3419
|
|
|
3360
3420
|
//#endregion
|
|
3361
3421
|
//#region src/vite/notes-plugin.ts
|
|
3362
|
-
const SLIDE_ID_RE = /^[a-z0-9_-]+$/i;
|
|
3363
|
-
async function readBody(req) {
|
|
3364
|
-
return await new Promise((resolve, reject) => {
|
|
3365
|
-
const chunks = [];
|
|
3366
|
-
req.on("data", (c) => chunks.push(c));
|
|
3367
|
-
req.on("end", () => {
|
|
3368
|
-
const raw = Buffer.concat(chunks).toString("utf8");
|
|
3369
|
-
if (!raw) return resolve({});
|
|
3370
|
-
try {
|
|
3371
|
-
resolve(JSON.parse(raw));
|
|
3372
|
-
} catch (e) {
|
|
3373
|
-
reject(e);
|
|
3374
|
-
}
|
|
3375
|
-
});
|
|
3376
|
-
req.on("error", reject);
|
|
3377
|
-
});
|
|
3378
|
-
}
|
|
3379
|
-
function json(res, status, body) {
|
|
3380
|
-
res.statusCode = status;
|
|
3381
|
-
res.setHeader("content-type", "application/json");
|
|
3382
|
-
res.end(JSON.stringify(body));
|
|
3383
|
-
}
|
|
3384
|
-
function resolveSlidePath(userCwd, slidesDir, slideId) {
|
|
3385
|
-
if (!SLIDE_ID_RE.test(slideId)) return null;
|
|
3386
|
-
const slidesRoot = path.resolve(userCwd, slidesDir);
|
|
3387
|
-
const full = path.resolve(slidesRoot, slideId, "index.tsx");
|
|
3388
|
-
if (!full.startsWith(slidesRoot + path.sep)) return null;
|
|
3389
|
-
return full;
|
|
3390
|
-
}
|
|
3391
3422
|
function parseSource(source) {
|
|
3392
3423
|
try {
|
|
3393
3424
|
return parse(source, {
|
|
@@ -3700,7 +3731,7 @@ ${cases}
|
|
|
3700
3731
|
`;
|
|
3701
3732
|
}
|
|
3702
3733
|
function openSlidePlugin(opts) {
|
|
3703
|
-
const { userCwd, config } = opts;
|
|
3734
|
+
const { userCwd, config, coreVersion } = opts;
|
|
3704
3735
|
const slidesDir = config.slidesDir ?? "slides";
|
|
3705
3736
|
const slidesRoot = path.resolve(userCwd, slidesDir);
|
|
3706
3737
|
const foldersManifestPath = path.join(slidesRoot, ".folders.json");
|
|
@@ -3761,7 +3792,8 @@ function openSlidePlugin(opts) {
|
|
|
3761
3792
|
};
|
|
3762
3793
|
const resolvedConfig = {
|
|
3763
3794
|
...config,
|
|
3764
|
-
build: buildResolved
|
|
3795
|
+
build: buildResolved,
|
|
3796
|
+
version: coreVersion
|
|
3765
3797
|
};
|
|
3766
3798
|
return `export default ${JSON.stringify(resolvedConfig)};\n`;
|
|
3767
3799
|
}
|
|
@@ -3978,6 +4010,15 @@ function findPackageRoot(fromFile) {
|
|
|
3978
4010
|
}
|
|
3979
4011
|
const PKG_ROOT = findPackageRoot(fileURLToPath(import.meta.url));
|
|
3980
4012
|
const APP_ROOT = path.join(PKG_ROOT, "src", "app");
|
|
4013
|
+
function readCoreVersion() {
|
|
4014
|
+
try {
|
|
4015
|
+
const raw = readFileSync(path.join(PKG_ROOT, "package.json"), "utf8");
|
|
4016
|
+
return JSON.parse(raw).version ?? "0.0.0";
|
|
4017
|
+
} catch {
|
|
4018
|
+
return "0.0.0";
|
|
4019
|
+
}
|
|
4020
|
+
}
|
|
4021
|
+
const CORE_VERSION = readCoreVersion();
|
|
3981
4022
|
async function createViteConfig(opts) {
|
|
3982
4023
|
const userCwd = path.resolve(opts.userCwd);
|
|
3983
4024
|
const config = opts.config ?? await loadUserConfig(userCwd);
|
|
@@ -4000,7 +4041,8 @@ async function createViteConfig(opts) {
|
|
|
4000
4041
|
tailwindcss(),
|
|
4001
4042
|
openSlidePlugin({
|
|
4002
4043
|
userCwd,
|
|
4003
|
-
config
|
|
4044
|
+
config,
|
|
4045
|
+
coreVersion: CORE_VERSION
|
|
4004
4046
|
}),
|
|
4005
4047
|
themesPlugin({
|
|
4006
4048
|
userCwd,
|
|
@@ -4010,7 +4052,8 @@ async function createViteConfig(opts) {
|
|
|
4010
4052
|
apiPlugin({
|
|
4011
4053
|
userCwd,
|
|
4012
4054
|
slidesDir,
|
|
4013
|
-
assetsDir
|
|
4055
|
+
assetsDir,
|
|
4056
|
+
coreVersion: CORE_VERSION
|
|
4014
4057
|
}),
|
|
4015
4058
|
notesPlugin({
|
|
4016
4059
|
userCwd,
|