@limcpf/everything-is-a-markdown 0.6.3 → 0.6.5

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/README.ko.md CHANGED
@@ -193,7 +193,8 @@ flowchart LR
193
193
 
194
194
  문서 전환 시에도 해당 블록을 다시 렌더링합니다.
195
195
  Mermaid fence는 일반 코드 블록 UI와 분리된 전용 컨테이너(`.mermaid-block`)에서 렌더링되므로 코드 헤더/파일명/복사 버튼이 표시되지 않습니다.
196
- 렌더된 SVG는 데스크톱/모바일 모두에서 컨테이너 기준으로 중앙 정렬되고 폭이 자동 보정됩니다.
196
+ 렌더된 SVG는 데스크톱/모바일 모두에서 중앙 정렬되며 `min(100%, 720px)` 기준으로 자동 축소됩니다.
197
+ 본문 이미지도 동일한 폭 정책을 적용해 글 읽기 흐름을 유지합니다.
197
198
  설정에서 Mermaid를 비활성화하거나 CDN 로드가 실패하면, 같은 컨테이너 안에서 소스 코드 텍스트를 유지하고 하단에 경고 메시지를 표시합니다.
198
199
 
199
200
  `blog.config.ts`에서 설정:
package/README.md CHANGED
@@ -193,7 +193,8 @@ flowchart LR
193
193
 
194
194
  Rendering happens in the browser on first load and when navigating to another document.
195
195
  Mermaid fences are rendered inside a dedicated diagram container (`.mermaid-block`) instead of the regular code block UI, so they do not show code headers, filenames, or copy buttons.
196
- Rendered SVG output is centered and constrained to the container width on both desktop and mobile.
196
+ Rendered SVG output is centered and automatically constrained to `min(100%, 720px)` on both desktop and mobile.
197
+ Regular content images inside the viewer follow the same width policy to keep reading flow stable.
197
198
  If Mermaid is disabled in config or CDN loading fails, the source block is shown as-is in the same container and a warning message appears below it.
198
199
 
199
200
  Configuration options (`blog.config.ts`):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@limcpf/everything-is-a-markdown",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
4
4
  "license": "MIT",
5
5
  "private": false,
6
6
  "type": "module",
package/src/build.ts CHANGED
@@ -979,7 +979,7 @@ async function writeOutputIfChanged(
979
979
 
980
980
  const outputPath = path.join(context.outDir, relOutputPath);
981
981
  const unchanged = context.previousHashes[relOutputPath] === outputHash;
982
- if (unchanged) {
982
+ if (unchanged && (await Bun.file(outputPath).exists())) {
983
983
  return;
984
984
  }
985
985
 
@@ -66,6 +66,9 @@
66
66
  --mobile-toggle-fg: #ffffff;
67
67
  --content-heading-accent-soft: rgba(136, 57, 239, 0.32);
68
68
  --content-heading-subtle: var(--latte-subtext1);
69
+ --content-visual-max-width: 720px;
70
+ --mermaid-wide-max-width: 640px;
71
+ --mermaid-tall-max-height: 560px;
69
72
  --desktop-sidebar-default: 420px;
70
73
  --desktop-sidebar-min: 320px;
71
74
  --desktop-viewer-min: 680px;
@@ -1101,6 +1104,9 @@ body.mobile-toggle-left .mobile-menu-toggle {
1101
1104
  .viewer-content .mermaid-block {
1102
1105
  margin: 1.5rem 0;
1103
1106
  padding: 16px 18px;
1107
+ max-width: min(100%, var(--content-visual-max-width));
1108
+ margin-left: auto;
1109
+ margin-right: auto;
1104
1110
  border-radius: 12px;
1105
1111
  border: 1px solid var(--latte-surface0);
1106
1112
  background: linear-gradient(180deg, var(--latte-base), var(--latte-mantle));
@@ -1135,12 +1141,20 @@ body.mobile-toggle-left .mobile-menu-toggle {
1135
1141
 
1136
1142
  .viewer-content .mermaid-block pre.mermaid[data-mermaid-rendered="true"] svg {
1137
1143
  display: block;
1138
- width: 100%;
1139
- max-width: 100%;
1144
+ width: auto;
1145
+ max-width: min(100%, var(--content-visual-max-width));
1140
1146
  height: auto;
1141
1147
  margin: 0 auto;
1142
1148
  }
1143
1149
 
1150
+ .viewer-content .mermaid-block.is-wide pre.mermaid[data-mermaid-rendered="true"] svg {
1151
+ max-width: min(100%, var(--mermaid-wide-max-width));
1152
+ }
1153
+
1154
+ .viewer-content .mermaid-block.is-tall pre.mermaid[data-mermaid-rendered="true"] svg {
1155
+ max-height: min(var(--mermaid-tall-max-height), 68vh);
1156
+ }
1157
+
1144
1158
  .viewer-content .mermaid-block .mermaid-render-error {
1145
1159
  margin: 12px 0 0;
1146
1160
  padding: 10px 12px;
@@ -1200,8 +1214,10 @@ body.mobile-toggle-left .mobile-menu-toggle {
1200
1214
  /* Images */
1201
1215
  .viewer-content img {
1202
1216
  display: block;
1203
- max-width: 100%;
1217
+ width: auto;
1218
+ max-width: min(100%, var(--content-visual-max-width));
1204
1219
  height: auto;
1220
+ margin: 0 auto;
1205
1221
  border-radius: 8px;
1206
1222
  border: 1px solid var(--latte-surface0);
1207
1223
  }
@@ -13,6 +13,7 @@ const DESKTOP_SPLITTER_STEP = 24;
13
13
  const DEFAULT_BRANCH = "dev";
14
14
  const DEFAULT_SITE_TITLE = "File-System Blog";
15
15
  const BRANCH_KEY = "fsblog.branch";
16
+ const APP_READY_STATE_ATTR = "data-app-ready";
16
17
  const FOCUSABLE_SELECTOR =
17
18
  'a[href], button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
18
19
  const MERMAID_CDN = "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js";
@@ -21,6 +22,10 @@ const MERMAID_SELECTOR = "pre.mermaid";
21
22
  const MERMAID_ERROR_CLASS = "mermaid-render-error";
22
23
  const MERMAID_THEME_VALIDATION_RE = /^[a-zA-Z][a-zA-Z0-9._-]*$/;
23
24
  const MERMAID_URL_VALIDATION_RE = /^(https?:\/\/|\/|\.{1,2}\/)[^\s"'<>]+$/;
25
+ const MERMAID_WIDE_RATIO = 2.4;
26
+ const MERMAID_TALL_RATIO = 0.85;
27
+ const MERMAID_BLOCK_WIDE_CLASS = "is-wide";
28
+ const MERMAID_BLOCK_TALL_CLASS = "is-tall";
24
29
  const mermaidRuntime = {
25
30
  initialized: false,
26
31
  loadingPromise: null,
@@ -103,44 +108,52 @@ function showMermaidError(preview, message) {
103
108
  preview.parentElement.appendChild(createMermaidLoadError(message));
104
109
  }
105
110
 
106
- function parseSvgSize(value) {
107
- const normalized = typeof value === "string" ? value.trim() : "";
108
- if (!normalized) {
109
- return null;
110
- }
111
- if (normalized.endsWith("%")) {
112
- return null;
113
- }
114
-
115
- const match = normalized.match(/^(\d+(?:\.\d+)?)/);
116
- if (!match) {
117
- return null;
118
- }
119
-
120
- const parsed = Number(match[1]);
121
- if (!Number.isFinite(parsed) || parsed <= 0) {
122
- return null;
123
- }
124
-
125
- return parsed;
126
- }
127
-
128
111
  function normalizeRenderedMermaidSvg(block) {
129
112
  if (!(block instanceof HTMLElement)) {
130
113
  return;
131
114
  }
132
115
 
116
+ const container = block.parentElement instanceof HTMLElement ? block.parentElement : null;
133
117
  const svg = block.querySelector("svg");
134
118
  if (!(svg instanceof SVGElement)) {
135
119
  return;
136
120
  }
137
121
 
138
- const intrinsicWidth = parseSvgSize(svg.getAttribute("width"));
122
+ if (container) {
123
+ container.classList.remove(MERMAID_BLOCK_WIDE_CLASS, MERMAID_BLOCK_TALL_CLASS);
124
+ }
125
+
139
126
  svg.style.display = "block";
140
- svg.style.width = "100%";
127
+ svg.style.width = "auto";
141
128
  svg.style.height = "auto";
142
129
  svg.style.margin = "0 auto";
143
- svg.style.maxWidth = intrinsicWidth ? `${intrinsicWidth}px` : "100%";
130
+ svg.style.maxWidth = "min(100%, var(--content-visual-max-width, 720px))";
131
+ svg.style.removeProperty("max-height");
132
+
133
+ const viewBox = svg.viewBox?.baseVal;
134
+ const intrinsicWidth =
135
+ viewBox && Number.isFinite(viewBox.width) && viewBox.width > 0
136
+ ? viewBox.width
137
+ : Number.parseFloat(svg.getAttribute("width") ?? "");
138
+ const intrinsicHeight =
139
+ viewBox && Number.isFinite(viewBox.height) && viewBox.height > 0
140
+ ? viewBox.height
141
+ : Number.parseFloat(svg.getAttribute("height") ?? "");
142
+
143
+ if (!(intrinsicWidth > 0) || !(intrinsicHeight > 0)) {
144
+ return;
145
+ }
146
+
147
+ const aspectRatio = intrinsicWidth / intrinsicHeight;
148
+ if (container && aspectRatio >= MERMAID_WIDE_RATIO) {
149
+ container.classList.add(MERMAID_BLOCK_WIDE_CLASS);
150
+ svg.style.maxWidth = "min(100%, var(--mermaid-wide-max-width, 640px))";
151
+ }
152
+
153
+ if (container && aspectRatio <= MERMAID_TALL_RATIO) {
154
+ container.classList.add(MERMAID_BLOCK_TALL_CLASS);
155
+ svg.style.maxHeight = "min(var(--mermaid-tall-max-height, 560px), 68vh)";
156
+ }
144
157
  }
145
158
 
146
159
  function parseMermaidNodes() {
@@ -156,6 +169,7 @@ function resetMermaidNodes(nodes) {
156
169
  for (const node of nodes) {
157
170
  node.removeAttribute("data-mermaid-rendered");
158
171
  if (node.parentElement instanceof HTMLElement) {
172
+ node.parentElement.classList.remove(MERMAID_BLOCK_WIDE_CLASS, MERMAID_BLOCK_TALL_CLASS);
159
173
  removeMermaidErrorMessage(node.parentElement);
160
174
  }
161
175
  }
@@ -1101,7 +1115,16 @@ function renderBacklinks(doc, pathBase) {
1101
1115
  return html;
1102
1116
  }
1103
1117
 
1118
+ function setAppReadyState(state) {
1119
+ if (!(document.documentElement instanceof HTMLElement)) {
1120
+ return;
1121
+ }
1122
+ document.documentElement.setAttribute(APP_READY_STATE_ATTR, state);
1123
+ }
1124
+
1104
1125
  async function start() {
1126
+ setAppReadyState("booting");
1127
+
1105
1128
  const treeRoot = document.getElementById("tree-root");
1106
1129
  const appRoot = document.querySelector(".app-root");
1107
1130
  const splitter = document.getElementById("app-splitter");
@@ -1922,6 +1945,7 @@ async function start() {
1922
1945
  const initialRoute = currentRoute === "/" ? pickHomeRoute(view) : currentRoute;
1923
1946
  handleLayoutChange();
1924
1947
  await state.navigate(initialRoute, currentRoute === "/" && initialRoute !== "/");
1948
+ setAppReadyState("ready");
1925
1949
 
1926
1950
  window.addEventListener("popstate", async () => {
1927
1951
  await state.navigate(resolveRouteFromLocation(view.routeMap, pathBase), false);
@@ -1929,6 +1953,8 @@ async function start() {
1929
1953
  }
1930
1954
 
1931
1955
  start().catch((error) => {
1956
+ setAppReadyState("error");
1957
+
1932
1958
  const contentEl = document.getElementById("viewer-content");
1933
1959
  if (contentEl) {
1934
1960
  const message = error instanceof Error ? error.message : String(error);