@gallop.software/studio 2.3.136 → 2.3.137

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.
@@ -11,7 +11,7 @@
11
11
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
12
12
  }
13
13
  </style>
14
- <script type="module" crossorigin src="/assets/index-8i66yX4y.js"></script>
14
+ <script type="module" crossorigin src="/assets/index-3VggyaFt.js"></script>
15
15
  <link rel="stylesheet" crossorigin href="/assets/index-DfPQBmNf.css">
16
16
  </head>
17
17
  <body>
@@ -4575,123 +4575,86 @@ async function handleEditImage(request) {
4575
4575
  // src/handlers/fonts.ts
4576
4576
  import { promises as fs12 } from "fs";
4577
4577
  import path11 from "path";
4578
- var VALID_WEIGHTS = [
4579
- "thin",
4580
- "extralight",
4581
- "light",
4582
- "regular",
4583
- "medium",
4584
- "semibold",
4585
- "bold",
4586
- "extrabold",
4587
- "black"
4588
- ];
4589
- function parseFontFilename(filename) {
4590
- const nameWithoutExt = filename.replace(/\.(ttf|woff2?|otf)$/i, "");
4591
- const nameLower = nameWithoutExt.toLowerCase();
4592
- const hasItalic = nameLower.includes("italic");
4593
- const style = hasItalic ? "italic" : "normal";
4594
- const parts = nameWithoutExt.split(/[-_]/);
4595
- if (parts.length === 1) {
4596
- return {
4597
- basename: nameLower.replace("italic", "").trim(),
4598
- weight: "regular",
4599
- style,
4600
- isValid: false
4601
- };
4602
- }
4603
- const basename = parts[0].toLowerCase();
4604
- const weightPart = parts.slice(1).join("").toLowerCase().replace("italic", "");
4605
- let weight = "regular";
4606
- let isValid = false;
4607
- for (const validWeight of VALID_WEIGHTS) {
4608
- if (weightPart.includes(validWeight)) {
4609
- weight = validWeight;
4610
- isValid = true;
4611
- break;
4578
+ async function handleFontsList(request) {
4579
+ const searchParams = new URL(request.url).searchParams;
4580
+ const requestedPath = searchParams.get("path") || "_fonts";
4581
+ try {
4582
+ const items = [];
4583
+ let fsPath;
4584
+ let allowedPaths = ["_fonts", "src/fonts", "src"];
4585
+ const isAllowed = allowedPaths.some(
4586
+ (allowed) => requestedPath === allowed || requestedPath.startsWith(allowed + "/")
4587
+ );
4588
+ if (!isAllowed) {
4589
+ return jsonResponse({ items: [], error: "Path not allowed" }, { status: 400 });
4612
4590
  }
4613
- }
4614
- if (!isValid) {
4615
- if (weightPart === "" || weightPart === "regular" || weightPart === "normal") {
4616
- weight = "regular";
4617
- isValid = true;
4591
+ if (requestedPath === "src") {
4592
+ const fontsFolderPath = getWorkspacePath("src", "fonts");
4593
+ try {
4594
+ const stat = await fs12.stat(fontsFolderPath);
4595
+ if (stat.isDirectory()) {
4596
+ items.push({
4597
+ name: "fonts",
4598
+ path: "src/fonts",
4599
+ type: "folder"
4600
+ });
4601
+ }
4602
+ } catch {
4603
+ }
4604
+ return jsonResponse({ items, canCreate: true });
4618
4605
  }
4619
- }
4620
- return { basename, weight, style, isValid };
4621
- }
4622
- function generateFontFilename(basename, weight, style, ext) {
4623
- const styleSuffix = style === "italic" ? "italic" : "";
4624
- const weightAndStyle = weight + styleSuffix;
4625
- return `${basename}-${weightAndStyle}${ext}`.toLowerCase();
4626
- }
4627
- async function handleFontsList() {
4628
- try {
4629
- const fontsDir = getWorkspacePath("_fonts");
4630
- const configDir = getWorkspacePath("src", "fonts");
4631
- const families = [];
4632
- const configs = [];
4606
+ fsPath = getWorkspacePath(requestedPath);
4633
4607
  try {
4634
- const entries = await fs12.readdir(fontsDir, { withFileTypes: true });
4635
- for (const entry of entries) {
4636
- if (entry.isDirectory()) {
4637
- const familyName = entry.name.toLowerCase();
4638
- const familyPath = path11.join(fontsDir, entry.name);
4639
- const files = await fs12.readdir(familyPath);
4640
- const fontFiles = [];
4641
- const weightsSet = /* @__PURE__ */ new Set();
4642
- for (const file of files) {
4643
- if (file.match(/\.(ttf|woff2?)$/i)) {
4644
- const parsed = parseFontFilename(file);
4645
- fontFiles.push({
4646
- name: file,
4647
- weight: parsed.weight,
4648
- style: parsed.style,
4649
- path: `_fonts/${entry.name}/${file}`
4650
- });
4651
- weightsSet.add(parsed.weight);
4652
- }
4653
- }
4654
- if (fontFiles.length > 0) {
4655
- families.push({
4656
- name: familyName,
4657
- files: fontFiles.sort((a, b) => {
4658
- const weightOrder = VALID_WEIGHTS.indexOf(a.weight) - VALID_WEIGHTS.indexOf(b.weight);
4659
- if (weightOrder !== 0) return weightOrder;
4660
- return a.style === "normal" ? -1 : 1;
4661
- }),
4662
- fileCount: fontFiles.length,
4663
- weights: Array.from(weightsSet).sort(
4664
- (a, b) => VALID_WEIGHTS.indexOf(a) - VALID_WEIGHTS.indexOf(b)
4665
- )
4666
- });
4667
- }
4668
- }
4608
+ const stat = await fs12.stat(fsPath);
4609
+ if (!stat.isDirectory()) {
4610
+ return jsonResponse({ items: [] });
4669
4611
  }
4670
4612
  } catch {
4613
+ return jsonResponse({ items: [], canCreate: true });
4671
4614
  }
4672
- try {
4673
- const configFiles = await fs12.readdir(configDir);
4674
- for (const file of configFiles) {
4675
- if (file.endsWith(".ts") && !file.startsWith("_")) {
4676
- const configPath = path11.join(configDir, file);
4677
- const content = await fs12.readFile(configPath, "utf-8");
4678
- const exportMatch = content.match(/export\s+const\s+(\w+)\s*=/);
4679
- const exportName = exportMatch ? exportMatch[1] : file.replace(".ts", "") + "Font";
4680
- const pathMatch = content.match(/path:\s*['"]\.\.\/\.\.\/\_fonts\/([^\/]+)\//);
4681
- const family = pathMatch ? pathMatch[1].toLowerCase() : "unknown";
4682
- const type = file.replace(".ts", "");
4683
- configs.push({
4684
- type,
4685
- family,
4686
- path: `src/fonts/${file}`,
4687
- exportName
4615
+ const entries = await fs12.readdir(fsPath, { withFileTypes: true });
4616
+ for (const entry of entries) {
4617
+ const itemPath = `${requestedPath}/${entry.name}`;
4618
+ if (entry.isDirectory()) {
4619
+ let fileCount = 0;
4620
+ try {
4621
+ const subEntries = await fs12.readdir(path11.join(fsPath, entry.name));
4622
+ fileCount = subEntries.filter(
4623
+ (f) => f.match(/\.(ttf|woff2?|otf|ts|tsx|js)$/i)
4624
+ ).length;
4625
+ } catch {
4626
+ }
4627
+ items.push({
4628
+ name: entry.name,
4629
+ path: itemPath,
4630
+ type: "folder",
4631
+ fileCount
4632
+ });
4633
+ } else {
4634
+ const ext = path11.extname(entry.name).toLowerCase();
4635
+ const allowedExts = [".ttf", ".woff", ".woff2", ".otf", ".ts", ".tsx", ".js"];
4636
+ if (allowedExts.includes(ext)) {
4637
+ let size = 0;
4638
+ try {
4639
+ const fileStat = await fs12.stat(path11.join(fsPath, entry.name));
4640
+ size = fileStat.size;
4641
+ } catch {
4642
+ }
4643
+ items.push({
4644
+ name: entry.name,
4645
+ path: itemPath,
4646
+ type: "file",
4647
+ size
4688
4648
  });
4689
4649
  }
4690
4650
  }
4691
- } catch {
4692
4651
  }
4693
- families.sort((a, b) => a.name.localeCompare(b.name));
4694
- return jsonResponse({ families, configs });
4652
+ items.sort((a, b) => {
4653
+ if (a.type === "folder" && b.type !== "folder") return -1;
4654
+ if (a.type !== "folder" && b.type === "folder") return 1;
4655
+ return a.name.localeCompare(b.name);
4656
+ });
4657
+ return jsonResponse({ items });
4695
4658
  } catch (error) {
4696
4659
  console.error("Error listing fonts:", error);
4697
4660
  return jsonResponse({ error: "Failed to list fonts" }, { status: 500 });
@@ -4700,67 +4663,95 @@ async function handleFontsList() {
4700
4663
  async function handleFontsUpload(request) {
4701
4664
  try {
4702
4665
  const formData = await request.formData();
4703
- const files = formData.getAll("files");
4704
- const renamesJson = formData.get("renames");
4705
- if (!files || files.length === 0) {
4706
- return jsonResponse({ error: "No files provided" }, { status: 400 });
4666
+ const file = formData.get("file");
4667
+ const targetPath = formData.get("path") || "_fonts";
4668
+ if (!file) {
4669
+ return jsonResponse({ error: "No file provided" }, { status: 400 });
4707
4670
  }
4708
- let renames = {};
4709
- if (renamesJson) {
4710
- try {
4711
- renames = JSON.parse(renamesJson);
4712
- } catch {
4671
+ if (!file.name.toLowerCase().endsWith(".ttf")) {
4672
+ return jsonResponse({ error: "Only TTF files are supported" }, { status: 400 });
4673
+ }
4674
+ if (!targetPath.startsWith("_fonts")) {
4675
+ return jsonResponse({ error: "Can only upload to _fonts/" }, { status: 400 });
4676
+ }
4677
+ const bytes = await file.arrayBuffer();
4678
+ const buffer = Buffer.from(bytes);
4679
+ const uploadDir = getWorkspacePath(targetPath);
4680
+ await fs12.mkdir(uploadDir, { recursive: true });
4681
+ const filePath = path11.join(uploadDir, file.name.toLowerCase());
4682
+ await fs12.writeFile(filePath, buffer);
4683
+ return jsonResponse({
4684
+ success: true,
4685
+ path: `${targetPath}/${file.name.toLowerCase()}`
4686
+ });
4687
+ } catch (error) {
4688
+ console.error("Error uploading font:", error);
4689
+ return jsonResponse({ error: "Failed to upload font" }, { status: 500 });
4690
+ }
4691
+ }
4692
+ async function handleFontsCreateFolder(request) {
4693
+ try {
4694
+ const { path: targetPath, name } = await request.json();
4695
+ if (!targetPath || !name) {
4696
+ return jsonResponse({ error: "Path and name are required" }, { status: 400 });
4697
+ }
4698
+ const allowedPaths = ["_fonts", "src/fonts", "src"];
4699
+ const isAllowed = allowedPaths.some(
4700
+ (allowed) => targetPath === allowed || targetPath.startsWith(allowed + "/")
4701
+ );
4702
+ if (!isAllowed) {
4703
+ return jsonResponse({ error: "Path not allowed" }, { status: 400 });
4704
+ }
4705
+ const folderPath = getWorkspacePath(targetPath, name.toLowerCase());
4706
+ await fs12.mkdir(folderPath, { recursive: true });
4707
+ return jsonResponse({
4708
+ success: true,
4709
+ path: `${targetPath}/${name.toLowerCase()}`
4710
+ });
4711
+ } catch (error) {
4712
+ console.error("Error creating folder:", error);
4713
+ return jsonResponse({ error: "Failed to create folder" }, { status: 500 });
4714
+ }
4715
+ }
4716
+ async function handleFontsDelete(request) {
4717
+ try {
4718
+ const { paths } = await request.json();
4719
+ if (!paths || !Array.isArray(paths) || paths.length === 0) {
4720
+ return jsonResponse({ error: "Paths are required" }, { status: 400 });
4721
+ }
4722
+ const allowedPaths = ["_fonts", "src/fonts"];
4723
+ for (const p of paths) {
4724
+ const isAllowed = allowedPaths.some(
4725
+ (allowed) => p.startsWith(allowed + "/")
4726
+ );
4727
+ if (!isAllowed) {
4728
+ return jsonResponse({ error: `Path not allowed: ${p}` }, { status: 400 });
4713
4729
  }
4714
4730
  }
4715
- const fontsDir = getWorkspacePath("_fonts");
4716
- const uploaded = [];
4731
+ const deleted = [];
4717
4732
  const errors = [];
4718
- for (const file of files) {
4733
+ for (const p of paths) {
4719
4734
  try {
4720
- if (!file.name.toLowerCase().endsWith(".ttf")) {
4721
- errors.push({ file: file.name, error: "Only TTF files are supported" });
4722
- continue;
4723
- }
4724
- const bytes = await file.arrayBuffer();
4725
- const buffer = Buffer.from(bytes);
4726
- let basename;
4727
- let weight;
4728
- let style;
4729
- if (renames[file.name]) {
4730
- basename = renames[file.name].basename.toLowerCase();
4731
- weight = renames[file.name].weight.toLowerCase();
4732
- style = renames[file.name].style.toLowerCase();
4735
+ const fullPath = getWorkspacePath(p);
4736
+ const stat = await fs12.stat(fullPath);
4737
+ if (stat.isDirectory()) {
4738
+ await fs12.rm(fullPath, { recursive: true });
4733
4739
  } else {
4734
- const parsed = parseFontFilename(file.name);
4735
- basename = parsed.basename;
4736
- weight = parsed.weight;
4737
- style = parsed.style;
4740
+ await fs12.unlink(fullPath);
4738
4741
  }
4739
- const newFilename = generateFontFilename(basename, weight, style, ".ttf");
4740
- const familyDir = path11.join(fontsDir, basename);
4741
- await fs12.mkdir(familyDir, { recursive: true });
4742
- const filePath = path11.join(familyDir, newFilename);
4743
- await fs12.writeFile(filePath, buffer);
4744
- uploaded.push({
4745
- original: file.name,
4746
- saved: newFilename,
4747
- path: `_fonts/${basename}/${newFilename}`
4748
- });
4742
+ deleted.push(p);
4749
4743
  } catch (err) {
4750
- errors.push({
4751
- file: file.name,
4752
- error: err instanceof Error ? err.message : "Unknown error"
4753
- });
4744
+ errors.push(`Failed to delete ${p}: ${err instanceof Error ? err.message : "Unknown error"}`);
4754
4745
  }
4755
4746
  }
4756
4747
  return jsonResponse({
4757
4748
  success: true,
4758
- uploaded,
4749
+ deleted,
4759
4750
  errors: errors.length > 0 ? errors : void 0
4760
4751
  });
4761
4752
  } catch (error) {
4762
- console.error("Error uploading fonts:", error);
4763
- return jsonResponse({ error: "Failed to upload fonts" }, { status: 500 });
4753
+ console.error("Error deleting:", error);
4754
+ return jsonResponse({ error: "Failed to delete" }, { status: 500 });
4764
4755
  }
4765
4756
  }
4766
4757
 
@@ -4840,6 +4831,8 @@ async function startServer(options) {
4840
4831
  );
4841
4832
  app.get("/api/studio/fonts/list", wrapHandler(handleFontsList));
4842
4833
  app.post("/api/studio/fonts/upload", wrapRawHandler(handleFontsUpload));
4834
+ app.post("/api/studio/fonts/create-folder", wrapHandler(handleFontsCreateFolder));
4835
+ app.post("/api/studio/fonts/delete", wrapHandler(handleFontsDelete));
4843
4836
  app.post("/api/studio/upload", wrapRawHandler(handleUpload));
4844
4837
  app.post("/api/studio/create-folder", wrapHandler(handleCreateFolder));
4845
4838
  app.post("/api/studio/rename", wrapHandler(handleRename));