@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.
- package/components/detail-page/detail-page-font-picker.tsx +9 -1
- package/components/detail-page/detail-page-pages-panel.tsx +14 -4
- package/components/detail-page/detail-page-properties-panel.tsx +1 -0
- package/i18n/en.json +1 -0
- package/i18n/ko.json +1 -0
- package/lib/cardnews/font-loader.ts +7 -1
- package/lib/detail-page-canvas/editor-fonts.ts +1 -1
- package/package.json +2 -2
|
@@ -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
|
-
|
|
256
|
-
|
|
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} × {
|
|
277
|
+
{width} × {shownHeight} px
|
|
268
278
|
</p>
|
|
269
279
|
</div>
|
|
270
280
|
|
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
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
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",
|