tfrere HF Staff commited on
Commit
d4a36db
·
1 Parent(s): 9ebbb3c

fix: resolve @custom-media queries in publisher CSS

Browse files

The publisher reads raw CSS source files that use @custom-media
(e.g. --bp-content-collapse). Browsers don't support this natively -
PostCSS resolves it in the Vite build but not for the publisher.

This broke ALL responsive breakpoints in published HTML: the grid
never collapsed, mobile TOC elements were unaffected by media queries,
and the layout was broken on narrow screens.

Added resolveCustomMedia() that extracts @custom-media declarations
and replaces their usage with the resolved values before injection.

Made-with: Cursor

Files changed (1) hide show
  1. backend/src/publisher/index.ts +47 -8
backend/src/publisher/index.ts CHANGED
@@ -41,6 +41,31 @@ export interface PublishCSS {
41
  * Reads the template CSS foundation + editor extensions.
42
  * Tries multiple paths: dev source, Docker copy, relative fallback.
43
  */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  function loadCSS(): PublishCSS {
45
  const candidates = [
46
  join(__dirname, "..", "..", "..", "frontend", "src", "styles"),
@@ -64,15 +89,29 @@ function loadCSS(): PublishCSS {
64
  .map((f) => readSafe(join(dir, "components", f)))
65
  .join("\n");
66
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  return {
68
- variables: readFileSync(varsPath, "utf-8"),
69
- reset: readSafe(join(dir, "_reset.css")),
70
- base: readFileSync(basePath, "utf-8"),
71
- layout: readSafe(join(dir, "_layout.css")),
72
- print: readSafe(join(dir, "_print.css")),
73
- editorTokens: readSafe(join(dir, "_editor-tokens.css")),
74
- article: readSafe(join(dir, "article.css")),
75
- components: componentsCss,
76
  };
77
  }
78
  }
 
41
  * Reads the template CSS foundation + editor extensions.
42
  * Tries multiple paths: dev source, Docker copy, relative fallback.
43
  */
44
+ /**
45
+ * Resolve @custom-media at-rules so the CSS works in browsers.
46
+ * PostCSS handles this in the Vite build, but the publisher reads raw source files.
47
+ */
48
+ function resolveCustomMedia(allCss: string): string {
49
+ const mediaMap = new Map<string, string>();
50
+
51
+ // Extract @custom-media declarations
52
+ const declRegex = /@custom-media\s+(--[\w-]+)\s+\(([^)]+)\)\s*;/g;
53
+ let match: RegExpExecArray | null;
54
+ while ((match = declRegex.exec(allCss)) !== null) {
55
+ mediaMap.set(match[1], `(${match[2]})`);
56
+ }
57
+
58
+ // Remove @custom-media declarations
59
+ let resolved = allCss.replace(/@custom-media\s+--[\w-]+\s+\([^)]+\)\s*;/g, "");
60
+
61
+ // Replace @media (--custom-name) with the resolved value
62
+ for (const [name, value] of mediaMap) {
63
+ resolved = resolved.replaceAll(`(${name})`, value);
64
+ }
65
+
66
+ return resolved;
67
+ }
68
+
69
  function loadCSS(): PublishCSS {
70
  const candidates = [
71
  join(__dirname, "..", "..", "..", "frontend", "src", "styles"),
 
89
  .map((f) => readSafe(join(dir, "components", f)))
90
  .join("\n");
91
 
92
+ // Read all CSS, resolve @custom-media, then split back
93
+ const variablesRaw = readFileSync(varsPath, "utf-8");
94
+ const resetRaw = readSafe(join(dir, "_reset.css"));
95
+ const baseRaw = readFileSync(basePath, "utf-8");
96
+ const layoutRaw = readSafe(join(dir, "_layout.css"));
97
+ const printRaw = readSafe(join(dir, "_print.css"));
98
+ const editorTokensRaw = readSafe(join(dir, "_editor-tokens.css"));
99
+ const articleRaw = readSafe(join(dir, "article.css"));
100
+
101
+ // @custom-media declarations live in _variables.css - resolve across all files
102
+ const allRaw = [variablesRaw, resetRaw, baseRaw, layoutRaw, printRaw, editorTokensRaw, articleRaw, componentsCss].join("\n/* __CSS_BOUNDARY__ */\n");
103
+ const resolved = resolveCustomMedia(allRaw);
104
+ const parts = resolved.split("/* __CSS_BOUNDARY__ */");
105
+
106
  return {
107
+ variables: parts[0]?.trim() ?? "",
108
+ reset: parts[1]?.trim() ?? "",
109
+ base: parts[2]?.trim() ?? "",
110
+ layout: parts[3]?.trim() ?? "",
111
+ print: parts[4]?.trim() ?? "",
112
+ editorTokens: parts[5]?.trim() ?? "",
113
+ article: parts[6]?.trim() ?? "",
114
+ components: parts[7]?.trim() ?? "",
115
  };
116
  }
117
  }