@gallop.software/studio 2.3.134 → 2.3.136

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-D6z5xlZG.js"></script>
14
+ <script type="module" crossorigin src="/assets/index-8i66yX4y.js"></script>
15
15
  <link rel="stylesheet" crossorigin href="/assets/index-DfPQBmNf.css">
16
16
  </head>
17
17
  <body>
@@ -3967,8 +3967,8 @@ import sharp4 from "sharp";
3967
3967
  function parseImageUrl(url) {
3968
3968
  const parsed = new URL(url);
3969
3969
  const base = `${parsed.protocol}//${parsed.host}`;
3970
- const path11 = parsed.pathname;
3971
- return { base, path: path11 };
3970
+ const path12 = parsed.pathname;
3971
+ return { base, path: path12 };
3972
3972
  }
3973
3973
  async function processRemoteImage(url) {
3974
3974
  const response = await fetch(url);
@@ -4029,10 +4029,10 @@ async function handleImportUrls(request) {
4029
4029
  const url = urls[i].trim();
4030
4030
  if (!url) continue;
4031
4031
  try {
4032
- const { base, path: path11 } = parseImageUrl(url);
4033
- const existingEntry = getMetaEntry(meta, path11);
4032
+ const { base, path: path12 } = parseImageUrl(url);
4033
+ const existingEntry = getMetaEntry(meta, path12);
4034
4034
  if (existingEntry) {
4035
- skipped.push(path11);
4035
+ skipped.push(path12);
4036
4036
  sendEvent({
4037
4037
  type: "progress",
4038
4038
  current: i + 1,
@@ -4045,13 +4045,13 @@ async function handleImportUrls(request) {
4045
4045
  }
4046
4046
  const cdnIndex = getOrAddCdnIndex(meta, base);
4047
4047
  const imageData = await processRemoteImage(url);
4048
- setMetaEntry(meta, path11, {
4048
+ setMetaEntry(meta, path12, {
4049
4049
  o: imageData.o,
4050
4050
  b: imageData.b,
4051
4051
  c: cdnIndex
4052
4052
  });
4053
4053
  await saveMeta(meta);
4054
- added.push(path11);
4054
+ added.push(path12);
4055
4055
  sendEvent({
4056
4056
  type: "progress",
4057
4057
  current: i + 1,
@@ -4572,6 +4572,198 @@ async function handleEditImage(request) {
4572
4572
  }
4573
4573
  }
4574
4574
 
4575
+ // src/handlers/fonts.ts
4576
+ import { promises as fs12 } from "fs";
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;
4612
+ }
4613
+ }
4614
+ if (!isValid) {
4615
+ if (weightPart === "" || weightPart === "regular" || weightPart === "normal") {
4616
+ weight = "regular";
4617
+ isValid = true;
4618
+ }
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 = [];
4633
+ 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
+ }
4669
+ }
4670
+ } catch {
4671
+ }
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
4688
+ });
4689
+ }
4690
+ }
4691
+ } catch {
4692
+ }
4693
+ families.sort((a, b) => a.name.localeCompare(b.name));
4694
+ return jsonResponse({ families, configs });
4695
+ } catch (error) {
4696
+ console.error("Error listing fonts:", error);
4697
+ return jsonResponse({ error: "Failed to list fonts" }, { status: 500 });
4698
+ }
4699
+ }
4700
+ async function handleFontsUpload(request) {
4701
+ try {
4702
+ 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 });
4707
+ }
4708
+ let renames = {};
4709
+ if (renamesJson) {
4710
+ try {
4711
+ renames = JSON.parse(renamesJson);
4712
+ } catch {
4713
+ }
4714
+ }
4715
+ const fontsDir = getWorkspacePath("_fonts");
4716
+ const uploaded = [];
4717
+ const errors = [];
4718
+ for (const file of files) {
4719
+ 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();
4733
+ } else {
4734
+ const parsed = parseFontFilename(file.name);
4735
+ basename = parsed.basename;
4736
+ weight = parsed.weight;
4737
+ style = parsed.style;
4738
+ }
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
+ });
4749
+ } catch (err) {
4750
+ errors.push({
4751
+ file: file.name,
4752
+ error: err instanceof Error ? err.message : "Unknown error"
4753
+ });
4754
+ }
4755
+ }
4756
+ return jsonResponse({
4757
+ success: true,
4758
+ uploaded,
4759
+ errors: errors.length > 0 ? errors : void 0
4760
+ });
4761
+ } catch (error) {
4762
+ console.error("Error uploading fonts:", error);
4763
+ return jsonResponse({ error: "Failed to upload fonts" }, { status: 500 });
4764
+ }
4765
+ }
4766
+
4575
4767
  // src/server/index.ts
4576
4768
  var __filename = fileURLToPath(import.meta.url);
4577
4769
  var __dirname = dirname(__filename);
@@ -4617,15 +4809,16 @@ async function startServer(options) {
4617
4809
  if (!process.env.STUDIO_DEV_SITE_URL && process.env.NEXT_PUBLIC_PRODUCTION_URL) {
4618
4810
  process.env.STUDIO_DEV_SITE_URL = process.env.NEXT_PUBLIC_PRODUCTION_URL;
4619
4811
  }
4812
+ const rawBodyPaths = ["/api/studio/upload", "/api/studio/fonts/upload"];
4620
4813
  app.use((req, res, next) => {
4621
- if (req.path === "/api/studio/upload") {
4814
+ if (rawBodyPaths.includes(req.path)) {
4622
4815
  next();
4623
4816
  } else {
4624
4817
  express.json({ limit: "50mb" })(req, res, next);
4625
4818
  }
4626
4819
  });
4627
4820
  app.use((req, res, next) => {
4628
- if (req.path === "/api/studio/upload") {
4821
+ if (rawBodyPaths.includes(req.path)) {
4629
4822
  next();
4630
4823
  } else {
4631
4824
  express.urlencoded({ extended: true, limit: "50mb" })(req, res, next);
@@ -4645,6 +4838,8 @@ async function startServer(options) {
4645
4838
  "/api/studio/featured-image-options",
4646
4839
  wrapHandler(handleGetFeaturedImageOptions)
4647
4840
  );
4841
+ app.get("/api/studio/fonts/list", wrapHandler(handleFontsList));
4842
+ app.post("/api/studio/fonts/upload", wrapRawHandler(handleFontsUpload));
4648
4843
  app.post("/api/studio/upload", wrapRawHandler(handleUpload));
4649
4844
  app.post("/api/studio/create-folder", wrapHandler(handleCreateFolder));
4650
4845
  app.post("/api/studio/rename", wrapHandler(handleRename));