@leviosa-ai/detail-page-editor 0.8.0 → 0.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.
@@ -25,10 +25,12 @@ import { FONT_TAGS, type FontTag } from "../../lib/detail-page-canvas/font-tags"
25
25
 
26
26
  export function DetailPageFontPicker({
27
27
  value,
28
+ text,
28
29
  documentFamilies,
29
30
  onSelect,
30
31
  }: {
31
32
  value: string;
33
+ text: string;
32
34
  documentFamilies: string[];
33
35
  onSelect: (family: string) => Promise<void>;
34
36
  }) {
@@ -37,6 +39,7 @@ export function DetailPageFontPicker({
37
39
  const [loading, setLoading] = useState<string | null>(null);
38
40
  const [error, setError] = useState(false);
39
41
  const [tags, setTags] = useState<FontTag[]>([]);
42
+ const hasHangul = /\p{Script=Hangul}/u.test(text);
40
43
  /** 404 로 떨어진 미리보기. 한 번 떨어지면 다시 안 부른다. */
41
44
  const [previewsMissing, setPreviewsMissing] = useState<ReadonlySet<string>>(
42
45
  () => new Set(),
@@ -85,7 +88,7 @@ export function DetailPageFontPicker({
85
88
  key={`${font.source}:${font.id}`}
86
89
  value={`${font.label} ${font.labelEn} ${font.family}`}
87
90
  onSelect={() => void select(font.family)}
88
- disabled={loading !== null}
91
+ disabled={loading !== null || (hasHangul && font.latinOnly)}
89
92
  className="group min-h-[56px] gap-2 px-2 py-1"
90
93
  >
91
94
  <span className="flex h-11 min-w-0 flex-1 items-center overflow-hidden">
@@ -225,6 +228,11 @@ export function DetailPageFontPicker({
225
228
  ) : null}
226
229
  </CommandList>
227
230
  </Command>
231
+ {hasHangul ? (
232
+ <p className="border-t border-dpe-ink-100 px-3 py-2 text-xs text-dpe-ink-500">
233
+ {t("detailPage.properties.fontLatinOnlyUnavailable")}
234
+ </p>
235
+ ) : null}
228
236
  {error ? (
229
237
  <p role="alert" className="border-t border-dpe-danger-100 bg-dpe-danger-50 px-3 py-2 text-xs text-dpe-danger-600">
230
238
  {t("detailPage.properties.fontLoadFailed")}
@@ -1,5 +1,7 @@
1
1
  "use client";
2
2
 
3
+ import { detailPageEditorProfile } from "../../lib/detail-page/editor-profile";
4
+
3
5
  import { useCallback, useState, useSyncExternalStore } from "react";
4
6
  import { observer } from "./canvas-observer";
5
7
  import { useTranslation } from "react-i18next";
@@ -252,10 +254,18 @@ export const DetailPagePagesPanel = observer(function DetailPagePagesPanel({
252
254
  [s],
253
255
  );
254
256
 
255
- const totalHeight = Math.round(
256
- s.pages.reduce((acc, p) => acc + p.computedHeight, 0),
257
- );
257
+ // 판이 고정 크기면(캐러셀) 높이를 «더하지 않는다». 상세페이지는 세로로 이어진 한 장이라
258
+ // 합계가 곧 문서 높이지만, 캐러셀은 판이 따로따로라 합계가 아무 뜻이 없다 —
259
+ // 8판짜리가 1080×10800 으로 보였다.
260
+ const profile = detailPageEditorProfile();
258
261
  const width = Math.round(s.pages[0]?.computedWidth ?? 0);
262
+ const shownHeight = profile.page.fixed
263
+ ? Math.round(
264
+ typeof profile.page.height === "number"
265
+ ? profile.page.height
266
+ : s.pages[0]?.computedHeight ?? 0,
267
+ )
268
+ : Math.round(s.pages.reduce((acc, p) => acc + p.computedHeight, 0));
259
269
 
260
270
  return (
261
271
  <div className="flex h-full flex-col">
@@ -264,7 +274,7 @@ export const DetailPagePagesPanel = observer(function DetailPagePagesPanel({
264
274
  {t("detailPage.pages.totalPages", { count: s.pages.length })}
265
275
  </p>
266
276
  <p className="text-xs text-dpe-ink-400">
267
- {width} × {totalHeight} px
277
+ {width} × {shownHeight} px
268
278
  </p>
269
279
  </div>
270
280
 
@@ -790,6 +790,7 @@ const TextInspector = observer(function TextInspector({
790
790
  <div className="grid grid-cols-[1fr_84px] gap-2">
791
791
  <DetailPageFontPicker
792
792
  value={fontFamily}
793
+ text={str(ref.text)}
793
794
  documentFamilies={fonts}
794
795
  onSelect={applyFontFamily}
795
796
  />
package/i18n/en.json CHANGED
@@ -124,6 +124,7 @@
124
124
  "typeShape": "Shape",
125
125
  "selectionCount": "{{count}} selected",
126
126
  "fontLatinOnly": "Latin only",
127
+ "fontLatinOnlyUnavailable": "Latin-only fonts cannot be used for text containing Korean.",
127
128
  "fontLoading": "Loading font",
128
129
  "chooseFont": "Choose font",
129
130
  "fontTagFilter": "Font style",
package/i18n/ko.json CHANGED
@@ -124,6 +124,7 @@
124
124
  "typeShape": "도형",
125
125
  "selectionCount": "{{count}}개 선택",
126
126
  "fontLatinOnly": "영문 전용",
127
+ "fontLatinOnlyUnavailable": "한글이 포함된 글자에는 영문 전용 글꼴을 쓸 수 없어요.",
127
128
  "fontLoading": "글꼴 불러오는 중",
128
129
  "chooseFont": "글꼴 선택",
129
130
  "fontTagFilter": "글꼴 종류",
@@ -54,6 +54,7 @@ export function getClosestSupportedFontWeight(
54
54
 
55
55
  const loadedFontFaces = new Set<string>();
56
56
  const loadedFamilyWeights = new Set<string>();
57
+ const warnedFallbackFaces = new Set<string>();
57
58
  const stylesheetPromises = new Map<string, Promise<void>>();
58
59
  const FONT_LOAD_SAMPLE = "가나다라마바사아자차카타파하 ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789";
59
60
  /**
@@ -220,7 +221,12 @@ function loadFontFacesOnce(requests: FontFaceRequest[]): Promise<void> {
220
221
  const descriptor = `${request.weight} ${request.size}px "${request.family}"`;
221
222
  const faces = await document.fonts.load(descriptor, request.sample);
222
223
  if (faces.length === 0) {
223
- throw new FontLoadError(`No bundled font face matched: ${descriptor}`);
224
+ if (!warnedFallbackFaces.has(descriptor)) {
225
+ warnedFallbackFaces.add(descriptor);
226
+ // eslint-disable-next-line no-console
227
+ console.warn(`[leviosa-canvas] ${descriptor}: bundled font did not match; using browser fallback`);
228
+ }
229
+ return;
224
230
  }
225
231
  const unloadedFace = faces.find((face) => face.status !== "loaded");
226
232
  if (unloadedFace) {
@@ -65,7 +65,6 @@ function fromCatalog(font: (typeof DETAIL_PAGE_FONTS)[number]): EditorFont {
65
65
  previewSrc: `${editorAssetBase("detailFontPreviews")}/${font.id}.webp`,
66
66
  tags: toFontTags(font.tags),
67
67
  weights: getDetailPageFontWeights(font),
68
- latinOnly: font.latinOnly,
69
68
  licenseName: font.licenseName,
70
69
  licenseUrl: font.licenseUrl,
71
70
  licenseNoteKey: font.licenseNoteKey,
@@ -82,6 +81,7 @@ function fromBundle(font: (typeof ALL_FONT_OPTIONS)[number]): EditorFont {
82
81
  previewSrc: `${editorAssetBase("cardnewsFontPreviews")}/${font.id}.webp`,
83
82
  tags: bundleFontTags(font.id),
84
83
  weights: [...font.weights].sort((a, b) => a - b),
84
+ latinOnly: (font as { latinOnly?: boolean }).latinOnly,
85
85
  licenseName: font.license.name,
86
86
  licenseUrl: font.license.url,
87
87
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leviosa-ai/detail-page-editor",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Leviosa detail-page editor shell. Source-only: consumers compile it through Next transpilePackages. Every app coupling (sourcing server, brand store, cache keys, toast) arrives through the DetailPageHost interface, and every URL it fetches comes from configureDetailPageEditor.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -66,7 +66,7 @@
66
66
  "woff2-encoder": "2.0.0"
67
67
  },
68
68
  "peerDependencies": {
69
- "@leviosa-ai/canvas": "0.3.0",
69
+ "@leviosa-ai/canvas": "0.4.0",
70
70
  "@tanstack/react-query": "^5.100.3",
71
71
  "konva": "^10.0.0",
72
72
  "lucide-react": "^0.563.0 || ^1.0.0",