@libraz/mejiro-react 0.5.0 → 0.7.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/README.md +11 -0
- package/dist/index.d.ts +350 -373
- package/dist/index.js +853 -721
- package/package.json +7 -6
package/dist/index.js
CHANGED
|
@@ -1,134 +1,13 @@
|
|
|
1
1
|
// src/i18n.tsx
|
|
2
|
+
import {
|
|
3
|
+
enMessages,
|
|
4
|
+
formatMessage,
|
|
5
|
+
messageCatalogs,
|
|
6
|
+
resolveMessages as resolveCoreMessages
|
|
7
|
+
} from "@libraz/mejiro";
|
|
2
8
|
import { createContext, useContext, useMemo } from "react";
|
|
9
|
+
import { enMessages as enMessages2, jaMessages } from "@libraz/mejiro";
|
|
3
10
|
import { jsx } from "react/jsx-runtime";
|
|
4
|
-
var enMessages = {
|
|
5
|
-
logoSubtitle: "Vertical Reader",
|
|
6
|
-
openButton: "Open",
|
|
7
|
-
settingsButton: "Settings",
|
|
8
|
-
imageButton: "Image",
|
|
9
|
-
imageRemoveButton: "Remove image",
|
|
10
|
-
loading: "Loading...",
|
|
11
|
-
prevSpread: "Previous spread",
|
|
12
|
-
nextSpread: "Next spread",
|
|
13
|
-
settingsFont: "Font",
|
|
14
|
-
settingsSize: "Size",
|
|
15
|
-
settingsSizeDown: "Decrease font size",
|
|
16
|
-
settingsSizeUp: "Increase font size",
|
|
17
|
-
settingsLayout: "Layout",
|
|
18
|
-
settingsKinsoku: "Kinsoku",
|
|
19
|
-
settingsHanging: "Hanging",
|
|
20
|
-
settingsLineSpacing: "Line spacing",
|
|
21
|
-
settingsStrict: "Strict",
|
|
22
|
-
settingsLoose: "Loose",
|
|
23
|
-
toggleOn: "On",
|
|
24
|
-
toggleOff: "Off",
|
|
25
|
-
dropZoneTitle: "Drop an EPUB here or click to open",
|
|
26
|
-
dropZoneHint: ".epub files only",
|
|
27
|
-
tocTitle: "Contents",
|
|
28
|
-
tocSearchPlaceholder: "Search chapters\u2026",
|
|
29
|
-
tocEmpty: 'No chapters match "{query}".',
|
|
30
|
-
shelfTitle: "Library",
|
|
31
|
-
chapterN: "Chapter {n}",
|
|
32
|
-
spreadAnnouncement: "Spread {spread} of {total}",
|
|
33
|
-
editorPreviewSubtitle: "Editor Preview",
|
|
34
|
-
editorTitle: "EPUB Editor",
|
|
35
|
-
editorNoBookLoaded: "No book loaded",
|
|
36
|
-
editorParagraphs: "Paragraphs",
|
|
37
|
-
editorProofread: "Proofread",
|
|
38
|
-
editorApplyText: "Apply text",
|
|
39
|
-
editorRuby: "Ruby",
|
|
40
|
-
editorRubyHint: "Select base text in the textarea, then type the ruby reading below.",
|
|
41
|
-
editorRubyRange: "Base: {start}-{end} ({count} chars)",
|
|
42
|
-
editorRubyPlaceholder: "furigana",
|
|
43
|
-
editorApplyRuby: "Apply ruby",
|
|
44
|
-
editorImages: "Images",
|
|
45
|
-
editorInsertImageAfterParagraph: "Insert image after paragraph",
|
|
46
|
-
editorExportEpub: "Export EPUB",
|
|
47
|
-
manuscriptPreviewSubtitle: "Manuscript Preview",
|
|
48
|
-
manuscriptTitle: "Manuscript EPUB",
|
|
49
|
-
manuscriptRubyHint: "Use \uFF5C\u6F22\u5B57\u300A\u304B\u3093\u3058\u300B or \u6F22\u5B57\u300A\u304B\u3093\u3058\u300B for ruby.",
|
|
50
|
-
manuscriptMetadata: "Metadata",
|
|
51
|
-
manuscriptChapters: "Chapters",
|
|
52
|
-
manuscriptDraft: "Draft",
|
|
53
|
-
manuscriptChooseCoverImage: "Choose cover image",
|
|
54
|
-
manuscriptDefaultTitle: "New work",
|
|
55
|
-
manuscriptDefaultChapterTitle: "Chapter {n}",
|
|
56
|
-
manuscriptDefaultBody: "This is a ruby example: \uFF5Ckanji\u300Areading\u300B.\n\nPaste your draft here.",
|
|
57
|
-
untitled: "Untitled",
|
|
58
|
-
manuscriptAddChapter: "Add chapter",
|
|
59
|
-
manuscriptRemove: "Remove",
|
|
60
|
-
manuscriptEmphasisDots: "Emphasis dots",
|
|
61
|
-
manuscriptTcy: "Tate-chu-yoko",
|
|
62
|
-
manuscriptEm: "Em",
|
|
63
|
-
manuscriptStrong: "Strong",
|
|
64
|
-
manuscriptReorderHandle: "Reorder {title}"
|
|
65
|
-
};
|
|
66
|
-
var jaMessages = {
|
|
67
|
-
logoSubtitle: "\u7E26\u66F8\u304D\u30EA\u30FC\u30C0\u30FC",
|
|
68
|
-
openButton: "\u958B\u304F",
|
|
69
|
-
settingsButton: "\u8A2D\u5B9A",
|
|
70
|
-
imageButton: "\u753B\u50CF",
|
|
71
|
-
imageRemoveButton: "\u753B\u50CF\u3092\u524A\u9664",
|
|
72
|
-
loading: "\u8AAD\u307F\u8FBC\u307F\u4E2D\u2026",
|
|
73
|
-
prevSpread: "\u524D\u306E\u898B\u958B\u304D",
|
|
74
|
-
nextSpread: "\u6B21\u306E\u898B\u958B\u304D",
|
|
75
|
-
settingsFont: "\u30D5\u30A9\u30F3\u30C8",
|
|
76
|
-
settingsSize: "\u30B5\u30A4\u30BA",
|
|
77
|
-
settingsSizeDown: "\u30D5\u30A9\u30F3\u30C8\u30B5\u30A4\u30BA\u3092\u4E0B\u3052\u308B",
|
|
78
|
-
settingsSizeUp: "\u30D5\u30A9\u30F3\u30C8\u30B5\u30A4\u30BA\u3092\u4E0A\u3052\u308B",
|
|
79
|
-
settingsLayout: "\u30EC\u30A4\u30A2\u30A6\u30C8",
|
|
80
|
-
settingsKinsoku: "\u7981\u5247",
|
|
81
|
-
settingsHanging: "\u3076\u3089\u4E0B\u3052",
|
|
82
|
-
settingsLineSpacing: "\u884C\u9593",
|
|
83
|
-
settingsStrict: "\u53B3\u683C",
|
|
84
|
-
settingsLoose: "\u7DE9\u548C",
|
|
85
|
-
toggleOn: "\u30AA\u30F3",
|
|
86
|
-
toggleOff: "\u30AA\u30D5",
|
|
87
|
-
dropZoneTitle: "EPUB \u3092\u30C9\u30ED\u30C3\u30D7\u307E\u305F\u306F\u30AF\u30EA\u30C3\u30AF\u3067\u958B\u304F",
|
|
88
|
-
dropZoneHint: ".epub \u30D5\u30A1\u30A4\u30EB\u306E\u307F",
|
|
89
|
-
tocTitle: "\u76EE\u6B21",
|
|
90
|
-
tocSearchPlaceholder: "\u7AE0\u3092\u691C\u7D22\u2026",
|
|
91
|
-
tocEmpty: "\u300C{query}\u300D\u306B\u4E00\u81F4\u3059\u308B\u7AE0\u306F\u3042\u308A\u307E\u305B\u3093\u3002",
|
|
92
|
-
shelfTitle: "\u672C\u68DA",
|
|
93
|
-
chapterN: "\u7B2C{n}\u7AE0",
|
|
94
|
-
spreadAnnouncement: "{total}\u898B\u958B\u304D\u4E2D{spread}\u898B\u958B\u304D\u76EE",
|
|
95
|
-
editorPreviewSubtitle: "\u7DE8\u96C6\u30D7\u30EC\u30D3\u30E5\u30FC",
|
|
96
|
-
editorTitle: "EPUB \u30A8\u30C7\u30A3\u30BF\u30FC",
|
|
97
|
-
editorNoBookLoaded: "\u672C\u304C\u8AAD\u307F\u8FBC\u307E\u308C\u3066\u3044\u307E\u305B\u3093",
|
|
98
|
-
editorParagraphs: "\u6BB5\u843D",
|
|
99
|
-
editorProofread: "\u6821\u6B63",
|
|
100
|
-
editorApplyText: "\u672C\u6587\u3092\u9069\u7528",
|
|
101
|
-
editorRuby: "\u30EB\u30D3",
|
|
102
|
-
editorRubyHint: "\u30C6\u30AD\u30B9\u30C8\u30A8\u30EA\u30A2\u3067\u89AA\u6587\u5B57\u3092\u9078\u629E\u3057\u3066\u304B\u3089\u3001\u4E0B\u306B\u30EB\u30D3\u3092\u5165\u529B\u3057\u307E\u3059\u3002",
|
|
103
|
-
editorRubyRange: "\u89AA\u6587\u5B57: {start}-{end}\uFF08{count}\u6587\u5B57\uFF09",
|
|
104
|
-
editorRubyPlaceholder: "\u3075\u308A\u304C\u306A",
|
|
105
|
-
editorApplyRuby: "\u30EB\u30D3\u3092\u9069\u7528",
|
|
106
|
-
editorImages: "\u753B\u50CF",
|
|
107
|
-
editorInsertImageAfterParagraph: "\u6BB5\u843D\u306E\u5F8C\u306B\u753B\u50CF\u3092\u633F\u5165",
|
|
108
|
-
editorExportEpub: "EPUB \u3092\u66F8\u304D\u51FA\u3059",
|
|
109
|
-
manuscriptPreviewSubtitle: "\u539F\u7A3F\u30D7\u30EC\u30D3\u30E5\u30FC",
|
|
110
|
-
manuscriptTitle: "\u539F\u7A3F EPUB",
|
|
111
|
-
manuscriptRubyHint: "\u30EB\u30D3\u306B\u306F \uFF5C\u6F22\u5B57\u300A\u304B\u3093\u3058\u300B \u307E\u305F\u306F \u6F22\u5B57\u300A\u304B\u3093\u3058\u300B \u3092\u4F7F\u3044\u307E\u3059\u3002",
|
|
112
|
-
manuscriptMetadata: "\u30E1\u30BF\u30C7\u30FC\u30BF",
|
|
113
|
-
manuscriptChapters: "\u7AE0",
|
|
114
|
-
manuscriptDraft: "\u539F\u7A3F",
|
|
115
|
-
manuscriptChooseCoverImage: "\u8868\u7D19\u753B\u50CF\u3092\u9078\u629E",
|
|
116
|
-
manuscriptDefaultTitle: "\u65B0\u3057\u3044\u4F5C\u54C1",
|
|
117
|
-
manuscriptDefaultChapterTitle: "\u7B2C{n}\u8A71",
|
|
118
|
-
manuscriptDefaultBody: "\u3053\u308C\u306F\uFF5C\u6F22\u5B57\u300A\u304B\u3093\u3058\u300B\u306E\u30EB\u30D3\u4F8B\u3067\u3059\u3002\n\n\u672C\u6587\u3092\u3053\u3053\u306B\u8CBC\u308A\u4ED8\u3051\u307E\u3059\u3002",
|
|
119
|
-
untitled: "\u7121\u984C",
|
|
120
|
-
manuscriptAddChapter: "\u7AE0\u3092\u8FFD\u52A0",
|
|
121
|
-
manuscriptRemove: "\u524A\u9664",
|
|
122
|
-
manuscriptEmphasisDots: "\u508D\u70B9",
|
|
123
|
-
manuscriptTcy: "\u7E26\u4E2D\u6A2A",
|
|
124
|
-
manuscriptEm: "\u5F37\u8ABF",
|
|
125
|
-
manuscriptStrong: "\u592A\u5B57",
|
|
126
|
-
manuscriptReorderHandle: "{title} \u306E\u9806\u756A\u3092\u5909\u66F4"
|
|
127
|
-
};
|
|
128
|
-
var CATALOGS = {
|
|
129
|
-
en: enMessages,
|
|
130
|
-
ja: jaMessages
|
|
131
|
-
};
|
|
132
11
|
var I18nContext = createContext(enMessages);
|
|
133
12
|
function MejiroI18nProvider({
|
|
134
13
|
locale,
|
|
@@ -136,10 +15,10 @@ function MejiroI18nProvider({
|
|
|
136
15
|
children
|
|
137
16
|
}) {
|
|
138
17
|
const fromContext = useContext(I18nContext);
|
|
139
|
-
const value = useMemo(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
18
|
+
const value = useMemo(
|
|
19
|
+
() => resolveCoreMessages(locale, messages, locale != null ? enMessages : fromContext),
|
|
20
|
+
[locale, messages, fromContext]
|
|
21
|
+
);
|
|
143
22
|
return /* @__PURE__ */ jsx(I18nContext.Provider, { value, children });
|
|
144
23
|
}
|
|
145
24
|
function useI18n(options = {}) {
|
|
@@ -147,19 +26,14 @@ function useI18n(options = {}) {
|
|
|
147
26
|
const { locale, messages } = options;
|
|
148
27
|
return useMemo(() => {
|
|
149
28
|
if (locale == null && messages == null) return fromContext;
|
|
150
|
-
|
|
151
|
-
return messages ? { ...base, ...messages } : base;
|
|
29
|
+
return resolveCoreMessages(locale, messages, fromContext);
|
|
152
30
|
}, [locale, messages, fromContext]);
|
|
153
31
|
}
|
|
154
32
|
function resolveMessages(locale, overrides) {
|
|
155
|
-
|
|
156
|
-
return overrides ? { ...base, ...overrides } : base;
|
|
33
|
+
return resolveCoreMessages(locale, overrides);
|
|
157
34
|
}
|
|
158
35
|
function format(template, vars) {
|
|
159
|
-
return template
|
|
160
|
-
/\{(\w+)\}/g,
|
|
161
|
-
(_, k) => Object.hasOwn(vars, k) ? String(vars[k]) : `{${k}}`
|
|
162
|
-
);
|
|
36
|
+
return formatMessage(template, vars);
|
|
163
37
|
}
|
|
164
38
|
|
|
165
39
|
// src/MejiroChapterNav.tsx
|
|
@@ -208,7 +82,10 @@ function MejiroChapterNav({
|
|
|
208
82
|
}) })
|
|
209
83
|
] });
|
|
210
84
|
}
|
|
211
|
-
return /* @__PURE__ */ jsx2("div", { className: "mejiro-reader-chapter-nav", children: /* @__PURE__ */ jsx2("select", { value: chapter, onChange: (e) => onChange(Number(e.target.value)), children: epub.chapters.map((ch, i) =>
|
|
85
|
+
return /* @__PURE__ */ jsx2("div", { className: "mejiro-reader-chapter-nav", children: /* @__PURE__ */ jsx2("select", { value: chapter, onChange: (e) => onChange(Number(e.target.value)), children: epub.chapters.map((ch, i) => (
|
|
86
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: chapter titles are not guaranteed unique
|
|
87
|
+
/* @__PURE__ */ jsx2("option", { value: i, children: ch.title ?? format(messages.chapterN, { n: i + 1 }) }, i)
|
|
88
|
+
)) }) });
|
|
212
89
|
}
|
|
213
90
|
|
|
214
91
|
// src/MejiroDropZone.tsx
|
|
@@ -274,10 +151,10 @@ function MejiroDropZone({
|
|
|
274
151
|
|
|
275
152
|
// src/MejiroEditor.tsx
|
|
276
153
|
import { EditableEpub } from "@libraz/mejiro/epub";
|
|
277
|
-
import { useCallback as useCallback7, useEffect as useEffect7, useRef as useRef10, useState as useState8 } from "react";
|
|
154
|
+
import { useCallback as useCallback7, useEffect as useEffect7, useMemo as useMemo5, useRef as useRef10, useState as useState8 } from "react";
|
|
278
155
|
|
|
279
156
|
// src/MejiroReader.tsx
|
|
280
|
-
import { DEFAULT_BOOK_OPTIONS } from "@libraz/mejiro/book";
|
|
157
|
+
import { DEFAULT_BOOK_OPTIONS, DEFAULT_PAGE_GEOMETRY } from "@libraz/mejiro/book";
|
|
281
158
|
import { normalizeFontFamily as normalizeFontFamily5 } from "@libraz/mejiro/browser";
|
|
282
159
|
import { manuscriptToEpubBook } from "@libraz/mejiro/epub";
|
|
283
160
|
import {
|
|
@@ -285,6 +162,7 @@ import {
|
|
|
285
162
|
useCallback as useCallback6,
|
|
286
163
|
useEffect as useEffect6,
|
|
287
164
|
useImperativeHandle,
|
|
165
|
+
useLayoutEffect as useLayoutEffect4,
|
|
288
166
|
useMemo as useMemo4,
|
|
289
167
|
useRef as useRef9,
|
|
290
168
|
useState as useState7
|
|
@@ -311,38 +189,36 @@ import {
|
|
|
311
189
|
|
|
312
190
|
// src/MejiroPageView.tsx
|
|
313
191
|
import { normalizeFontFamily } from "@libraz/mejiro/browser";
|
|
314
|
-
import { Fragment as Fragment3 } from "react";
|
|
315
192
|
|
|
316
|
-
// src/
|
|
317
|
-
import {
|
|
318
|
-
|
|
193
|
+
// src/renderInlineNode.tsx
|
|
194
|
+
import {
|
|
195
|
+
segmentToInlineNode
|
|
196
|
+
} from "@libraz/mejiro/render";
|
|
197
|
+
import { createElement, Fragment as Fragment2 } from "react";
|
|
198
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
319
199
|
function renderSegment(segment, key) {
|
|
320
|
-
|
|
321
|
-
case "text":
|
|
322
|
-
return /* @__PURE__ */ jsx4(Fragment2, { children: segment.text }, key);
|
|
323
|
-
case "ruby":
|
|
324
|
-
return /* @__PURE__ */ jsxs4("ruby", { children: [
|
|
325
|
-
segment.base,
|
|
326
|
-
/* @__PURE__ */ jsx4("rt", { children: segment.rubyText })
|
|
327
|
-
] }, key);
|
|
328
|
-
case "emphasis":
|
|
329
|
-
return /* @__PURE__ */ jsx4("span", { className: `mejiro-emphasis mejiro-emphasis--${segment.style}`, children: segment.text }, key);
|
|
330
|
-
case "tcy":
|
|
331
|
-
return /* @__PURE__ */ jsx4("span", { className: "mejiro-tcy", children: segment.text }, key);
|
|
332
|
-
case "em":
|
|
333
|
-
return /* @__PURE__ */ jsx4("em", { children: segment.text }, key);
|
|
334
|
-
case "strong":
|
|
335
|
-
return /* @__PURE__ */ jsx4("strong", { children: segment.text }, key);
|
|
336
|
-
case "link":
|
|
337
|
-
return /* @__PURE__ */ jsx4("a", { href: segment.href, title: segment.title, children: segment.text }, key);
|
|
338
|
-
case "footnote-ref":
|
|
339
|
-
return /* @__PURE__ */ jsx4("a", { className: "mejiro-footnote-ref", href: `#${segment.noteId}`, children: segment.text }, key);
|
|
340
|
-
}
|
|
200
|
+
return renderInlineNode(segmentToInlineNode(segment), key);
|
|
341
201
|
}
|
|
202
|
+
function renderInlineNode(node, key) {
|
|
203
|
+
if (node.type === "text") return /* @__PURE__ */ jsx4(Fragment2, { children: node.text }, key);
|
|
204
|
+
return createElement(
|
|
205
|
+
node.tag,
|
|
206
|
+
{
|
|
207
|
+
key,
|
|
208
|
+
className: node.className,
|
|
209
|
+
href: node.href,
|
|
210
|
+
title: node.title
|
|
211
|
+
},
|
|
212
|
+
node.children.map((child, index) => renderInlineNode(child, `${key}-${index}`))
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/MejiroPage.tsx
|
|
217
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
342
218
|
function renderLine(line, lineIndex) {
|
|
343
219
|
const nodes = [];
|
|
344
220
|
if (lineIndex > 0) {
|
|
345
|
-
nodes.push(/* @__PURE__ */
|
|
221
|
+
nodes.push(/* @__PURE__ */ jsx5("br", {}, `br-${lineIndex}`));
|
|
346
222
|
}
|
|
347
223
|
for (let i = 0; i < line.segments.length; i++) {
|
|
348
224
|
nodes.push(renderSegment(line.segments[i], `${lineIndex}-${i}`));
|
|
@@ -351,7 +227,7 @@ function renderLine(line, lineIndex) {
|
|
|
351
227
|
}
|
|
352
228
|
function MejiroPage({ page, className, style }) {
|
|
353
229
|
const rootClass = className ? `mejiro-page ${className}` : "mejiro-page";
|
|
354
|
-
return /* @__PURE__ */
|
|
230
|
+
return /* @__PURE__ */ jsx5("div", { className: rootClass, style, children: page.paragraphs.map((paragraph, pi) => {
|
|
355
231
|
let paraClass = "mejiro-paragraph";
|
|
356
232
|
if (paragraph.headingLevel != null) {
|
|
357
233
|
paraClass += ` mejiro-paragraph--h${paragraph.headingLevel}`;
|
|
@@ -360,36 +236,13 @@ function MejiroPage({ page, className, style }) {
|
|
|
360
236
|
}
|
|
361
237
|
return (
|
|
362
238
|
// biome-ignore lint/suspicious/noArrayIndexKey: paragraphs have no stable ID
|
|
363
|
-
/* @__PURE__ */
|
|
239
|
+
/* @__PURE__ */ jsx5("div", { className: paraClass, children: paragraph.lines.flatMap((line, li) => renderLine(line, li)) }, pi)
|
|
364
240
|
);
|
|
365
241
|
}) });
|
|
366
242
|
}
|
|
367
243
|
|
|
368
244
|
// src/MejiroPageView.tsx
|
|
369
|
-
import { jsx as
|
|
370
|
-
function renderSlotSegment(segment, key) {
|
|
371
|
-
switch (segment.type) {
|
|
372
|
-
case "text":
|
|
373
|
-
return /* @__PURE__ */ jsx5(Fragment3, { children: segment.text }, key);
|
|
374
|
-
case "ruby":
|
|
375
|
-
return /* @__PURE__ */ jsxs5("ruby", { children: [
|
|
376
|
-
segment.base,
|
|
377
|
-
/* @__PURE__ */ jsx5("rt", { children: segment.rubyText })
|
|
378
|
-
] }, key);
|
|
379
|
-
case "emphasis":
|
|
380
|
-
return /* @__PURE__ */ jsx5("span", { className: `mejiro-emphasis mejiro-emphasis--${segment.style}`, children: segment.text }, key);
|
|
381
|
-
case "tcy":
|
|
382
|
-
return /* @__PURE__ */ jsx5("span", { className: "mejiro-tcy", children: segment.text }, key);
|
|
383
|
-
case "em":
|
|
384
|
-
return /* @__PURE__ */ jsx5("em", { children: segment.text }, key);
|
|
385
|
-
case "strong":
|
|
386
|
-
return /* @__PURE__ */ jsx5("strong", { children: segment.text }, key);
|
|
387
|
-
case "link":
|
|
388
|
-
return /* @__PURE__ */ jsx5("a", { href: segment.href, title: segment.title, children: segment.text }, key);
|
|
389
|
-
case "footnote-ref":
|
|
390
|
-
return /* @__PURE__ */ jsx5("a", { className: "mejiro-footnote-ref", href: `#${segment.noteId}`, children: segment.text }, key);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
245
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
393
246
|
function MejiroPageView({
|
|
394
247
|
result,
|
|
395
248
|
fontFamily,
|
|
@@ -401,7 +254,7 @@ function MejiroPageView({
|
|
|
401
254
|
if (result.hasImages || slotMode) {
|
|
402
255
|
const rootClass = className ? `mejiro-page-slots ${className}` : "mejiro-page-slots";
|
|
403
256
|
const fontFamilyCss = fontFamily != null ? normalizeFontFamily(fontFamily) : void 0;
|
|
404
|
-
return /* @__PURE__ */
|
|
257
|
+
return /* @__PURE__ */ jsx6("div", { className: rootClass, style: { position: "relative", ...style }, children: result.lines.map((line, i) => {
|
|
405
258
|
const slot = result.slots[i];
|
|
406
259
|
if (!slot || slot.height <= 0) return null;
|
|
407
260
|
const colStyle = {
|
|
@@ -419,15 +272,15 @@ function MejiroPageView({
|
|
|
419
272
|
};
|
|
420
273
|
return (
|
|
421
274
|
// biome-ignore lint/suspicious/noArrayIndexKey: lines have no stable ID
|
|
422
|
-
/* @__PURE__ */
|
|
275
|
+
/* @__PURE__ */ jsx6("div", { style: colStyle, children: line.segments.map((seg, si) => renderSegment(seg, `${i}-${si}`)) }, i)
|
|
423
276
|
);
|
|
424
277
|
}) });
|
|
425
278
|
}
|
|
426
|
-
return /* @__PURE__ */
|
|
279
|
+
return /* @__PURE__ */ jsx6(MejiroPage, { page: result.page, className, style });
|
|
427
280
|
}
|
|
428
281
|
|
|
429
282
|
// src/MejiroScrollView.tsx
|
|
430
|
-
import { jsx as
|
|
283
|
+
import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
431
284
|
function MejiroScrollView({
|
|
432
285
|
layout,
|
|
433
286
|
pageWidth,
|
|
@@ -444,10 +297,12 @@ function MejiroScrollView({
|
|
|
444
297
|
const pageRefs = useRef2([]);
|
|
445
298
|
const onVisiblePageChangeRef = useRef2(onVisiblePageChange);
|
|
446
299
|
onVisiblePageChangeRef.current = onVisiblePageChange;
|
|
300
|
+
const programmaticScrollRef = useRef2(false);
|
|
447
301
|
const pages = useMemo2(() => {
|
|
448
302
|
const total = layout.totalPages;
|
|
449
303
|
return Array.from({ length: total }, (_, i) => layout.getPage(i));
|
|
450
304
|
}, [layout]);
|
|
305
|
+
const pageCount = pages.length;
|
|
451
306
|
const useSlot = true;
|
|
452
307
|
const contentStyle = { height: contentHeight };
|
|
453
308
|
if (fontFamily) contentStyle.fontFamily = normalizeFontFamily2(fontFamily);
|
|
@@ -456,6 +311,7 @@ function MejiroScrollView({
|
|
|
456
311
|
useEffect(() => {
|
|
457
312
|
const container = containerRef.current;
|
|
458
313
|
if (!container) return;
|
|
314
|
+
if (pageCount === 0) return;
|
|
459
315
|
if (typeof IntersectionObserver === "undefined") return;
|
|
460
316
|
let mostVisibleIdx = -1;
|
|
461
317
|
let mostVisibleRatio = 0;
|
|
@@ -474,7 +330,12 @@ function MejiroScrollView({
|
|
|
474
330
|
}
|
|
475
331
|
}
|
|
476
332
|
}
|
|
477
|
-
if (mostVisibleIdx >= 0)
|
|
333
|
+
if (mostVisibleIdx >= 0) {
|
|
334
|
+
onVisiblePageChangeRef.current?.(
|
|
335
|
+
mostVisibleIdx,
|
|
336
|
+
programmaticScrollRef.current ? "programmatic" : "user"
|
|
337
|
+
);
|
|
338
|
+
}
|
|
478
339
|
},
|
|
479
340
|
{ root: container, threshold: [0.25, 0.5, 0.75] }
|
|
480
341
|
);
|
|
@@ -482,22 +343,28 @@ function MejiroScrollView({
|
|
|
482
343
|
if (el) observer.observe(el);
|
|
483
344
|
}
|
|
484
345
|
return () => observer.disconnect();
|
|
485
|
-
}, []);
|
|
346
|
+
}, [pageCount]);
|
|
486
347
|
useLayoutEffect(() => {
|
|
487
348
|
if (scrollToPage == null) return;
|
|
349
|
+
if (pageCount === 0) return;
|
|
488
350
|
const el = pageRefs.current[scrollToPage];
|
|
489
351
|
if (!(el && containerRef.current)) return;
|
|
352
|
+
programmaticScrollRef.current = true;
|
|
490
353
|
containerRef.current.scrollTo({
|
|
491
354
|
top: el.offsetTop,
|
|
492
355
|
behavior: "auto"
|
|
493
356
|
});
|
|
494
|
-
|
|
495
|
-
|
|
357
|
+
const timer = setTimeout(() => {
|
|
358
|
+
programmaticScrollRef.current = false;
|
|
359
|
+
}, 0);
|
|
360
|
+
return () => clearTimeout(timer);
|
|
361
|
+
}, [scrollToPage, pageCount]);
|
|
362
|
+
return /* @__PURE__ */ jsx7("div", { ref: containerRef, className: "mejiro-reader-scroll", children: /* @__PURE__ */ jsx7(
|
|
496
363
|
"div",
|
|
497
364
|
{
|
|
498
365
|
className: "mejiro-reader-scroll-track",
|
|
499
366
|
style: { display: "flex", flexDirection: "column", gap: pageGap },
|
|
500
|
-
children: pages.map((result, i) => /* @__PURE__ */
|
|
367
|
+
children: pages.map((result, i) => /* @__PURE__ */ jsxs4(
|
|
501
368
|
"div",
|
|
502
369
|
{
|
|
503
370
|
ref: (el) => {
|
|
@@ -507,12 +374,12 @@ function MejiroScrollView({
|
|
|
507
374
|
className: "mejiro-reader-page",
|
|
508
375
|
style: { width: pageWidth, height: pageHeight, flexShrink: 0 },
|
|
509
376
|
children: [
|
|
510
|
-
/* @__PURE__ */
|
|
511
|
-
/* @__PURE__ */
|
|
512
|
-
/* @__PURE__ */
|
|
513
|
-
/* @__PURE__ */
|
|
377
|
+
/* @__PURE__ */ jsx7("div", { className: "mejiro-reader-page-rule" }),
|
|
378
|
+
/* @__PURE__ */ jsxs4("div", { className: "mejiro-reader-page-header", children: [
|
|
379
|
+
/* @__PURE__ */ jsx7("span", { className: "mejiro-reader-page-header-title" }),
|
|
380
|
+
/* @__PURE__ */ jsx7("span", { className: "mejiro-reader-page-header-num", children: i + 1 })
|
|
514
381
|
] }),
|
|
515
|
-
/* @__PURE__ */
|
|
382
|
+
/* @__PURE__ */ jsx7("div", { className: "mejiro-reader-page-viewport", children: /* @__PURE__ */ jsx7("div", { className: "mejiro-reader-page-clip", style: { height: contentHeight }, children: /* @__PURE__ */ jsx7(
|
|
516
383
|
MejiroPageView,
|
|
517
384
|
{
|
|
518
385
|
result,
|
|
@@ -533,11 +400,23 @@ function MejiroScrollView({
|
|
|
533
400
|
|
|
534
401
|
// src/MejiroSettingsPanel.tsx
|
|
535
402
|
import { normalizeFontFamily as normalizeFontFamily3 } from "@libraz/mejiro/browser";
|
|
536
|
-
import { jsx as
|
|
403
|
+
import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
537
404
|
var DEFAULT_FONTS = [
|
|
538
405
|
{ value: "serif", label: "System Serif" },
|
|
539
406
|
{ value: "sans-serif", label: "System Sans" }
|
|
540
407
|
];
|
|
408
|
+
function fontLabelFromCss(css) {
|
|
409
|
+
const first = css.split(",")[0]?.trim() ?? css;
|
|
410
|
+
return first.replace(/^["']|["']$/g, "") || css;
|
|
411
|
+
}
|
|
412
|
+
function clampNumber(value, min, max, fallback) {
|
|
413
|
+
if (!Number.isFinite(value)) return fallback;
|
|
414
|
+
return Math.max(min, Math.min(max, value));
|
|
415
|
+
}
|
|
416
|
+
function parseClampedInput(value, min, max, fallback) {
|
|
417
|
+
if (value.trim() === "") return fallback;
|
|
418
|
+
return clampNumber(Number(value), min, max, fallback);
|
|
419
|
+
}
|
|
541
420
|
function MejiroSettingsPanel({
|
|
542
421
|
open,
|
|
543
422
|
settings,
|
|
@@ -548,20 +427,15 @@ function MejiroSettingsPanel({
|
|
|
548
427
|
}) {
|
|
549
428
|
const messages = useI18n();
|
|
550
429
|
const patch = (next) => onChange({ ...settings, ...next });
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
}
|
|
561
|
-
) }),
|
|
562
|
-
/* @__PURE__ */ jsxs7("div", { className: "mejiro-reader-control", children: [
|
|
563
|
-
/* @__PURE__ */ jsx7("label", { className: "mejiro-reader-control-label", htmlFor: "mejiro-reader-font-size", children: messages.settingsSize }),
|
|
564
|
-
/* @__PURE__ */ jsx7(
|
|
430
|
+
const currentFont = normalizeFontFamily3(settings.fontFamily);
|
|
431
|
+
const fontOptions = fonts.some((f) => f.value === currentFont) ? fonts : [{ value: currentFont, label: fontLabelFromCss(currentFont) }, ...fonts];
|
|
432
|
+
return /* @__PURE__ */ jsx8("div", { className: `mejiro-reader-settings-panel${open ? " is-open" : ""}`, children: /* @__PURE__ */ jsx8("div", { className: "mejiro-reader-settings-inner", children: /* @__PURE__ */ jsxs5("div", { className: "mejiro-reader-settings-content", children: [
|
|
433
|
+
/* @__PURE__ */ jsxs5("div", { className: "mejiro-reader-settings-group", children: [
|
|
434
|
+
/* @__PURE__ */ jsx8("span", { className: "mejiro-reader-settings-group-title", children: messages.settingsFont }),
|
|
435
|
+
/* @__PURE__ */ jsx8("div", { className: "mejiro-reader-control", children: /* @__PURE__ */ jsx8("select", { value: currentFont, onChange: (e) => patch({ fontFamily: e.target.value }), children: fontOptions.map((f) => /* @__PURE__ */ jsx8("option", { value: f.value, children: f.label }, f.value)) }) }),
|
|
436
|
+
/* @__PURE__ */ jsxs5("div", { className: "mejiro-reader-control", children: [
|
|
437
|
+
/* @__PURE__ */ jsx8("label", { className: "mejiro-reader-control-label", htmlFor: "mejiro-reader-font-size", children: messages.settingsSize }),
|
|
438
|
+
/* @__PURE__ */ jsx8(
|
|
565
439
|
"button",
|
|
566
440
|
{
|
|
567
441
|
type: "button",
|
|
@@ -571,7 +445,7 @@ function MejiroSettingsPanel({
|
|
|
571
445
|
children: "A\u2212"
|
|
572
446
|
}
|
|
573
447
|
),
|
|
574
|
-
/* @__PURE__ */
|
|
448
|
+
/* @__PURE__ */ jsx8(
|
|
575
449
|
"input",
|
|
576
450
|
{
|
|
577
451
|
id: "mejiro-reader-font-size",
|
|
@@ -579,10 +453,17 @@ function MejiroSettingsPanel({
|
|
|
579
453
|
value: settings.fontSize,
|
|
580
454
|
min: minFontSize,
|
|
581
455
|
max: maxFontSize,
|
|
582
|
-
onChange: (e) => patch({
|
|
456
|
+
onChange: (e) => patch({
|
|
457
|
+
fontSize: parseClampedInput(
|
|
458
|
+
e.target.value,
|
|
459
|
+
minFontSize,
|
|
460
|
+
maxFontSize,
|
|
461
|
+
settings.fontSize
|
|
462
|
+
)
|
|
463
|
+
})
|
|
583
464
|
}
|
|
584
465
|
),
|
|
585
|
-
/* @__PURE__ */
|
|
466
|
+
/* @__PURE__ */ jsx8(
|
|
586
467
|
"button",
|
|
587
468
|
{
|
|
588
469
|
type: "button",
|
|
@@ -594,41 +475,41 @@ function MejiroSettingsPanel({
|
|
|
594
475
|
)
|
|
595
476
|
] })
|
|
596
477
|
] }),
|
|
597
|
-
/* @__PURE__ */
|
|
598
|
-
/* @__PURE__ */
|
|
599
|
-
/* @__PURE__ */
|
|
600
|
-
/* @__PURE__ */
|
|
601
|
-
/* @__PURE__ */
|
|
478
|
+
/* @__PURE__ */ jsxs5("div", { className: "mejiro-reader-settings-group", children: [
|
|
479
|
+
/* @__PURE__ */ jsx8("span", { className: "mejiro-reader-settings-group-title", children: messages.settingsLayout }),
|
|
480
|
+
/* @__PURE__ */ jsxs5("div", { className: "mejiro-reader-control", children: [
|
|
481
|
+
/* @__PURE__ */ jsx8("label", { className: "mejiro-reader-control-label", htmlFor: "mejiro-reader-kinsoku", children: messages.settingsKinsoku }),
|
|
482
|
+
/* @__PURE__ */ jsxs5(
|
|
602
483
|
"select",
|
|
603
484
|
{
|
|
604
485
|
id: "mejiro-reader-kinsoku",
|
|
605
486
|
value: settings.mode ?? "strict",
|
|
606
487
|
onChange: (e) => patch({ mode: e.target.value }),
|
|
607
488
|
children: [
|
|
608
|
-
/* @__PURE__ */
|
|
609
|
-
/* @__PURE__ */
|
|
489
|
+
/* @__PURE__ */ jsx8("option", { value: "strict", children: messages.settingsStrict }),
|
|
490
|
+
/* @__PURE__ */ jsx8("option", { value: "loose", children: messages.settingsLoose })
|
|
610
491
|
]
|
|
611
492
|
}
|
|
612
493
|
)
|
|
613
494
|
] }),
|
|
614
|
-
/* @__PURE__ */
|
|
615
|
-
/* @__PURE__ */
|
|
616
|
-
/* @__PURE__ */
|
|
495
|
+
/* @__PURE__ */ jsxs5("div", { className: "mejiro-reader-control", children: [
|
|
496
|
+
/* @__PURE__ */ jsx8("label", { className: "mejiro-reader-control-label", htmlFor: "mejiro-reader-hanging", children: messages.settingsHanging }),
|
|
497
|
+
/* @__PURE__ */ jsxs5(
|
|
617
498
|
"select",
|
|
618
499
|
{
|
|
619
500
|
id: "mejiro-reader-hanging",
|
|
620
501
|
value: String(settings.enableHanging ?? true),
|
|
621
502
|
onChange: (e) => patch({ enableHanging: e.target.value === "true" }),
|
|
622
503
|
children: [
|
|
623
|
-
/* @__PURE__ */
|
|
624
|
-
/* @__PURE__ */
|
|
504
|
+
/* @__PURE__ */ jsx8("option", { value: "true", children: messages.toggleOn }),
|
|
505
|
+
/* @__PURE__ */ jsx8("option", { value: "false", children: messages.toggleOff })
|
|
625
506
|
]
|
|
626
507
|
}
|
|
627
508
|
)
|
|
628
509
|
] }),
|
|
629
|
-
/* @__PURE__ */
|
|
630
|
-
/* @__PURE__ */
|
|
631
|
-
/* @__PURE__ */
|
|
510
|
+
/* @__PURE__ */ jsxs5("div", { className: "mejiro-reader-control", children: [
|
|
511
|
+
/* @__PURE__ */ jsx8("label", { className: "mejiro-reader-control-label", htmlFor: "mejiro-reader-line-spacing", children: messages.settingsLineSpacing }),
|
|
512
|
+
/* @__PURE__ */ jsx8(
|
|
632
513
|
"input",
|
|
633
514
|
{
|
|
634
515
|
id: "mejiro-reader-line-spacing",
|
|
@@ -638,12 +519,19 @@ function MejiroSettingsPanel({
|
|
|
638
519
|
min: 1,
|
|
639
520
|
max: 3,
|
|
640
521
|
step: 0.1,
|
|
641
|
-
onChange: (e) => patch({
|
|
522
|
+
onChange: (e) => patch({
|
|
523
|
+
lineSpacing: parseClampedInput(
|
|
524
|
+
e.target.value,
|
|
525
|
+
1,
|
|
526
|
+
3,
|
|
527
|
+
settings.lineSpacing ?? 1.8
|
|
528
|
+
)
|
|
529
|
+
})
|
|
642
530
|
}
|
|
643
531
|
)
|
|
644
532
|
] })
|
|
645
533
|
] })
|
|
646
|
-
] }) });
|
|
534
|
+
] }) }) });
|
|
647
535
|
}
|
|
648
536
|
|
|
649
537
|
// src/MejiroSpread.tsx
|
|
@@ -653,7 +541,7 @@ import {
|
|
|
653
541
|
} from "react";
|
|
654
542
|
|
|
655
543
|
// src/MejiroImageOverlay.tsx
|
|
656
|
-
import { jsx as
|
|
544
|
+
import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
657
545
|
function MejiroImageOverlay({
|
|
658
546
|
rect,
|
|
659
547
|
label,
|
|
@@ -663,7 +551,7 @@ function MejiroImageOverlay({
|
|
|
663
551
|
}) {
|
|
664
552
|
const messages = useI18n();
|
|
665
553
|
const resolvedLabel = label ?? messages.imageButton;
|
|
666
|
-
return /* @__PURE__ */
|
|
554
|
+
return /* @__PURE__ */ jsxs6(
|
|
667
555
|
"div",
|
|
668
556
|
{
|
|
669
557
|
className: "mejiro-reader-image-overlay",
|
|
@@ -675,21 +563,28 @@ function MejiroImageOverlay({
|
|
|
675
563
|
},
|
|
676
564
|
onPointerDown: onOverlayPointerDown,
|
|
677
565
|
children: [
|
|
678
|
-
/* @__PURE__ */
|
|
679
|
-
/* @__PURE__ */
|
|
680
|
-
/* @__PURE__ */
|
|
566
|
+
/* @__PURE__ */ jsxs6("div", { className: "mejiro-reader-image-overlay-label", children: [
|
|
567
|
+
/* @__PURE__ */ jsx9("div", { className: "mejiro-reader-image-overlay-icon" }),
|
|
568
|
+
/* @__PURE__ */ jsx9("span", { children: resolvedLabel })
|
|
681
569
|
] }),
|
|
682
|
-
/* @__PURE__ */
|
|
683
|
-
/* @__PURE__ */
|
|
570
|
+
/* @__PURE__ */ jsx9("div", { className: "mejiro-reader-image-overlay-resize", onPointerDown: onResizePointerDown }),
|
|
571
|
+
/* @__PURE__ */ jsx9(
|
|
684
572
|
"button",
|
|
685
573
|
{
|
|
686
574
|
type: "button",
|
|
687
575
|
"aria-label": messages.imageRemoveButton,
|
|
576
|
+
title: messages.imageRemoveButton,
|
|
688
577
|
className: "mejiro-reader-image-overlay-close",
|
|
689
578
|
onPointerDown: (e) => {
|
|
690
579
|
e.stopPropagation();
|
|
691
580
|
e.preventDefault();
|
|
692
581
|
onClose?.();
|
|
582
|
+
},
|
|
583
|
+
onKeyDown: (e) => {
|
|
584
|
+
if (e.key !== "Enter" && e.key !== " ") return;
|
|
585
|
+
e.stopPropagation();
|
|
586
|
+
e.preventDefault();
|
|
587
|
+
onClose?.();
|
|
693
588
|
}
|
|
694
589
|
}
|
|
695
590
|
)
|
|
@@ -699,7 +594,7 @@ function MejiroImageOverlay({
|
|
|
699
594
|
}
|
|
700
595
|
|
|
701
596
|
// src/MejiroSelectionLayer.tsx
|
|
702
|
-
import { jsx as
|
|
597
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
703
598
|
function MejiroSelectionLayer({
|
|
704
599
|
rects,
|
|
705
600
|
side,
|
|
@@ -710,7 +605,7 @@ function MejiroSelectionLayer({
|
|
|
710
605
|
}) {
|
|
711
606
|
const wrapperClass = className ? `mejiro-selection-layer ${className}` : "mejiro-selection-layer";
|
|
712
607
|
const rectClass = rectClassName ?? "mejiro-selection-rect";
|
|
713
|
-
return /* @__PURE__ */
|
|
608
|
+
return /* @__PURE__ */ jsx10(
|
|
714
609
|
"div",
|
|
715
610
|
{
|
|
716
611
|
className: wrapperClass,
|
|
@@ -723,7 +618,7 @@ function MejiroSelectionLayer({
|
|
|
723
618
|
children: rects.map((r, i) => {
|
|
724
619
|
if (r.side !== side) return null;
|
|
725
620
|
const posStyle = side === "right" ? { left: r.x, top: r.y } : { right: -(r.x + r.width), top: r.y };
|
|
726
|
-
return /* @__PURE__ */
|
|
621
|
+
return /* @__PURE__ */ jsx10(
|
|
727
622
|
"div",
|
|
728
623
|
{
|
|
729
624
|
className: rectClass,
|
|
@@ -743,11 +638,11 @@ function MejiroSelectionLayer({
|
|
|
743
638
|
}
|
|
744
639
|
|
|
745
640
|
// src/MejiroSpread.tsx
|
|
746
|
-
import { jsx as
|
|
641
|
+
import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
747
642
|
function defaultHeader(data) {
|
|
748
|
-
return /* @__PURE__ */
|
|
749
|
-
/* @__PURE__ */
|
|
750
|
-
/* @__PURE__ */
|
|
643
|
+
return /* @__PURE__ */ jsxs7("div", { className: "mejiro-reader-page-header", children: [
|
|
644
|
+
/* @__PURE__ */ jsx11("span", { className: "mejiro-reader-page-header-title", children: data.title ?? "" }),
|
|
645
|
+
/* @__PURE__ */ jsx11("span", { className: "mejiro-reader-page-header-num", children: data.pageNumber != null ? String(data.pageNumber) : "" })
|
|
751
646
|
] });
|
|
752
647
|
}
|
|
753
648
|
function MejiroSpread({
|
|
@@ -862,16 +757,16 @@ function MejiroSpread({
|
|
|
862
757
|
height: pageHeight
|
|
863
758
|
};
|
|
864
759
|
if (isRight && hasImages) pageStyle.overflow = "visible";
|
|
865
|
-
return /* @__PURE__ */
|
|
760
|
+
return /* @__PURE__ */ jsxs7(
|
|
866
761
|
"div",
|
|
867
762
|
{
|
|
868
763
|
className: `mejiro-reader-page mejiro-reader-page--${side}`,
|
|
869
764
|
style: pageStyle,
|
|
870
765
|
children: [
|
|
871
|
-
/* @__PURE__ */
|
|
766
|
+
/* @__PURE__ */ jsx11("div", { className: "mejiro-reader-page-rule" }),
|
|
872
767
|
renderPageHeader ? renderPageHeader(side, header) : defaultHeader(header),
|
|
873
|
-
/* @__PURE__ */
|
|
874
|
-
/* @__PURE__ */
|
|
768
|
+
/* @__PURE__ */ jsx11("div", { className: "mejiro-reader-page-viewport", children: /* @__PURE__ */ jsxs7("div", { className: "mejiro-reader-page-clip", style: { height: contentHeight }, children: [
|
|
769
|
+
/* @__PURE__ */ jsx11(
|
|
875
770
|
MejiroPageView,
|
|
876
771
|
{
|
|
877
772
|
result,
|
|
@@ -883,9 +778,9 @@ function MejiroSpread({
|
|
|
883
778
|
},
|
|
884
779
|
pageKey
|
|
885
780
|
),
|
|
886
|
-
selectionRects && selectionRects.length > 0 && /* @__PURE__ */
|
|
781
|
+
selectionRects && selectionRects.length > 0 && /* @__PURE__ */ jsx11(MejiroSelectionLayer, { rects: selectionRects, side })
|
|
887
782
|
] }) }),
|
|
888
|
-
isRight && images.map((item) => /* @__PURE__ */
|
|
783
|
+
isRight && images.map((item) => /* @__PURE__ */ jsx11(
|
|
889
784
|
MejiroImageOverlay,
|
|
890
785
|
{
|
|
891
786
|
rect: item.rect,
|
|
@@ -912,7 +807,7 @@ function MejiroSpread({
|
|
|
912
807
|
if (selectionEnabled) handlePointerUp(e);
|
|
913
808
|
}
|
|
914
809
|
const useCombined = selectionEnabled || gestureEnabled;
|
|
915
|
-
return /* @__PURE__ */
|
|
810
|
+
return /* @__PURE__ */ jsx11("div", { className: "mejiro-reader-book", children: /* @__PURE__ */ jsxs7(
|
|
916
811
|
"div",
|
|
917
812
|
{
|
|
918
813
|
className: `mejiro-reader-spread${turning ? " is-turning" : ""}${singlePage ? " mejiro-reader-spread--single" : ""}`,
|
|
@@ -923,7 +818,7 @@ function MejiroSpread({
|
|
|
923
818
|
children: [
|
|
924
819
|
renderPage("right"),
|
|
925
820
|
!singlePage && renderPage("left"),
|
|
926
|
-
/* @__PURE__ */
|
|
821
|
+
/* @__PURE__ */ jsx11(
|
|
927
822
|
"button",
|
|
928
823
|
{
|
|
929
824
|
type: "button",
|
|
@@ -932,7 +827,7 @@ function MejiroSpread({
|
|
|
932
827
|
onClick: onPrev
|
|
933
828
|
}
|
|
934
829
|
),
|
|
935
|
-
/* @__PURE__ */
|
|
830
|
+
/* @__PURE__ */ jsx11(
|
|
936
831
|
"button",
|
|
937
832
|
{
|
|
938
833
|
type: "button",
|
|
@@ -949,7 +844,7 @@ function MejiroSpread({
|
|
|
949
844
|
|
|
950
845
|
// src/MejiroStats.tsx
|
|
951
846
|
import { estimateReadingTime, formatReadingTime } from "@libraz/mejiro/book";
|
|
952
|
-
import { jsx as
|
|
847
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
953
848
|
function MejiroStats({
|
|
954
849
|
chapter,
|
|
955
850
|
totalPages,
|
|
@@ -959,7 +854,7 @@ function MejiroStats({
|
|
|
959
854
|
cpm = 600,
|
|
960
855
|
readingTimeLocale = "ja"
|
|
961
856
|
}) {
|
|
962
|
-
if (!chapter) return /* @__PURE__ */
|
|
857
|
+
if (!chapter) return /* @__PURE__ */ jsx12("span", { className: "mejiro-reader-stats" });
|
|
963
858
|
const totalChars = chapter.paragraphs.reduce((s, p) => s + p.text.length, 0);
|
|
964
859
|
const totalRuby = chapter.paragraphs.reduce(
|
|
965
860
|
(s, p) => s + p.inlineAnnotations.filter((a) => a.kind === "ruby").length,
|
|
@@ -974,11 +869,17 @@ function MejiroStats({
|
|
|
974
869
|
fontLabel || null,
|
|
975
870
|
`${elapsedMs.toFixed(0)}ms`
|
|
976
871
|
];
|
|
977
|
-
return /* @__PURE__ */
|
|
872
|
+
return /* @__PURE__ */ jsx12("span", { className: "mejiro-reader-stats", children: parts.filter(Boolean).join(" / ") });
|
|
978
873
|
}
|
|
979
874
|
|
|
980
875
|
// src/useChapterLayout.ts
|
|
981
|
-
import {
|
|
876
|
+
import {
|
|
877
|
+
useCallback,
|
|
878
|
+
useEffect as useEffect2,
|
|
879
|
+
useLayoutEffect as useLayoutEffect2,
|
|
880
|
+
useRef as useRef4,
|
|
881
|
+
useState as useState2
|
|
882
|
+
} from "react";
|
|
982
883
|
function useChapterLayout(book, epub, chapterIndex, surface, options = {}) {
|
|
983
884
|
const enableResize = options.enableResize ?? true;
|
|
984
885
|
const resizeDebounce = options.resizeDebounce ?? 120;
|
|
@@ -989,31 +890,43 @@ function useChapterLayout(book, epub, chapterIndex, surface, options = {}) {
|
|
|
989
890
|
const [elapsedMs, setElapsedMs] = useState2(0);
|
|
990
891
|
const layoutRef = useRef4(null);
|
|
991
892
|
const requestIdRef = useRef4(0);
|
|
893
|
+
const pendingRestore = useRef4(null);
|
|
992
894
|
layoutRef.current = layout;
|
|
993
|
-
const
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
895
|
+
const optionsRef = useRef4(options);
|
|
896
|
+
optionsRef.current = options;
|
|
897
|
+
const recompute = useCallback(
|
|
898
|
+
async (opts = {}) => {
|
|
899
|
+
const blank = opts.blank ?? true;
|
|
900
|
+
const o = optionsRef.current;
|
|
901
|
+
const requestId = ++requestIdRef.current;
|
|
902
|
+
const chapter = epub?.chapters[chapterIndex];
|
|
903
|
+
if (!(chapter && surface.current)) {
|
|
904
|
+
setLayout(null);
|
|
905
|
+
setPageWidth(0);
|
|
906
|
+
setPageHeight(0);
|
|
907
|
+
setContentHeight(0);
|
|
908
|
+
setElapsedMs(0);
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
const captured = !blank && layoutRef.current ? o.capturePosition?.(layoutRef.current) ?? null : null;
|
|
912
|
+
pendingRestore.current = captured;
|
|
913
|
+
if (blank) setLayout(null);
|
|
914
|
+
const dims = book.computePageSize(surface.current, o.pageGeometry?.());
|
|
915
|
+
setPageWidth(dims.pageWidth);
|
|
916
|
+
setPageHeight(dims.pageHeight);
|
|
917
|
+
setContentHeight(dims.contentHeight);
|
|
918
|
+
const t0 = performance.now();
|
|
919
|
+
const l = await book.layoutChapter(chapter);
|
|
920
|
+
if (requestId !== requestIdRef.current) return;
|
|
921
|
+
layoutRef.current = l;
|
|
922
|
+
setLayout(l);
|
|
923
|
+
setElapsedMs(performance.now() - t0);
|
|
924
|
+
},
|
|
925
|
+
[book, epub, chapterIndex, surface]
|
|
926
|
+
);
|
|
1015
927
|
useLayoutEffect2(() => {
|
|
1016
928
|
requestIdRef.current++;
|
|
929
|
+
pendingRestore.current = null;
|
|
1017
930
|
setLayout(null);
|
|
1018
931
|
setPageWidth(0);
|
|
1019
932
|
setPageHeight(0);
|
|
@@ -1028,29 +941,55 @@ function useChapterLayout(book, epub, chapterIndex, surface, options = {}) {
|
|
|
1028
941
|
}, [recompute]);
|
|
1029
942
|
useEffect2(() => {
|
|
1030
943
|
if (!enableResize) return;
|
|
944
|
+
const el = surface.current;
|
|
945
|
+
if (!el) return;
|
|
1031
946
|
let timer = null;
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
if (timer)
|
|
947
|
+
let observed = false;
|
|
948
|
+
const clear = () => {
|
|
949
|
+
if (timer) {
|
|
950
|
+
clearTimeout(timer);
|
|
951
|
+
timer = null;
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
const scheduleReflow = (immediate) => {
|
|
955
|
+
clear();
|
|
956
|
+
if (immediate) {
|
|
957
|
+
void recompute({ blank: false });
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
1035
960
|
timer = setTimeout(() => {
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
setPageWidth(dims.pageWidth);
|
|
1039
|
-
setPageHeight(dims.pageHeight);
|
|
1040
|
-
setContentHeight(dims.contentHeight);
|
|
1041
|
-
layoutRef.current.resize({
|
|
1042
|
-
pageWidth: dims.pageWidth,
|
|
1043
|
-
lineWidth: dims.contentHeight - book.getOptions().fontSize * 0.5
|
|
1044
|
-
});
|
|
961
|
+
timer = null;
|
|
962
|
+
void recompute({ blank: false });
|
|
1045
963
|
}, resizeDebounce);
|
|
1046
964
|
};
|
|
1047
|
-
|
|
965
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
966
|
+
const observer = new ResizeObserver(() => {
|
|
967
|
+
const immediate = !observed;
|
|
968
|
+
observed = true;
|
|
969
|
+
scheduleReflow(immediate);
|
|
970
|
+
});
|
|
971
|
+
observer.observe(el);
|
|
972
|
+
return () => {
|
|
973
|
+
observer.disconnect();
|
|
974
|
+
clear();
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
const onWindowResize = () => scheduleReflow(false);
|
|
978
|
+
window.addEventListener("resize", onWindowResize);
|
|
1048
979
|
return () => {
|
|
1049
|
-
window.removeEventListener("resize",
|
|
1050
|
-
|
|
980
|
+
window.removeEventListener("resize", onWindowResize);
|
|
981
|
+
clear();
|
|
1051
982
|
};
|
|
1052
|
-
}, [
|
|
1053
|
-
return {
|
|
983
|
+
}, [surface, enableResize, resizeDebounce, recompute]);
|
|
984
|
+
return {
|
|
985
|
+
layout,
|
|
986
|
+
pageWidth,
|
|
987
|
+
pageHeight,
|
|
988
|
+
contentHeight,
|
|
989
|
+
elapsedMs,
|
|
990
|
+
recompute,
|
|
991
|
+
pendingRestore
|
|
992
|
+
};
|
|
1054
993
|
}
|
|
1055
994
|
|
|
1056
995
|
// src/useEpub.ts
|
|
@@ -1407,6 +1346,7 @@ function useSpread(layout, options = {}) {
|
|
|
1407
1346
|
spreadIdxRef.current = spreadIdx;
|
|
1408
1347
|
const turnTimerRef = useRef8(null);
|
|
1409
1348
|
const layoutGenerationRef = useRef8(0);
|
|
1349
|
+
const lastEmittedRef = useRef8(null);
|
|
1410
1350
|
const totalPages = layout?.totalPages ?? 0;
|
|
1411
1351
|
const totalSpreads = Math.max(1, Math.ceil(totalPages / 2));
|
|
1412
1352
|
const refresh = useCallback5(() => {
|
|
@@ -1433,7 +1373,15 @@ function useSpread(layout, options = {}) {
|
|
|
1433
1373
|
useEffect5(() => {
|
|
1434
1374
|
if (!layout) return;
|
|
1435
1375
|
setSpread(layout.getSpread(spreadIdx));
|
|
1436
|
-
|
|
1376
|
+
const last = lastEmittedRef.current;
|
|
1377
|
+
if (!last) {
|
|
1378
|
+
lastEmittedRef.current = { layout, spreadIdx };
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
if (last.spreadIdx !== spreadIdx) {
|
|
1382
|
+
onChangeRef.current?.(spreadIdx);
|
|
1383
|
+
}
|
|
1384
|
+
lastEmittedRef.current = { layout, spreadIdx };
|
|
1437
1385
|
}, [layout, spreadIdx]);
|
|
1438
1386
|
const goTo = useCallback5(
|
|
1439
1387
|
(index) => {
|
|
@@ -1457,6 +1405,16 @@ function useSpread(layout, options = {}) {
|
|
|
1457
1405
|
},
|
|
1458
1406
|
[turnDuration]
|
|
1459
1407
|
);
|
|
1408
|
+
const setSpreadIndex = useCallback5((index) => {
|
|
1409
|
+
if (!layoutRef.current) return;
|
|
1410
|
+
if (turnTimerRef.current) {
|
|
1411
|
+
clearTimeout(turnTimerRef.current);
|
|
1412
|
+
turnTimerRef.current = null;
|
|
1413
|
+
}
|
|
1414
|
+
setTurning(false);
|
|
1415
|
+
const max = Math.max(1, Math.ceil(layoutRef.current.totalPages / 2)) - 1;
|
|
1416
|
+
setSpreadIdx(Math.max(0, Math.min(max, index)));
|
|
1417
|
+
}, []);
|
|
1460
1418
|
const next = useCallback5(() => goTo(spreadIdxRef.current + 1), [goTo]);
|
|
1461
1419
|
const prev = useCallback5(() => goTo(spreadIdxRef.current - 1), [goTo]);
|
|
1462
1420
|
useEffect5(() => {
|
|
@@ -1475,16 +1433,28 @@ function useSpread(layout, options = {}) {
|
|
|
1475
1433
|
},
|
|
1476
1434
|
[]
|
|
1477
1435
|
);
|
|
1478
|
-
return {
|
|
1436
|
+
return {
|
|
1437
|
+
spreadIdx,
|
|
1438
|
+
spread,
|
|
1439
|
+
totalPages,
|
|
1440
|
+
totalSpreads,
|
|
1441
|
+
turning,
|
|
1442
|
+
next,
|
|
1443
|
+
prev,
|
|
1444
|
+
goTo,
|
|
1445
|
+
setSpread: setSpreadIndex,
|
|
1446
|
+
refresh
|
|
1447
|
+
};
|
|
1479
1448
|
}
|
|
1480
1449
|
|
|
1481
1450
|
// src/MejiroReader.tsx
|
|
1482
|
-
import { jsx as
|
|
1451
|
+
import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1483
1452
|
function MejiroReaderInner(props, ref) {
|
|
1484
1453
|
const manuscriptProp = "manuscript" in props ? props.manuscript : void 0;
|
|
1485
1454
|
const dialectProp = "dialect" in props ? props.dialect : void 0;
|
|
1486
1455
|
const {
|
|
1487
|
-
options
|
|
1456
|
+
options: optionsProp,
|
|
1457
|
+
pageGeometry: pageGeometryProp,
|
|
1488
1458
|
fonts,
|
|
1489
1459
|
epub: epubProp,
|
|
1490
1460
|
epubUrl,
|
|
@@ -1493,6 +1463,7 @@ function MejiroReaderInner(props, ref) {
|
|
|
1493
1463
|
theme = "light",
|
|
1494
1464
|
mode = "paginated",
|
|
1495
1465
|
spreadMode = "double",
|
|
1466
|
+
fit = "fill",
|
|
1496
1467
|
enableSurfaceTap = true,
|
|
1497
1468
|
fallback,
|
|
1498
1469
|
fetchOptions,
|
|
@@ -1508,10 +1479,12 @@ function MejiroReaderInner(props, ref) {
|
|
|
1508
1479
|
enableChapterNav = !bare,
|
|
1509
1480
|
chapterNavMode = "select",
|
|
1510
1481
|
enableSettings = !bare,
|
|
1482
|
+
renderSettings,
|
|
1511
1483
|
enableImageOverlay = false,
|
|
1512
1484
|
enableStats = !bare,
|
|
1513
1485
|
enableKeyboard = true,
|
|
1514
1486
|
enablePageIndicator = !bare,
|
|
1487
|
+
pageNumbers = "both",
|
|
1515
1488
|
annotations,
|
|
1516
1489
|
onLoad,
|
|
1517
1490
|
onError,
|
|
@@ -1525,6 +1498,7 @@ function MejiroReaderInner(props, ref) {
|
|
|
1525
1498
|
const fileRef = useRef9(null);
|
|
1526
1499
|
const [settingsOpen, setSettingsOpen] = useState7(false);
|
|
1527
1500
|
const [chapterState, setChapterState] = useState7(chapterProp ?? 0);
|
|
1501
|
+
const chapterIsUncontrolled = chapterProp == null;
|
|
1528
1502
|
const chapter = chapterProp ?? chapterState;
|
|
1529
1503
|
const [chromeHidden, setChromeHidden] = useState7(false);
|
|
1530
1504
|
const [autoSingle, setAutoSingle] = useState7(false);
|
|
@@ -1551,7 +1525,37 @@ function MejiroReaderInner(props, ref) {
|
|
|
1551
1525
|
return () => observer.disconnect();
|
|
1552
1526
|
}, [spreadMode]);
|
|
1553
1527
|
const effectiveSingle = spreadMode === "single" || spreadMode === "auto" && autoSingle;
|
|
1554
|
-
const
|
|
1528
|
+
const resolvedGeometry = useMemo4(() => {
|
|
1529
|
+
const columns = effectiveSingle ? 1 : 2;
|
|
1530
|
+
if (fit !== "width") return { columns, ...pageGeometryProp };
|
|
1531
|
+
return {
|
|
1532
|
+
columns,
|
|
1533
|
+
gutterOffset: 0,
|
|
1534
|
+
headerOffset: 0,
|
|
1535
|
+
maxHeight: Number.POSITIVE_INFINITY,
|
|
1536
|
+
minWidth: 0,
|
|
1537
|
+
minHeight: 0,
|
|
1538
|
+
...pageGeometryProp
|
|
1539
|
+
};
|
|
1540
|
+
}, [fit, pageGeometryProp, effectiveSingle]);
|
|
1541
|
+
const surfaceAspect = useMemo4(() => {
|
|
1542
|
+
const columns = effectiveSingle ? 1 : 2;
|
|
1543
|
+
const aspect = resolvedGeometry?.aspect ?? DEFAULT_PAGE_GEOMETRY.aspect;
|
|
1544
|
+
return `${columns} / ${aspect}`;
|
|
1545
|
+
}, [effectiveSingle, resolvedGeometry]);
|
|
1546
|
+
const resolvedOptions = useMemo4(
|
|
1547
|
+
() => ({ ...DEFAULT_BOOK_OPTIONS, ...optionsProp ?? {} }),
|
|
1548
|
+
[optionsProp]
|
|
1549
|
+
);
|
|
1550
|
+
const { book, options: bookOptions, setOptions } = useMejiroBook(resolvedOptions);
|
|
1551
|
+
const didSyncOptionsPropRef = useRef9(false);
|
|
1552
|
+
useEffect6(() => {
|
|
1553
|
+
if (!didSyncOptionsPropRef.current) {
|
|
1554
|
+
didSyncOptionsPropRef.current = true;
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
void setOptions(resolvedOptions);
|
|
1558
|
+
}, [resolvedOptions, setOptions]);
|
|
1555
1559
|
const synthesizedEpub = useMemo4(() => {
|
|
1556
1560
|
if (manuscriptProp === void 0) return null;
|
|
1557
1561
|
return manuscriptToEpubBook(manuscriptProp, { dialect: dialectProp });
|
|
@@ -1575,7 +1579,18 @@ function MejiroReaderInner(props, ref) {
|
|
|
1575
1579
|
useEffect6(() => {
|
|
1576
1580
|
if (epubCtx.error) onErrorRef.current?.(epubCtx.error);
|
|
1577
1581
|
}, [epubCtx.error]);
|
|
1578
|
-
const
|
|
1582
|
+
const pageGeometryRef = useRef9(resolvedGeometry);
|
|
1583
|
+
pageGeometryRef.current = resolvedGeometry;
|
|
1584
|
+
const curSpreadIdxRef = useRef9(0);
|
|
1585
|
+
const spreadIdxPropRef = useRef9(spreadIdxProp);
|
|
1586
|
+
spreadIdxPropRef.current = spreadIdxProp;
|
|
1587
|
+
const layoutCtx = useChapterLayout(book, e, chapter, surfaceRef, {
|
|
1588
|
+
pageGeometry: () => pageGeometryRef.current,
|
|
1589
|
+
// Preserve the reading position across a reflow re-layout (size / option
|
|
1590
|
+
// changes), but only in uncontrolled mode — when `spreadIdx` is controlled
|
|
1591
|
+
// the host owns the position and the controlled-restore effect handles it.
|
|
1592
|
+
capturePosition: (layout2) => spreadIdxPropRef.current != null ? null : layout2.anchorAt(curSpreadIdxRef.current, "right")
|
|
1593
|
+
});
|
|
1579
1594
|
const spreadChangedRef = useRef9(void 0);
|
|
1580
1595
|
spreadChangedRef.current = (i) => {
|
|
1581
1596
|
onSpreadChange?.(i);
|
|
@@ -1585,6 +1600,46 @@ function MejiroReaderInner(props, ref) {
|
|
|
1585
1600
|
enableKeyboard,
|
|
1586
1601
|
onChange: (i) => spreadChangedRef.current?.(i)
|
|
1587
1602
|
});
|
|
1603
|
+
curSpreadIdxRef.current = spreadCtx.spreadIdx;
|
|
1604
|
+
const setSpreadRef = useRef9(spreadCtx.setSpread);
|
|
1605
|
+
setSpreadRef.current = spreadCtx.setSpread;
|
|
1606
|
+
useLayoutEffect4(() => {
|
|
1607
|
+
const anchor = layoutCtx.pendingRestore.current;
|
|
1608
|
+
if (!(anchor && layoutCtx.layout)) return;
|
|
1609
|
+
layoutCtx.pendingRestore.current = null;
|
|
1610
|
+
const loc = layoutCtx.layout.locateAnchor(anchor);
|
|
1611
|
+
setSpreadRef.current(loc?.spreadIdx ?? 0);
|
|
1612
|
+
}, [layoutCtx.layout]);
|
|
1613
|
+
const optionsKey = [
|
|
1614
|
+
bookOptions.fontFamily,
|
|
1615
|
+
bookOptions.fontSize,
|
|
1616
|
+
bookOptions.lineSpacing,
|
|
1617
|
+
bookOptions.mode,
|
|
1618
|
+
bookOptions.enableHanging
|
|
1619
|
+
].join("|");
|
|
1620
|
+
const optionsKeyRef = useRef9(optionsKey);
|
|
1621
|
+
const bookOptionsRef = useRef9(bookOptions);
|
|
1622
|
+
bookOptionsRef.current = bookOptions;
|
|
1623
|
+
const recomputeRef = useRef9(layoutCtx.recompute);
|
|
1624
|
+
recomputeRef.current = layoutCtx.recompute;
|
|
1625
|
+
useEffect6(() => {
|
|
1626
|
+
if (optionsKeyRef.current === optionsKey) return;
|
|
1627
|
+
optionsKeyRef.current = optionsKey;
|
|
1628
|
+
const timer = setTimeout(() => {
|
|
1629
|
+
void (async () => {
|
|
1630
|
+
await setOptions({ ...bookOptionsRef.current });
|
|
1631
|
+
await recomputeRef.current({ blank: false });
|
|
1632
|
+
})();
|
|
1633
|
+
}, 60);
|
|
1634
|
+
return () => clearTimeout(timer);
|
|
1635
|
+
}, [optionsKey, setOptions]);
|
|
1636
|
+
const geometryKey = JSON.stringify(resolvedGeometry ?? null);
|
|
1637
|
+
const geometryKeyRef = useRef9(geometryKey);
|
|
1638
|
+
useEffect6(() => {
|
|
1639
|
+
if (geometryKeyRef.current === geometryKey) return;
|
|
1640
|
+
geometryKeyRef.current = geometryKey;
|
|
1641
|
+
void recomputeRef.current({ blank: false });
|
|
1642
|
+
}, [geometryKey]);
|
|
1588
1643
|
const imageCtx = useMultiImageOverlay(layoutCtx.layout, spreadCtx.spreadIdx, {
|
|
1589
1644
|
onUpdate: () => spreadCtx.refresh()
|
|
1590
1645
|
});
|
|
@@ -1607,18 +1662,22 @@ function MejiroReaderInner(props, ref) {
|
|
|
1607
1662
|
onLoadRef.current = onLoad;
|
|
1608
1663
|
useEffect6(() => {
|
|
1609
1664
|
if (!controlled) return;
|
|
1610
|
-
if (
|
|
1665
|
+
if (chapterIsUncontrolled) setChapterState(0);
|
|
1611
1666
|
clearImagesRef.current();
|
|
1612
1667
|
book.clearCache();
|
|
1613
|
-
if (
|
|
1614
|
-
}, [controlled,
|
|
1668
|
+
if (e) onLoadRef.current?.(e);
|
|
1669
|
+
}, [controlled, e, book, chapterIsUncontrolled]);
|
|
1615
1670
|
const spreadGoToRef = useRef9(spreadCtx.goTo);
|
|
1616
1671
|
spreadGoToRef.current = spreadCtx.goTo;
|
|
1617
1672
|
useEffect6(() => {
|
|
1618
1673
|
if (spreadIdxProp == null) return;
|
|
1619
|
-
if (spreadIdxProp === spreadCtx.spreadIdx) return;
|
|
1620
1674
|
spreadGoToRef.current(spreadIdxProp);
|
|
1621
|
-
}, [spreadIdxProp
|
|
1675
|
+
}, [spreadIdxProp]);
|
|
1676
|
+
useLayoutEffect4(() => {
|
|
1677
|
+
const next = spreadIdxPropRef.current;
|
|
1678
|
+
if (next == null || !layoutCtx.layout) return;
|
|
1679
|
+
setSpreadRef.current(next);
|
|
1680
|
+
}, [layoutCtx.layout]);
|
|
1622
1681
|
const onChapter = useCallback6(
|
|
1623
1682
|
(i) => {
|
|
1624
1683
|
if (i === chapter) return;
|
|
@@ -1788,20 +1847,30 @@ function MejiroReaderInner(props, ref) {
|
|
|
1788
1847
|
const layoutReady = e && spreadCtx.spread && layoutCtx.layout && layoutCtx.pageWidth > 0;
|
|
1789
1848
|
const themeName = typeof theme === "string" ? theme : theme.name;
|
|
1790
1849
|
const themeOverride = typeof theme === "string" ? void 0 : theme.override;
|
|
1791
|
-
const themeStyle =
|
|
1850
|
+
const themeStyle = useMemo4(() => {
|
|
1851
|
+
const pad = pageGeometryProp?.padding;
|
|
1852
|
+
const styleVars = {};
|
|
1853
|
+
if (pad?.x != null) styleVars["--mejiro-page-pad-x"] = `${pad.x}px`;
|
|
1854
|
+
if (pad?.y != null) styleVars["--mejiro-page-pad-y"] = `${pad.y}px`;
|
|
1855
|
+
if (pad?.bottom != null) styleVars["--mejiro-page-pad-bottom"] = `${pad.bottom}px`;
|
|
1856
|
+
if (fit === "width") styleVars["--mejiro-surface-aspect"] = surfaceAspect;
|
|
1857
|
+
const hasVars = Object.keys(styleVars).length > 0;
|
|
1858
|
+
if (!(themeOverride || hasVars)) return void 0;
|
|
1859
|
+
return { ...themeOverride, ...styleVars };
|
|
1860
|
+
}, [themeOverride, pageGeometryProp, fit, surfaceAspect]);
|
|
1792
1861
|
const resolvedMessages = useI18n({ locale, messages });
|
|
1793
1862
|
const effectiveSubtitle = subtitle ?? resolvedMessages.logoSubtitle;
|
|
1794
|
-
const defaultLogo = /* @__PURE__ */
|
|
1795
|
-
/* @__PURE__ */
|
|
1796
|
-
effectiveSubtitle && /* @__PURE__ */
|
|
1863
|
+
const defaultLogo = /* @__PURE__ */ jsxs8("div", { className: "mejiro-reader-logo", children: [
|
|
1864
|
+
/* @__PURE__ */ jsx13("span", { className: "mejiro-reader-logo-mark", children: title }),
|
|
1865
|
+
effectiveSubtitle && /* @__PURE__ */ jsx13("span", { className: "mejiro-reader-logo-sub", children: effectiveSubtitle })
|
|
1797
1866
|
] });
|
|
1798
|
-
const header = enableHeader ? /* @__PURE__ */
|
|
1799
|
-
/* @__PURE__ */
|
|
1867
|
+
const header = enableHeader ? /* @__PURE__ */ jsxs8("header", { className: "mejiro-reader-header", children: [
|
|
1868
|
+
/* @__PURE__ */ jsxs8("div", { className: "mejiro-reader-header-left", children: [
|
|
1800
1869
|
logo === void 0 ? defaultLogo : logo,
|
|
1801
|
-
showChapterSelect && /* @__PURE__ */
|
|
1870
|
+
showChapterSelect && /* @__PURE__ */ jsx13(MejiroChapterNav, { epub: e, chapter, onChange: onChapter })
|
|
1802
1871
|
] }),
|
|
1803
|
-
/* @__PURE__ */
|
|
1804
|
-
enableStats && /* @__PURE__ */
|
|
1872
|
+
/* @__PURE__ */ jsxs8("div", { className: "mejiro-reader-header-actions", children: [
|
|
1873
|
+
enableStats && /* @__PURE__ */ jsx13(
|
|
1805
1874
|
MejiroStats,
|
|
1806
1875
|
{
|
|
1807
1876
|
chapter: e?.chapters[chapter] ?? null,
|
|
@@ -1810,7 +1879,7 @@ function MejiroReaderInner(props, ref) {
|
|
|
1810
1879
|
fontLabel
|
|
1811
1880
|
}
|
|
1812
1881
|
),
|
|
1813
|
-
enableDropZone && /* @__PURE__ */
|
|
1882
|
+
enableDropZone && /* @__PURE__ */ jsx13(
|
|
1814
1883
|
"button",
|
|
1815
1884
|
{
|
|
1816
1885
|
type: "button",
|
|
@@ -1819,7 +1888,7 @@ function MejiroReaderInner(props, ref) {
|
|
|
1819
1888
|
children: resolvedMessages.openButton
|
|
1820
1889
|
}
|
|
1821
1890
|
),
|
|
1822
|
-
enableImageOverlay && e && /* @__PURE__ */
|
|
1891
|
+
enableImageOverlay && e && /* @__PURE__ */ jsx13(
|
|
1823
1892
|
"button",
|
|
1824
1893
|
{
|
|
1825
1894
|
type: "button",
|
|
@@ -1828,7 +1897,7 @@ function MejiroReaderInner(props, ref) {
|
|
|
1828
1897
|
children: resolvedMessages.imageButton
|
|
1829
1898
|
}
|
|
1830
1899
|
),
|
|
1831
|
-
enableSettings && /* @__PURE__ */
|
|
1900
|
+
enableSettings && /* @__PURE__ */ jsxs8(
|
|
1832
1901
|
"button",
|
|
1833
1902
|
{
|
|
1834
1903
|
type: "button",
|
|
@@ -1836,7 +1905,7 @@ function MejiroReaderInner(props, ref) {
|
|
|
1836
1905
|
onClick: () => setSettingsOpen((v) => !v),
|
|
1837
1906
|
children: [
|
|
1838
1907
|
resolvedMessages.settingsButton,
|
|
1839
|
-
/* @__PURE__ */
|
|
1908
|
+
/* @__PURE__ */ jsx13("span", { className: "mejiro-reader-btn-arrow", children: "\u25BE" })
|
|
1840
1909
|
]
|
|
1841
1910
|
}
|
|
1842
1911
|
)
|
|
@@ -1846,15 +1915,22 @@ function MejiroReaderInner(props, ref) {
|
|
|
1846
1915
|
const rightPage = currentSpread * 2 + 1;
|
|
1847
1916
|
const leftPage = currentSpread * 2 + 2;
|
|
1848
1917
|
const showLeft = spreadCtx.spread != null && leftPage <= spreadCtx.spread.totalPages;
|
|
1849
|
-
|
|
1918
|
+
const showRightNum = pageNumbers === "both" || pageNumbers === "right";
|
|
1919
|
+
const showLeftNum = pageNumbers === "both" || pageNumbers === "left";
|
|
1920
|
+
return /* @__PURE__ */ jsx13(MejiroI18nProvider, { messages: resolvedMessages, children: /* @__PURE__ */ jsxs8(
|
|
1850
1921
|
"div",
|
|
1851
1922
|
{
|
|
1852
|
-
className: `mejiro-reader${chromeHidden ? " mejiro-reader--chrome-hidden" : ""}`,
|
|
1923
|
+
className: `mejiro-reader${chromeHidden ? " mejiro-reader--chrome-hidden" : ""}${fit === "width" ? " mejiro-reader--fit-width" : ""}`,
|
|
1853
1924
|
"data-mejiro-theme": themeName,
|
|
1854
1925
|
style: themeStyle,
|
|
1855
1926
|
children: [
|
|
1856
1927
|
header,
|
|
1857
|
-
enableSettings && /* @__PURE__ */
|
|
1928
|
+
enableSettings && (renderSettings ? /* @__PURE__ */ jsx13("div", { className: `mejiro-reader-settings-panel${settingsOpen ? " is-open" : ""}`, children: /* @__PURE__ */ jsx13("div", { className: "mejiro-reader-settings-inner", children: /* @__PURE__ */ jsx13("div", { className: "mejiro-reader-settings-content", children: renderSettings({
|
|
1929
|
+
settings: editable,
|
|
1930
|
+
update: setOptions,
|
|
1931
|
+
open: settingsOpen,
|
|
1932
|
+
toggle: () => setSettingsOpen((v) => !v)
|
|
1933
|
+
}) }) }) }) : /* @__PURE__ */ jsx13(
|
|
1858
1934
|
MejiroSettingsPanel,
|
|
1859
1935
|
{
|
|
1860
1936
|
open: settingsOpen,
|
|
@@ -1862,14 +1938,14 @@ function MejiroReaderInner(props, ref) {
|
|
|
1862
1938
|
fonts,
|
|
1863
1939
|
onChange: setOptions
|
|
1864
1940
|
}
|
|
1865
|
-
),
|
|
1866
|
-
/* @__PURE__ */
|
|
1867
|
-
showChapterPanel && /* @__PURE__ */
|
|
1868
|
-
/* @__PURE__ */
|
|
1869
|
-
!(e || epubCtx.loading) && enableDropZone && /* @__PURE__ */
|
|
1870
|
-
epubCtx.loading && /* @__PURE__ */
|
|
1871
|
-
!layoutReady && fallback && /* @__PURE__ */
|
|
1872
|
-
layoutReady && mode === "scroll" && layoutCtx.layout && /* @__PURE__ */
|
|
1941
|
+
)),
|
|
1942
|
+
/* @__PURE__ */ jsxs8("div", { className: `mejiro-reader-body${showChapterPanel ? " has-chapter-panel" : ""}`, children: [
|
|
1943
|
+
showChapterPanel && /* @__PURE__ */ jsx13(MejiroChapterNav, { epub: e, chapter, onChange: onChapter, variant: "panel" }),
|
|
1944
|
+
/* @__PURE__ */ jsxs8("div", { ref: surfaceRef, className: "mejiro-reader-surface", children: [
|
|
1945
|
+
!(e || epubCtx.loading) && enableDropZone && /* @__PURE__ */ jsx13(MejiroDropZone, { onFile: (f) => void epubCtx.loadFile(f) }),
|
|
1946
|
+
epubCtx.loading && /* @__PURE__ */ jsx13("div", { className: "mejiro-reader-loading", children: resolvedMessages.loading }),
|
|
1947
|
+
!layoutReady && fallback && /* @__PURE__ */ jsx13("div", { className: "mejiro-reader-fallback", children: fallback }),
|
|
1948
|
+
layoutReady && mode === "scroll" && layoutCtx.layout && /* @__PURE__ */ jsx13(
|
|
1873
1949
|
MejiroScrollView,
|
|
1874
1950
|
{
|
|
1875
1951
|
layout: layoutCtx.layout,
|
|
@@ -1880,13 +1956,14 @@ function MejiroReaderInner(props, ref) {
|
|
|
1880
1956
|
fontSize: bookOptions.fontSize,
|
|
1881
1957
|
lineSpacing: bookOptions.lineSpacing,
|
|
1882
1958
|
scrollToPage: spreadCtx.spreadIdx * 2,
|
|
1883
|
-
onVisiblePageChange: (pageIdx) => {
|
|
1959
|
+
onVisiblePageChange: (pageIdx, source) => {
|
|
1960
|
+
if (source === "programmatic") return;
|
|
1884
1961
|
const target = Math.floor(pageIdx / 2);
|
|
1885
|
-
if (target !== spreadCtx.spreadIdx)
|
|
1962
|
+
if (target !== spreadCtx.spreadIdx) spreadCtx.setSpread(target);
|
|
1886
1963
|
}
|
|
1887
1964
|
}
|
|
1888
1965
|
),
|
|
1889
|
-
layoutReady && mode === "paginated" && spreadCtx.spread && /* @__PURE__ */
|
|
1966
|
+
layoutReady && mode === "paginated" && spreadCtx.spread && /* @__PURE__ */ jsx13(
|
|
1890
1967
|
MejiroSpread,
|
|
1891
1968
|
{
|
|
1892
1969
|
singlePage: effectiveSingle,
|
|
@@ -1900,13 +1977,16 @@ function MejiroReaderInner(props, ref) {
|
|
|
1900
1977
|
fontSize: bookOptions.fontSize,
|
|
1901
1978
|
lineSpacing: bookOptions.lineSpacing,
|
|
1902
1979
|
turning: spreadCtx.turning,
|
|
1903
|
-
rightHeader: {
|
|
1980
|
+
rightHeader: {
|
|
1981
|
+
title: runningTitleRight,
|
|
1982
|
+
pageNumber: showRightNum ? rightPage : null
|
|
1983
|
+
},
|
|
1904
1984
|
leftHeader: {
|
|
1905
1985
|
title: runningTitleLeft,
|
|
1906
|
-
pageNumber: showLeft ? leftPage : null
|
|
1986
|
+
pageNumber: showLeft && showLeftNum ? leftPage : null
|
|
1907
1987
|
},
|
|
1908
1988
|
images: imageCtx.currentImages,
|
|
1909
|
-
indicator: enablePageIndicator ? /* @__PURE__ */
|
|
1989
|
+
indicator: enablePageIndicator ? /* @__PURE__ */ jsx13(
|
|
1910
1990
|
MejiroPageIndicator,
|
|
1911
1991
|
{
|
|
1912
1992
|
current: spreadCtx.spreadIdx + 1,
|
|
@@ -1924,7 +2004,7 @@ function MejiroReaderInner(props, ref) {
|
|
|
1924
2004
|
)
|
|
1925
2005
|
] })
|
|
1926
2006
|
] }),
|
|
1927
|
-
/* @__PURE__ */
|
|
2007
|
+
/* @__PURE__ */ jsx13(
|
|
1928
2008
|
"input",
|
|
1929
2009
|
{
|
|
1930
2010
|
ref: fileRef,
|
|
@@ -1937,7 +2017,7 @@ function MejiroReaderInner(props, ref) {
|
|
|
1937
2017
|
}
|
|
1938
2018
|
}
|
|
1939
2019
|
),
|
|
1940
|
-
/* @__PURE__ */
|
|
2020
|
+
/* @__PURE__ */ jsx13("div", { className: "mejiro-reader-sr-only", role: "status", "aria-live": "polite", children: spreadCtx.totalSpreads > 0 ? format(resolvedMessages.spreadAnnouncement, {
|
|
1941
2021
|
spread: spreadCtx.spreadIdx + 1,
|
|
1942
2022
|
total: spreadCtx.totalSpreads
|
|
1943
2023
|
}) : "" })
|
|
@@ -1949,7 +2029,7 @@ var MejiroReader = forwardRef(MejiroReaderInner);
|
|
|
1949
2029
|
MejiroReader.displayName = "MejiroReader";
|
|
1950
2030
|
|
|
1951
2031
|
// src/MejiroEditor.tsx
|
|
1952
|
-
import { Fragment as
|
|
2032
|
+
import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1953
2033
|
function cloneBook(book) {
|
|
1954
2034
|
return {
|
|
1955
2035
|
...book,
|
|
@@ -2025,8 +2105,11 @@ function MejiroEditor({
|
|
|
2025
2105
|
const book = editor?.book ?? null;
|
|
2026
2106
|
const chapter = book?.chapters[selection.chapter] ?? null;
|
|
2027
2107
|
const paragraph = chapter?.paragraphs[selection.paragraph] ?? null;
|
|
2028
|
-
const
|
|
2029
|
-
|
|
2108
|
+
const textDirty = paragraph ? text !== paragraph.text : false;
|
|
2109
|
+
const previewBook = useMemo5(() => {
|
|
2110
|
+
void revision;
|
|
2111
|
+
return book ? cloneBook(book) : null;
|
|
2112
|
+
}, [book, revision]);
|
|
2030
2113
|
useEffect7(() => {
|
|
2031
2114
|
onLoadRef.current = onLoad;
|
|
2032
2115
|
}, [onLoad]);
|
|
@@ -2120,7 +2203,7 @@ function MejiroEditor({
|
|
|
2120
2203
|
setRevision((value) => value + 1);
|
|
2121
2204
|
}
|
|
2122
2205
|
function applyRuby() {
|
|
2123
|
-
if (!(editor && paragraph && rubyText.trim())) return;
|
|
2206
|
+
if (!(editor && paragraph && rubyText.trim()) || textDirty) return;
|
|
2124
2207
|
const len = [...text].length;
|
|
2125
2208
|
const start = Math.max(0, Math.min(rubyStart, len));
|
|
2126
2209
|
const end = Math.max(start + 1, Math.min(rubyEnd, len));
|
|
@@ -2171,9 +2254,9 @@ function MejiroEditor({
|
|
|
2171
2254
|
a.click();
|
|
2172
2255
|
URL.revokeObjectURL(url);
|
|
2173
2256
|
}
|
|
2174
|
-
return /* @__PURE__ */
|
|
2175
|
-
/* @__PURE__ */
|
|
2176
|
-
previewBook ? /* @__PURE__ */
|
|
2257
|
+
return /* @__PURE__ */ jsxs9("div", { className: "mejiro-editor", children: [
|
|
2258
|
+
/* @__PURE__ */ jsxs9("main", { className: "mejiro-editor-preview", children: [
|
|
2259
|
+
previewBook ? /* @__PURE__ */ jsx14(
|
|
2177
2260
|
MejiroReader,
|
|
2178
2261
|
{
|
|
2179
2262
|
epub: previewBook,
|
|
@@ -2183,38 +2266,38 @@ function MejiroEditor({
|
|
|
2183
2266
|
enableImageOverlay: false,
|
|
2184
2267
|
enableSurfaceTap: false
|
|
2185
2268
|
}
|
|
2186
|
-
) : /* @__PURE__ */
|
|
2187
|
-
loading && /* @__PURE__ */
|
|
2188
|
-
error && /* @__PURE__ */
|
|
2269
|
+
) : /* @__PURE__ */ jsx14(MejiroDropZone, { onFile: (file) => void loadFile(file) }),
|
|
2270
|
+
loading && /* @__PURE__ */ jsx14("div", { className: "mejiro-editor-loading", children: messages.loading }),
|
|
2271
|
+
error && /* @__PURE__ */ jsx14("div", { className: "mejiro-editor-error", children: error.message })
|
|
2189
2272
|
] }),
|
|
2190
|
-
/* @__PURE__ */
|
|
2191
|
-
/* @__PURE__ */
|
|
2192
|
-
/* @__PURE__ */
|
|
2193
|
-
/* @__PURE__ */
|
|
2194
|
-
editor?.author && /* @__PURE__ */
|
|
2273
|
+
/* @__PURE__ */ jsxs9("aside", { className: "mejiro-editor-panel", children: [
|
|
2274
|
+
/* @__PURE__ */ jsxs9("div", { className: "mejiro-editor-head", children: [
|
|
2275
|
+
/* @__PURE__ */ jsx14("span", { children: messages.editorTitle }),
|
|
2276
|
+
/* @__PURE__ */ jsx14("strong", { children: editor?.title ?? messages.editorNoBookLoaded }),
|
|
2277
|
+
editor?.author && /* @__PURE__ */ jsx14("small", { children: editor.author })
|
|
2195
2278
|
] }),
|
|
2196
|
-
book && /* @__PURE__ */
|
|
2197
|
-
/* @__PURE__ */
|
|
2198
|
-
/* @__PURE__ */
|
|
2199
|
-
/* @__PURE__ */
|
|
2200
|
-
(ch, ci) => ch.blocks.filter((b) => b.kind === "paragraph").map((block, pi) => /* @__PURE__ */
|
|
2279
|
+
book && /* @__PURE__ */ jsxs9(Fragment3, { children: [
|
|
2280
|
+
/* @__PURE__ */ jsxs9("div", { className: "mejiro-editor-section", children: [
|
|
2281
|
+
/* @__PURE__ */ jsx14("span", { className: "mejiro-editor-label", children: messages.editorParagraphs }),
|
|
2282
|
+
/* @__PURE__ */ jsx14("div", { className: "mejiro-editor-paragraphs", children: book.chapters.map(
|
|
2283
|
+
(ch, ci) => ch.blocks.filter((b) => b.kind === "paragraph").map((block, pi) => /* @__PURE__ */ jsxs9(
|
|
2201
2284
|
"button",
|
|
2202
2285
|
{
|
|
2203
2286
|
type: "button",
|
|
2204
2287
|
className: selection.chapter === ci && selection.paragraph === pi ? "is-active" : "",
|
|
2205
2288
|
onClick: () => selectParagraph(ci, pi),
|
|
2206
2289
|
children: [
|
|
2207
|
-
/* @__PURE__ */
|
|
2208
|
-
/* @__PURE__ */
|
|
2290
|
+
/* @__PURE__ */ jsx14("span", { children: ch.title ?? format(messages.chapterN, { n: ci + 1 }) }),
|
|
2291
|
+
/* @__PURE__ */ jsx14("strong", { children: block.text.slice(0, 42) })
|
|
2209
2292
|
]
|
|
2210
2293
|
},
|
|
2211
2294
|
`${ch.href}-${block.id}`
|
|
2212
2295
|
))
|
|
2213
2296
|
) })
|
|
2214
2297
|
] }),
|
|
2215
|
-
enableProofread && /* @__PURE__ */
|
|
2216
|
-
/* @__PURE__ */
|
|
2217
|
-
/* @__PURE__ */
|
|
2298
|
+
enableProofread && /* @__PURE__ */ jsxs9("div", { className: "mejiro-editor-section", children: [
|
|
2299
|
+
/* @__PURE__ */ jsx14("span", { className: "mejiro-editor-label", children: messages.editorProofread }),
|
|
2300
|
+
/* @__PURE__ */ jsx14(
|
|
2218
2301
|
"textarea",
|
|
2219
2302
|
{
|
|
2220
2303
|
ref: textareaRef,
|
|
@@ -2223,17 +2306,17 @@ function MejiroEditor({
|
|
|
2223
2306
|
onSelect: captureRubyRange
|
|
2224
2307
|
}
|
|
2225
2308
|
),
|
|
2226
|
-
/* @__PURE__ */
|
|
2309
|
+
/* @__PURE__ */ jsx14("button", { type: "button", className: "mejiro-editor-primary", onClick: applyText, children: messages.editorApplyText })
|
|
2227
2310
|
] }),
|
|
2228
|
-
enableRuby && /* @__PURE__ */
|
|
2229
|
-
/* @__PURE__ */
|
|
2230
|
-
/* @__PURE__ */
|
|
2231
|
-
/* @__PURE__ */
|
|
2311
|
+
enableRuby && /* @__PURE__ */ jsxs9("div", { className: "mejiro-editor-section", children: [
|
|
2312
|
+
/* @__PURE__ */ jsx14("span", { className: "mejiro-editor-label", children: messages.editorRuby }),
|
|
2313
|
+
/* @__PURE__ */ jsx14("p", { className: "mejiro-editor-hint", children: messages.editorRubyHint }),
|
|
2314
|
+
/* @__PURE__ */ jsx14("p", { className: "mejiro-editor-range", children: format(messages.editorRubyRange, {
|
|
2232
2315
|
start: rubyStart,
|
|
2233
2316
|
end: rubyEnd,
|
|
2234
2317
|
count: Math.max(0, rubyEnd - rubyStart)
|
|
2235
2318
|
}) }),
|
|
2236
|
-
/* @__PURE__ */
|
|
2319
|
+
/* @__PURE__ */ jsx14(
|
|
2237
2320
|
"input",
|
|
2238
2321
|
{
|
|
2239
2322
|
value: rubyText,
|
|
@@ -2241,12 +2324,21 @@ function MejiroEditor({
|
|
|
2241
2324
|
onChange: (event) => setRubyText(event.target.value)
|
|
2242
2325
|
}
|
|
2243
2326
|
),
|
|
2244
|
-
/* @__PURE__ */
|
|
2327
|
+
/* @__PURE__ */ jsx14(
|
|
2328
|
+
"button",
|
|
2329
|
+
{
|
|
2330
|
+
type: "button",
|
|
2331
|
+
className: "mejiro-editor-primary",
|
|
2332
|
+
disabled: textDirty || !rubyText.trim(),
|
|
2333
|
+
onClick: applyRuby,
|
|
2334
|
+
children: messages.editorApplyRuby
|
|
2335
|
+
}
|
|
2336
|
+
)
|
|
2245
2337
|
] }),
|
|
2246
|
-
enableImages && /* @__PURE__ */
|
|
2247
|
-
/* @__PURE__ */
|
|
2248
|
-
/* @__PURE__ */
|
|
2249
|
-
/* @__PURE__ */
|
|
2338
|
+
enableImages && /* @__PURE__ */ jsxs9("div", { className: "mejiro-editor-section", children: [
|
|
2339
|
+
/* @__PURE__ */ jsx14("span", { className: "mejiro-editor-label", children: messages.editorImages }),
|
|
2340
|
+
/* @__PURE__ */ jsx14("button", { type: "button", onClick: () => imageInputRef.current?.click(), children: messages.editorInsertImageAfterParagraph }),
|
|
2341
|
+
/* @__PURE__ */ jsx14(
|
|
2250
2342
|
"input",
|
|
2251
2343
|
{
|
|
2252
2344
|
ref: imageInputRef,
|
|
@@ -2260,7 +2352,7 @@ function MejiroEditor({
|
|
|
2260
2352
|
}
|
|
2261
2353
|
)
|
|
2262
2354
|
] }),
|
|
2263
|
-
enableExport && /* @__PURE__ */
|
|
2355
|
+
enableExport && /* @__PURE__ */ jsx14(
|
|
2264
2356
|
"button",
|
|
2265
2357
|
{
|
|
2266
2358
|
type: "button",
|
|
@@ -2285,35 +2377,189 @@ function paragraphBlockId(chapter, paragraphIndex) {
|
|
|
2285
2377
|
|
|
2286
2378
|
// src/MejiroManuscriptEditor.tsx
|
|
2287
2379
|
import { EpubProject } from "@libraz/mejiro/epub";
|
|
2288
|
-
import { useCallback as useCallback9, useEffect as useEffect9, useRef as
|
|
2380
|
+
import { useCallback as useCallback9, useEffect as useEffect9, useRef as useRef13, useState as useState11 } from "react";
|
|
2381
|
+
|
|
2382
|
+
// src/MejiroNotationHighlighter.tsx
|
|
2383
|
+
import { tokenizeManuscriptSource } from "@libraz/mejiro";
|
|
2384
|
+
import {
|
|
2385
|
+
forwardRef as forwardRef2,
|
|
2386
|
+
useImperativeHandle as useImperativeHandle2,
|
|
2387
|
+
useMemo as useMemo6,
|
|
2388
|
+
useRef as useRef11,
|
|
2389
|
+
useState as useState9
|
|
2390
|
+
} from "react";
|
|
2391
|
+
import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2392
|
+
var MejiroNotationHighlighter = forwardRef2(function MejiroNotationHighlighter2({
|
|
2393
|
+
value,
|
|
2394
|
+
onChange,
|
|
2395
|
+
dialect = "mejiro",
|
|
2396
|
+
wrapperClassName,
|
|
2397
|
+
wrapperStyle,
|
|
2398
|
+
className,
|
|
2399
|
+
style,
|
|
2400
|
+
onScroll,
|
|
2401
|
+
onCompositionStart,
|
|
2402
|
+
onCompositionEnd,
|
|
2403
|
+
...rest
|
|
2404
|
+
}, ref) {
|
|
2405
|
+
const textareaRef = useRef11(null);
|
|
2406
|
+
const overlayRef = useRef11(null);
|
|
2407
|
+
useImperativeHandle2(ref, () => textareaRef.current, []);
|
|
2408
|
+
const [isComposing, setIsComposing] = useState9(false);
|
|
2409
|
+
const lastSegmentsRef = useRef11(null);
|
|
2410
|
+
const segments = useMemo6(() => {
|
|
2411
|
+
if (isComposing && lastSegmentsRef.current) return lastSegmentsRef.current;
|
|
2412
|
+
const next = buildSegments(value, dialect);
|
|
2413
|
+
lastSegmentsRef.current = next;
|
|
2414
|
+
return next;
|
|
2415
|
+
}, [value, dialect, isComposing]);
|
|
2416
|
+
return /* @__PURE__ */ jsxs10(
|
|
2417
|
+
"div",
|
|
2418
|
+
{
|
|
2419
|
+
className: `mejiro-notation-highlighter${wrapperClassName ? ` ${wrapperClassName}` : ""}`,
|
|
2420
|
+
style: wrapperStyle,
|
|
2421
|
+
children: [
|
|
2422
|
+
/* @__PURE__ */ jsxs10(
|
|
2423
|
+
"div",
|
|
2424
|
+
{
|
|
2425
|
+
ref: overlayRef,
|
|
2426
|
+
className: "mejiro-notation-overlay",
|
|
2427
|
+
"aria-hidden": "true",
|
|
2428
|
+
children: [
|
|
2429
|
+
segments.map((segment) => /* @__PURE__ */ jsx15(
|
|
2430
|
+
"span",
|
|
2431
|
+
{
|
|
2432
|
+
className: segment.kind ? `mejiro-notation-token mejiro-notation-${segment.kind}` : void 0,
|
|
2433
|
+
"data-token": segment.kind,
|
|
2434
|
+
children: segment.text
|
|
2435
|
+
},
|
|
2436
|
+
segment.key
|
|
2437
|
+
)),
|
|
2438
|
+
"\u200B"
|
|
2439
|
+
]
|
|
2440
|
+
}
|
|
2441
|
+
),
|
|
2442
|
+
/* @__PURE__ */ jsx15(
|
|
2443
|
+
"textarea",
|
|
2444
|
+
{
|
|
2445
|
+
...rest,
|
|
2446
|
+
ref: textareaRef,
|
|
2447
|
+
className: `mejiro-notation-textarea${className ? ` ${className}` : ""}`,
|
|
2448
|
+
style,
|
|
2449
|
+
value,
|
|
2450
|
+
onChange: (event) => onChange(event.target.value),
|
|
2451
|
+
onCompositionStart: (event) => {
|
|
2452
|
+
setIsComposing(true);
|
|
2453
|
+
onCompositionStart?.(event);
|
|
2454
|
+
},
|
|
2455
|
+
onCompositionEnd: (event) => {
|
|
2456
|
+
setIsComposing(false);
|
|
2457
|
+
onCompositionEnd?.(event);
|
|
2458
|
+
},
|
|
2459
|
+
onScroll: (event) => {
|
|
2460
|
+
if (overlayRef.current) {
|
|
2461
|
+
overlayRef.current.scrollTop = event.currentTarget.scrollTop;
|
|
2462
|
+
overlayRef.current.scrollLeft = event.currentTarget.scrollLeft;
|
|
2463
|
+
}
|
|
2464
|
+
onScroll?.(event);
|
|
2465
|
+
},
|
|
2466
|
+
spellCheck: false
|
|
2467
|
+
}
|
|
2468
|
+
)
|
|
2469
|
+
]
|
|
2470
|
+
}
|
|
2471
|
+
);
|
|
2472
|
+
});
|
|
2473
|
+
function buildSegments(text, dialect) {
|
|
2474
|
+
const tokens = tokenizeManuscriptSource(text, dialect);
|
|
2475
|
+
const segments = [];
|
|
2476
|
+
let cursor = 0;
|
|
2477
|
+
for (const [i, token] of tokens.entries()) {
|
|
2478
|
+
if (token.start > cursor) {
|
|
2479
|
+
segments.push({ key: `t-${i}-pre`, text: text.slice(cursor, token.start) });
|
|
2480
|
+
}
|
|
2481
|
+
segments.push({
|
|
2482
|
+
key: `t-${i}`,
|
|
2483
|
+
text: text.slice(token.start, token.end),
|
|
2484
|
+
kind: token.kind
|
|
2485
|
+
});
|
|
2486
|
+
cursor = token.end;
|
|
2487
|
+
}
|
|
2488
|
+
if (cursor < text.length) {
|
|
2489
|
+
segments.push({ key: "tail", text: text.slice(cursor) });
|
|
2490
|
+
}
|
|
2491
|
+
if (segments.length === 0) {
|
|
2492
|
+
segments.push({ key: "empty", text });
|
|
2493
|
+
}
|
|
2494
|
+
return segments;
|
|
2495
|
+
}
|
|
2289
2496
|
|
|
2290
2497
|
// src/useManuscriptDraft.ts
|
|
2291
|
-
import { useCallback as useCallback8, useEffect as useEffect8, useRef as
|
|
2498
|
+
import { useCallback as useCallback8, useEffect as useEffect8, useRef as useRef12, useState as useState10 } from "react";
|
|
2292
2499
|
var DEFAULT_DELAY = 800;
|
|
2293
|
-
function defaultChapter(index) {
|
|
2294
|
-
return { id: `chapter-${Date.now()}-${index}`, title:
|
|
2500
|
+
function defaultChapter(index, titleFor = (i) => `\u7B2C${i + 1}\u8A71`, bodyFor = () => "") {
|
|
2501
|
+
return { id: `chapter-${Date.now()}-${index}`, title: titleFor(index), body: bodyFor(index) };
|
|
2295
2502
|
}
|
|
2296
2503
|
function useManuscriptDraft(options = {}) {
|
|
2297
|
-
const {
|
|
2298
|
-
|
|
2299
|
-
|
|
2504
|
+
const {
|
|
2505
|
+
onAutosave,
|
|
2506
|
+
autosaveDelay = DEFAULT_DELAY,
|
|
2507
|
+
autosavePayload,
|
|
2508
|
+
autosaveKey,
|
|
2509
|
+
defaultChapterTitle,
|
|
2510
|
+
defaultChapterBody
|
|
2511
|
+
} = options;
|
|
2512
|
+
const titleForRef = useRef12(defaultChapterTitle);
|
|
2513
|
+
titleForRef.current = defaultChapterTitle;
|
|
2514
|
+
const bodyForRef = useRef12(defaultChapterBody);
|
|
2515
|
+
bodyForRef.current = defaultChapterBody;
|
|
2516
|
+
const [chapters, setChaptersState] = useState10(
|
|
2517
|
+
() => options.initialChapters?.length ? options.initialChapters : [defaultChapter(0, defaultChapterTitle, defaultChapterBody)]
|
|
2300
2518
|
);
|
|
2301
|
-
const [selected, setSelectedState] =
|
|
2302
|
-
const
|
|
2519
|
+
const [selected, setSelectedState] = useState10(0);
|
|
2520
|
+
const [autosaveError, setAutosaveError] = useState10(null);
|
|
2521
|
+
const saveRef = useRef12(onAutosave);
|
|
2303
2522
|
saveRef.current = onAutosave;
|
|
2304
|
-
const
|
|
2523
|
+
const payloadRef = useRef12(autosavePayload);
|
|
2524
|
+
payloadRef.current = autosavePayload;
|
|
2525
|
+
const chaptersRef = useRef12(chapters);
|
|
2526
|
+
chaptersRef.current = chapters;
|
|
2527
|
+
const didMountRef = useRef12(false);
|
|
2528
|
+
const dirtyRef = useRef12(false);
|
|
2529
|
+
const mountedRef = useRef12(true);
|
|
2530
|
+
const flushAutosave = useCallback8(() => {
|
|
2531
|
+
const callback = saveRef.current;
|
|
2532
|
+
if (!(callback && dirtyRef.current)) return;
|
|
2533
|
+
dirtyRef.current = false;
|
|
2534
|
+
const payload = payloadRef.current ? payloadRef.current(chaptersRef.current) : chaptersRef.current;
|
|
2535
|
+
void Promise.resolve(callback(payload)).catch((err) => {
|
|
2536
|
+
if (!mountedRef.current) return;
|
|
2537
|
+
setAutosaveError(err instanceof Error ? err : new Error(String(err)));
|
|
2538
|
+
});
|
|
2539
|
+
}, []);
|
|
2305
2540
|
useEffect8(() => {
|
|
2306
2541
|
if (!didMountRef.current) {
|
|
2307
2542
|
didMountRef.current = true;
|
|
2308
2543
|
return void 0;
|
|
2309
2544
|
}
|
|
2310
|
-
|
|
2311
|
-
|
|
2545
|
+
if (!saveRef.current) return void 0;
|
|
2546
|
+
dirtyRef.current = true;
|
|
2547
|
+
setAutosaveError(null);
|
|
2312
2548
|
const timer = setTimeout(() => {
|
|
2313
|
-
|
|
2549
|
+
flushAutosave();
|
|
2314
2550
|
}, autosaveDelay);
|
|
2315
2551
|
return () => clearTimeout(timer);
|
|
2316
|
-
}, [chapters, autosaveDelay]);
|
|
2552
|
+
}, [chapters, autosaveKey, autosaveDelay, flushAutosave]);
|
|
2553
|
+
useEffect8(() => {
|
|
2554
|
+
mountedRef.current = true;
|
|
2555
|
+
const handleBeforeUnload = () => flushAutosave();
|
|
2556
|
+
window.addEventListener("beforeunload", handleBeforeUnload);
|
|
2557
|
+
return () => {
|
|
2558
|
+
flushAutosave();
|
|
2559
|
+
mountedRef.current = false;
|
|
2560
|
+
window.removeEventListener("beforeunload", handleBeforeUnload);
|
|
2561
|
+
};
|
|
2562
|
+
}, [flushAutosave]);
|
|
2317
2563
|
const setSelected = useCallback8(
|
|
2318
2564
|
(index) => {
|
|
2319
2565
|
setSelectedState(() => Math.max(0, Math.min(index, chapters.length - 1)));
|
|
@@ -2322,7 +2568,7 @@ function useManuscriptDraft(options = {}) {
|
|
|
2322
2568
|
);
|
|
2323
2569
|
const setChapters = useCallback8(
|
|
2324
2570
|
(next) => {
|
|
2325
|
-
const normalized = next.length ? next : [defaultChapter(0)];
|
|
2571
|
+
const normalized = next.length ? next : [defaultChapter(0, titleForRef.current, bodyForRef.current)];
|
|
2326
2572
|
setChaptersState(normalized);
|
|
2327
2573
|
setSelectedState((current) => {
|
|
2328
2574
|
const selectedId = chapters[current]?.id;
|
|
@@ -2339,7 +2585,7 @@ function useManuscriptDraft(options = {}) {
|
|
|
2339
2585
|
}, []);
|
|
2340
2586
|
const addChapter = useCallback8((chapter = {}) => {
|
|
2341
2587
|
setChaptersState((current) => {
|
|
2342
|
-
const generated = defaultChapter(current.length);
|
|
2588
|
+
const generated = defaultChapter(current.length, titleForRef.current, bodyForRef.current);
|
|
2343
2589
|
const next = [
|
|
2344
2590
|
...current,
|
|
2345
2591
|
{
|
|
@@ -2388,12 +2634,14 @@ function useManuscriptDraft(options = {}) {
|
|
|
2388
2634
|
patchChapter,
|
|
2389
2635
|
addChapter,
|
|
2390
2636
|
removeChapter,
|
|
2391
|
-
reorderChapters
|
|
2637
|
+
reorderChapters,
|
|
2638
|
+
autosaveError,
|
|
2639
|
+
flushAutosave
|
|
2392
2640
|
};
|
|
2393
2641
|
}
|
|
2394
2642
|
|
|
2395
2643
|
// src/MejiroManuscriptEditor.tsx
|
|
2396
|
-
import { jsx as
|
|
2644
|
+
import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2397
2645
|
function downloadEpub(buffer, title) {
|
|
2398
2646
|
const url = URL.createObjectURL(new Blob([buffer], { type: "application/epub+zip" }));
|
|
2399
2647
|
const a = document.createElement("a");
|
|
@@ -2445,14 +2693,14 @@ function MejiroManuscriptEditor({
|
|
|
2445
2693
|
onExport
|
|
2446
2694
|
}) {
|
|
2447
2695
|
const messages = useI18n();
|
|
2448
|
-
const coverInputRef =
|
|
2449
|
-
const bodyTextareaRef =
|
|
2696
|
+
const coverInputRef = useRef13(null);
|
|
2697
|
+
const bodyTextareaRef = useRef13(null);
|
|
2450
2698
|
const titleControlled = onTitleChange !== void 0;
|
|
2451
2699
|
const authorControlled = onAuthorChange !== void 0;
|
|
2452
2700
|
const coverControlled = onCoverChange !== void 0;
|
|
2453
|
-
const [titleState, setTitleState] =
|
|
2454
|
-
const [authorState, setAuthorState] =
|
|
2455
|
-
const [coverState, setCoverState] =
|
|
2701
|
+
const [titleState, setTitleState] = useState11(titleProp ?? messages.manuscriptDefaultTitle);
|
|
2702
|
+
const [authorState, setAuthorState] = useState11(authorProp ?? "");
|
|
2703
|
+
const [coverState, setCoverState] = useState11(coverProp ?? null);
|
|
2456
2704
|
const title = titleControlled ? titleProp ?? "" : titleState;
|
|
2457
2705
|
const author = authorControlled ? authorProp ?? "" : authorState;
|
|
2458
2706
|
const cover = coverControlled ? coverProp ?? null : coverState;
|
|
@@ -2489,7 +2737,10 @@ function MejiroManuscriptEditor({
|
|
|
2489
2737
|
const draft = useManuscriptDraft({
|
|
2490
2738
|
initialChapters: initialChapters?.length ? initialChapters : [defaultChapter2(messages)],
|
|
2491
2739
|
onAutosave,
|
|
2492
|
-
|
|
2740
|
+
autosavePayload: (savedChapters) => ({ title, author, cover, chapters: savedChapters }),
|
|
2741
|
+
autosaveKey: `${title}\0${author}\0${cover?.name ?? ""}\0${cover?.size ?? 0}\0${cover?.lastModified ?? 0}`,
|
|
2742
|
+
autosaveDelay,
|
|
2743
|
+
defaultChapterTitle: (index) => format(messages.manuscriptDefaultChapterTitle, { n: index + 1 })
|
|
2493
2744
|
});
|
|
2494
2745
|
const {
|
|
2495
2746
|
chapters,
|
|
@@ -2501,7 +2752,7 @@ function MejiroManuscriptEditor({
|
|
|
2501
2752
|
reorderChapters
|
|
2502
2753
|
} = draft;
|
|
2503
2754
|
const current = chapters[selected] ?? chapters[0];
|
|
2504
|
-
const [draggingIndex, setDraggingIndex] =
|
|
2755
|
+
const [draggingIndex, setDraggingIndex] = useState11(null);
|
|
2505
2756
|
function wrapSelection(open, close) {
|
|
2506
2757
|
const el = bodyTextareaRef.current;
|
|
2507
2758
|
if (!el) return;
|
|
@@ -2539,8 +2790,8 @@ function MejiroManuscriptEditor({
|
|
|
2539
2790
|
onExport?.(buffer);
|
|
2540
2791
|
downloadEpub(buffer, title);
|
|
2541
2792
|
}, [assetResolver, author, chapters, cover, messages.untitled, onExport, title]);
|
|
2542
|
-
return /* @__PURE__ */
|
|
2543
|
-
/* @__PURE__ */
|
|
2793
|
+
return /* @__PURE__ */ jsxs11("div", { className: "mejiro-editor mejiro-manuscript-editor", children: [
|
|
2794
|
+
/* @__PURE__ */ jsx16("main", { className: "mejiro-editor-preview", children: /* @__PURE__ */ jsx16(
|
|
2544
2795
|
MejiroReader,
|
|
2545
2796
|
{
|
|
2546
2797
|
subtitle: messages.manuscriptPreviewSubtitle,
|
|
@@ -2558,18 +2809,19 @@ function MejiroManuscriptEditor({
|
|
|
2558
2809
|
enableImageOverlay: false
|
|
2559
2810
|
}
|
|
2560
2811
|
) }),
|
|
2561
|
-
/* @__PURE__ */
|
|
2562
|
-
/* @__PURE__ */
|
|
2563
|
-
/* @__PURE__ */
|
|
2564
|
-
/* @__PURE__ */
|
|
2565
|
-
/* @__PURE__ */
|
|
2812
|
+
/* @__PURE__ */ jsxs11("aside", { className: "mejiro-editor-panel", children: [
|
|
2813
|
+
/* @__PURE__ */ jsxs11("div", { className: "mejiro-editor-head", children: [
|
|
2814
|
+
/* @__PURE__ */ jsx16("span", { children: messages.manuscriptTitle }),
|
|
2815
|
+
/* @__PURE__ */ jsx16("strong", { children: title }),
|
|
2816
|
+
/* @__PURE__ */ jsx16("small", { children: messages.manuscriptRubyHint })
|
|
2566
2817
|
] }),
|
|
2567
|
-
/* @__PURE__ */
|
|
2568
|
-
|
|
2569
|
-
/* @__PURE__ */
|
|
2570
|
-
/* @__PURE__ */
|
|
2571
|
-
/* @__PURE__ */
|
|
2572
|
-
/* @__PURE__ */
|
|
2818
|
+
draft.autosaveError && /* @__PURE__ */ jsx16("div", { className: "mejiro-editor-error", children: draft.autosaveError.message }),
|
|
2819
|
+
/* @__PURE__ */ jsxs11("div", { className: "mejiro-editor-section", children: [
|
|
2820
|
+
/* @__PURE__ */ jsx16("span", { className: "mejiro-editor-label", children: messages.manuscriptMetadata }),
|
|
2821
|
+
/* @__PURE__ */ jsx16("input", { value: title, onChange: (event) => setTitle(event.target.value) }),
|
|
2822
|
+
/* @__PURE__ */ jsx16("input", { value: author, onChange: (event) => setAuthor(event.target.value) }),
|
|
2823
|
+
/* @__PURE__ */ jsx16("button", { type: "button", onClick: () => coverInputRef.current?.click(), children: cover ? cover.name : messages.manuscriptChooseCoverImage }),
|
|
2824
|
+
/* @__PURE__ */ jsx16(
|
|
2573
2825
|
"input",
|
|
2574
2826
|
{
|
|
2575
2827
|
ref: coverInputRef,
|
|
@@ -2580,9 +2832,9 @@ function MejiroManuscriptEditor({
|
|
|
2580
2832
|
}
|
|
2581
2833
|
)
|
|
2582
2834
|
] }),
|
|
2583
|
-
/* @__PURE__ */
|
|
2584
|
-
/* @__PURE__ */
|
|
2585
|
-
/* @__PURE__ */
|
|
2835
|
+
/* @__PURE__ */ jsxs11("div", { className: "mejiro-editor-section", children: [
|
|
2836
|
+
/* @__PURE__ */ jsx16("span", { className: "mejiro-editor-label", children: messages.manuscriptChapters }),
|
|
2837
|
+
/* @__PURE__ */ jsx16("div", { className: "mejiro-editor-paragraphs", children: chapters.map((chapter, index) => /* @__PURE__ */ jsxs11(
|
|
2586
2838
|
"button",
|
|
2587
2839
|
{
|
|
2588
2840
|
type: "button",
|
|
@@ -2611,14 +2863,14 @@ function MejiroManuscriptEditor({
|
|
|
2611
2863
|
},
|
|
2612
2864
|
"data-dragging": draggingIndex === index ? "" : void 0,
|
|
2613
2865
|
children: [
|
|
2614
|
-
/* @__PURE__ */
|
|
2615
|
-
/* @__PURE__ */
|
|
2866
|
+
/* @__PURE__ */ jsx16("span", { children: format(messages.chapterN, { n: index + 1 }) }),
|
|
2867
|
+
/* @__PURE__ */ jsx16("strong", { children: chapter.title || messages.untitled })
|
|
2616
2868
|
]
|
|
2617
2869
|
},
|
|
2618
2870
|
chapter.id
|
|
2619
2871
|
)) }),
|
|
2620
|
-
/* @__PURE__ */
|
|
2621
|
-
/* @__PURE__ */
|
|
2872
|
+
/* @__PURE__ */ jsxs11("div", { className: "mejiro-editor-grid", children: [
|
|
2873
|
+
/* @__PURE__ */ jsx16(
|
|
2622
2874
|
"button",
|
|
2623
2875
|
{
|
|
2624
2876
|
type: "button",
|
|
@@ -2630,138 +2882,41 @@ function MejiroManuscriptEditor({
|
|
|
2630
2882
|
children: messages.manuscriptAddChapter
|
|
2631
2883
|
}
|
|
2632
2884
|
),
|
|
2633
|
-
/* @__PURE__ */
|
|
2885
|
+
/* @__PURE__ */ jsx16("button", { type: "button", onClick: () => removeChapter(selected), children: messages.manuscriptRemove })
|
|
2634
2886
|
] })
|
|
2635
2887
|
] }),
|
|
2636
|
-
current && /* @__PURE__ */
|
|
2637
|
-
/* @__PURE__ */
|
|
2638
|
-
/* @__PURE__ */
|
|
2888
|
+
current && /* @__PURE__ */ jsxs11("div", { className: "mejiro-editor-section", children: [
|
|
2889
|
+
/* @__PURE__ */ jsx16("span", { className: "mejiro-editor-label", children: messages.manuscriptDraft }),
|
|
2890
|
+
/* @__PURE__ */ jsx16(
|
|
2639
2891
|
"input",
|
|
2640
2892
|
{
|
|
2641
2893
|
value: current.title,
|
|
2642
2894
|
onChange: (event) => patchChapter(selected, { title: event.target.value })
|
|
2643
2895
|
}
|
|
2644
2896
|
),
|
|
2645
|
-
/* @__PURE__ */
|
|
2646
|
-
/* @__PURE__ */
|
|
2647
|
-
/* @__PURE__ */
|
|
2648
|
-
/* @__PURE__ */
|
|
2649
|
-
/* @__PURE__ */
|
|
2897
|
+
/* @__PURE__ */ jsxs11("div", { className: "mejiro-editor-grid mejiro-editor-notation", children: [
|
|
2898
|
+
/* @__PURE__ */ jsx16("button", { type: "button", onClick: () => wrapSelection("\u300A\u300A", "\u300B\u300B"), children: messages.manuscriptEmphasisDots }),
|
|
2899
|
+
/* @__PURE__ */ jsx16("button", { type: "button", onClick: () => wrapSelection("\u3014", "\u3015"), children: messages.manuscriptTcy }),
|
|
2900
|
+
/* @__PURE__ */ jsx16("button", { type: "button", onClick: () => wrapSelection("*", "*"), children: messages.manuscriptEm }),
|
|
2901
|
+
/* @__PURE__ */ jsx16("button", { type: "button", onClick: () => wrapSelection("**", "**"), children: messages.manuscriptStrong })
|
|
2650
2902
|
] }),
|
|
2651
|
-
/* @__PURE__ */
|
|
2652
|
-
|
|
2903
|
+
/* @__PURE__ */ jsx16(
|
|
2904
|
+
MejiroNotationHighlighter,
|
|
2653
2905
|
{
|
|
2654
2906
|
ref: bodyTextareaRef,
|
|
2655
2907
|
className: "mejiro-editor-manuscript",
|
|
2656
2908
|
value: current.body,
|
|
2657
|
-
onChange: (
|
|
2909
|
+
onChange: (body) => patchChapter(selected, { body })
|
|
2658
2910
|
}
|
|
2659
2911
|
)
|
|
2660
2912
|
] }),
|
|
2661
|
-
/* @__PURE__ */
|
|
2913
|
+
/* @__PURE__ */ jsx16("button", { type: "button", className: "mejiro-editor-export", onClick: () => void exportEpub(), children: messages.editorExportEpub })
|
|
2662
2914
|
] })
|
|
2663
2915
|
] });
|
|
2664
2916
|
}
|
|
2665
2917
|
|
|
2666
|
-
// src/MejiroNotationHighlighter.tsx
|
|
2667
|
-
import { tokenizeManuscriptSource } from "@libraz/mejiro";
|
|
2668
|
-
import {
|
|
2669
|
-
forwardRef as forwardRef2,
|
|
2670
|
-
useImperativeHandle as useImperativeHandle2,
|
|
2671
|
-
useMemo as useMemo5,
|
|
2672
|
-
useRef as useRef13
|
|
2673
|
-
} from "react";
|
|
2674
|
-
import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2675
|
-
var MejiroNotationHighlighter = forwardRef2(function MejiroNotationHighlighter2({
|
|
2676
|
-
value,
|
|
2677
|
-
onChange,
|
|
2678
|
-
dialect = "mejiro",
|
|
2679
|
-
wrapperClassName,
|
|
2680
|
-
wrapperStyle,
|
|
2681
|
-
className,
|
|
2682
|
-
style,
|
|
2683
|
-
onScroll,
|
|
2684
|
-
...rest
|
|
2685
|
-
}, ref) {
|
|
2686
|
-
const textareaRef = useRef13(null);
|
|
2687
|
-
const overlayRef = useRef13(null);
|
|
2688
|
-
useImperativeHandle2(ref, () => textareaRef.current, []);
|
|
2689
|
-
const segments = useMemo5(() => buildSegments(value, dialect), [value, dialect]);
|
|
2690
|
-
return /* @__PURE__ */ jsxs13(
|
|
2691
|
-
"div",
|
|
2692
|
-
{
|
|
2693
|
-
className: `mejiro-notation-highlighter${wrapperClassName ? ` ${wrapperClassName}` : ""}`,
|
|
2694
|
-
style: wrapperStyle,
|
|
2695
|
-
children: [
|
|
2696
|
-
/* @__PURE__ */ jsxs13(
|
|
2697
|
-
"div",
|
|
2698
|
-
{
|
|
2699
|
-
ref: overlayRef,
|
|
2700
|
-
className: "mejiro-notation-overlay",
|
|
2701
|
-
"aria-hidden": "true",
|
|
2702
|
-
children: [
|
|
2703
|
-
segments.map((segment) => /* @__PURE__ */ jsx15(
|
|
2704
|
-
"span",
|
|
2705
|
-
{
|
|
2706
|
-
className: segment.kind ? `mejiro-notation-token mejiro-notation-${segment.kind}` : void 0,
|
|
2707
|
-
"data-token": segment.kind,
|
|
2708
|
-
children: segment.text
|
|
2709
|
-
},
|
|
2710
|
-
segment.key
|
|
2711
|
-
)),
|
|
2712
|
-
"\u200B"
|
|
2713
|
-
]
|
|
2714
|
-
}
|
|
2715
|
-
),
|
|
2716
|
-
/* @__PURE__ */ jsx15(
|
|
2717
|
-
"textarea",
|
|
2718
|
-
{
|
|
2719
|
-
...rest,
|
|
2720
|
-
ref: textareaRef,
|
|
2721
|
-
className: `mejiro-notation-textarea${className ? ` ${className}` : ""}`,
|
|
2722
|
-
style,
|
|
2723
|
-
value,
|
|
2724
|
-
onChange: (event) => onChange(event.target.value),
|
|
2725
|
-
onScroll: (event) => {
|
|
2726
|
-
if (overlayRef.current) {
|
|
2727
|
-
overlayRef.current.scrollTop = event.currentTarget.scrollTop;
|
|
2728
|
-
overlayRef.current.scrollLeft = event.currentTarget.scrollLeft;
|
|
2729
|
-
}
|
|
2730
|
-
onScroll?.(event);
|
|
2731
|
-
},
|
|
2732
|
-
spellCheck: false
|
|
2733
|
-
}
|
|
2734
|
-
)
|
|
2735
|
-
]
|
|
2736
|
-
}
|
|
2737
|
-
);
|
|
2738
|
-
});
|
|
2739
|
-
function buildSegments(text, dialect) {
|
|
2740
|
-
const tokens = tokenizeManuscriptSource(text, dialect);
|
|
2741
|
-
const segments = [];
|
|
2742
|
-
let cursor = 0;
|
|
2743
|
-
for (const [i, token] of tokens.entries()) {
|
|
2744
|
-
if (token.start > cursor) {
|
|
2745
|
-
segments.push({ key: `t-${i}-pre`, text: text.slice(cursor, token.start) });
|
|
2746
|
-
}
|
|
2747
|
-
segments.push({
|
|
2748
|
-
key: `t-${i}`,
|
|
2749
|
-
text: text.slice(token.start, token.end),
|
|
2750
|
-
kind: token.kind
|
|
2751
|
-
});
|
|
2752
|
-
cursor = token.end;
|
|
2753
|
-
}
|
|
2754
|
-
if (cursor < text.length) {
|
|
2755
|
-
segments.push({ key: "tail", text: text.slice(cursor) });
|
|
2756
|
-
}
|
|
2757
|
-
if (segments.length === 0) {
|
|
2758
|
-
segments.push({ key: "empty", text });
|
|
2759
|
-
}
|
|
2760
|
-
return segments;
|
|
2761
|
-
}
|
|
2762
|
-
|
|
2763
2918
|
// src/MejiroShelf.tsx
|
|
2764
|
-
import { jsx as
|
|
2919
|
+
import { jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
2765
2920
|
function MejiroShelf({
|
|
2766
2921
|
volumes,
|
|
2767
2922
|
currentId,
|
|
@@ -2770,26 +2925,26 @@ function MejiroShelf({
|
|
|
2770
2925
|
}) {
|
|
2771
2926
|
const messages = useI18n();
|
|
2772
2927
|
const resolvedTitle = title ?? messages.shelfTitle;
|
|
2773
|
-
return /* @__PURE__ */
|
|
2774
|
-
/* @__PURE__ */
|
|
2775
|
-
/* @__PURE__ */
|
|
2928
|
+
return /* @__PURE__ */ jsxs12("section", { className: "mejiro-shelf", "aria-label": resolvedTitle, children: [
|
|
2929
|
+
/* @__PURE__ */ jsx17("header", { className: "mejiro-shelf-header", children: /* @__PURE__ */ jsx17("span", { className: "mejiro-shelf-title", children: resolvedTitle }) }),
|
|
2930
|
+
/* @__PURE__ */ jsx17("ul", { className: "mejiro-shelf-grid", children: volumes.map((v) => /* @__PURE__ */ jsx17("li", { className: "mejiro-shelf-item", children: /* @__PURE__ */ jsxs12(
|
|
2776
2931
|
"button",
|
|
2777
2932
|
{
|
|
2778
2933
|
type: "button",
|
|
2779
2934
|
className: `mejiro-shelf-card${v.id === currentId ? " is-active" : ""}`,
|
|
2780
2935
|
onClick: () => onSelect?.(v),
|
|
2781
2936
|
children: [
|
|
2782
|
-
v.cover ? /* @__PURE__ */
|
|
2937
|
+
v.cover ? /* @__PURE__ */ jsx17(
|
|
2783
2938
|
"span",
|
|
2784
2939
|
{
|
|
2785
2940
|
className: "mejiro-shelf-cover",
|
|
2786
2941
|
style: { backgroundImage: `url(${JSON.stringify(v.cover)})` },
|
|
2787
2942
|
"aria-hidden": "true"
|
|
2788
2943
|
}
|
|
2789
|
-
) : /* @__PURE__ */
|
|
2790
|
-
/* @__PURE__ */
|
|
2791
|
-
/* @__PURE__ */
|
|
2792
|
-
v.author && /* @__PURE__ */
|
|
2944
|
+
) : /* @__PURE__ */ jsx17("span", { className: "mejiro-shelf-cover mejiro-shelf-cover--blank", "aria-hidden": "true" }),
|
|
2945
|
+
/* @__PURE__ */ jsxs12("span", { className: "mejiro-shelf-meta", children: [
|
|
2946
|
+
/* @__PURE__ */ jsx17("span", { className: "mejiro-shelf-label", children: v.label }),
|
|
2947
|
+
v.author && /* @__PURE__ */ jsx17("span", { className: "mejiro-shelf-author", children: v.author })
|
|
2793
2948
|
] })
|
|
2794
2949
|
]
|
|
2795
2950
|
}
|
|
@@ -2798,8 +2953,8 @@ function MejiroShelf({
|
|
|
2798
2953
|
}
|
|
2799
2954
|
|
|
2800
2955
|
// src/MejiroToc.tsx
|
|
2801
|
-
import { useMemo as
|
|
2802
|
-
import { jsx as
|
|
2956
|
+
import { useMemo as useMemo7, useState as useState12 } from "react";
|
|
2957
|
+
import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2803
2958
|
function buildEntries(epub, messages) {
|
|
2804
2959
|
return epub.chapters.map((ch, i) => {
|
|
2805
2960
|
const title = ch.title ?? format(messages.chapterN, { n: i + 1 });
|
|
@@ -2818,9 +2973,9 @@ function MejiroToc({
|
|
|
2818
2973
|
const messages = useI18n();
|
|
2819
2974
|
const resolvedTitle = title ?? messages.tocTitle;
|
|
2820
2975
|
const resolvedSearchPlaceholder = searchPlaceholder ?? messages.tocSearchPlaceholder;
|
|
2821
|
-
const [query, setQuery] =
|
|
2822
|
-
const entries =
|
|
2823
|
-
const filtered =
|
|
2976
|
+
const [query, setQuery] = useState12("");
|
|
2977
|
+
const entries = useMemo7(() => buildEntries(epub, messages), [epub, messages]);
|
|
2978
|
+
const filtered = useMemo7(() => {
|
|
2824
2979
|
if (!query) return entries;
|
|
2825
2980
|
const needle = query.toLowerCase();
|
|
2826
2981
|
return entries.filter(
|
|
@@ -2828,12 +2983,12 @@ function MejiroToc({
|
|
|
2828
2983
|
);
|
|
2829
2984
|
}, [entries, query]);
|
|
2830
2985
|
const activeIndex = currentAnchor?.chapter ?? -1;
|
|
2831
|
-
return /* @__PURE__ */
|
|
2832
|
-
/* @__PURE__ */
|
|
2833
|
-
/* @__PURE__ */
|
|
2834
|
-
epub.author && /* @__PURE__ */
|
|
2986
|
+
return /* @__PURE__ */ jsxs13("nav", { className: "mejiro-toc", "aria-label": resolvedTitle, children: [
|
|
2987
|
+
/* @__PURE__ */ jsxs13("header", { className: "mejiro-toc-header", children: [
|
|
2988
|
+
/* @__PURE__ */ jsx18("span", { className: "mejiro-toc-title", children: resolvedTitle }),
|
|
2989
|
+
epub.author && /* @__PURE__ */ jsx18("span", { className: "mejiro-toc-subtitle", children: epub.author })
|
|
2835
2990
|
] }),
|
|
2836
|
-
searchable && /* @__PURE__ */
|
|
2991
|
+
searchable && /* @__PURE__ */ jsx18("div", { className: "mejiro-toc-search", children: /* @__PURE__ */ jsx18(
|
|
2837
2992
|
"input",
|
|
2838
2993
|
{
|
|
2839
2994
|
type: "search",
|
|
@@ -2843,8 +2998,8 @@ function MejiroToc({
|
|
|
2843
2998
|
"aria-label": resolvedSearchPlaceholder
|
|
2844
2999
|
}
|
|
2845
3000
|
) }),
|
|
2846
|
-
/* @__PURE__ */
|
|
2847
|
-
/* @__PURE__ */
|
|
3001
|
+
/* @__PURE__ */ jsx18("ol", { className: "mejiro-toc-list", children: filtered.map((entry) => /* @__PURE__ */ jsxs13("li", { className: "mejiro-toc-item", children: [
|
|
3002
|
+
/* @__PURE__ */ jsxs13(
|
|
2848
3003
|
"button",
|
|
2849
3004
|
{
|
|
2850
3005
|
type: "button",
|
|
@@ -2852,58 +3007,37 @@ function MejiroToc({
|
|
|
2852
3007
|
"aria-current": entry.index === activeIndex ? "true" : void 0,
|
|
2853
3008
|
onClick: () => onSelect?.(entry.index),
|
|
2854
3009
|
children: [
|
|
2855
|
-
/* @__PURE__ */
|
|
2856
|
-
/* @__PURE__ */
|
|
3010
|
+
/* @__PURE__ */ jsx18("span", { className: "mejiro-toc-num", children: String(entry.index + 1).padStart(2, "0") }),
|
|
3011
|
+
/* @__PURE__ */ jsx18("span", { className: "mejiro-toc-label", children: entry.title })
|
|
2857
3012
|
]
|
|
2858
3013
|
}
|
|
2859
3014
|
),
|
|
2860
|
-
entry.headings.length > 0 && /* @__PURE__ */
|
|
3015
|
+
entry.headings.length > 0 && /* @__PURE__ */ jsx18("ul", { className: "mejiro-toc-subheads", children: entry.headings.slice(0, 5).map((h) => /* @__PURE__ */ jsx18("li", { className: "mejiro-toc-subhead", children: h }, h)) })
|
|
2861
3016
|
] }, entry.index)) }),
|
|
2862
|
-
filtered.length === 0 && /* @__PURE__ */
|
|
3017
|
+
filtered.length === 0 && /* @__PURE__ */ jsx18("div", { className: "mejiro-toc-empty", role: "status", children: format(messages.tocEmpty, { query }) })
|
|
2863
3018
|
] });
|
|
2864
3019
|
}
|
|
2865
3020
|
|
|
2866
3021
|
// src/useAnnotations.ts
|
|
2867
|
-
import {
|
|
2868
|
-
|
|
3022
|
+
import {
|
|
3023
|
+
createAnnotationId,
|
|
3024
|
+
parseAnnotations,
|
|
3025
|
+
serializeAnnotations,
|
|
3026
|
+
sortAnnotations
|
|
3027
|
+
} from "@libraz/mejiro";
|
|
3028
|
+
import { useCallback as useCallback10, useEffect as useEffect10, useRef as useRef14, useState as useState13 } from "react";
|
|
2869
3029
|
function resolveDefaultStorage() {
|
|
2870
3030
|
if (typeof globalThis === "undefined") return null;
|
|
2871
3031
|
const w = globalThis.localStorage;
|
|
2872
3032
|
return w ?? null;
|
|
2873
3033
|
}
|
|
2874
|
-
function parse(raw) {
|
|
2875
|
-
if (!raw) return [];
|
|
2876
|
-
try {
|
|
2877
|
-
const parsed = JSON.parse(raw);
|
|
2878
|
-
if (parsed && Array.isArray(parsed.annotations)) return parsed.annotations;
|
|
2879
|
-
} catch {
|
|
2880
|
-
}
|
|
2881
|
-
return [];
|
|
2882
|
-
}
|
|
2883
|
-
function serialize(annotations) {
|
|
2884
|
-
const payload = { version: STORAGE_VERSION, annotations: [...annotations] };
|
|
2885
|
-
return JSON.stringify(payload);
|
|
2886
|
-
}
|
|
2887
|
-
function sort(annotations) {
|
|
2888
|
-
return [...annotations].sort((a, b) => {
|
|
2889
|
-
if (a.chapter !== b.chapter) return a.chapter - b.chapter;
|
|
2890
|
-
if (a.start.paragraph !== b.start.paragraph) return a.start.paragraph - b.start.paragraph;
|
|
2891
|
-
return a.start.charIndex - b.start.charIndex;
|
|
2892
|
-
});
|
|
2893
|
-
}
|
|
2894
|
-
function makeId() {
|
|
2895
|
-
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
2896
|
-
return crypto.randomUUID();
|
|
2897
|
-
}
|
|
2898
|
-
return `ann-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
2899
|
-
}
|
|
2900
3034
|
function useAnnotations(options) {
|
|
2901
3035
|
const { key, throttleMs = 250 } = options;
|
|
2902
3036
|
const storage = options.storage ?? resolveDefaultStorage();
|
|
2903
|
-
const [annotations, setAnnotations] =
|
|
3037
|
+
const [annotations, setAnnotations] = useState13(() => {
|
|
2904
3038
|
if (!storage) return [];
|
|
2905
3039
|
try {
|
|
2906
|
-
return
|
|
3040
|
+
return sortAnnotations(parseAnnotations(storage.getItem(key)));
|
|
2907
3041
|
} catch {
|
|
2908
3042
|
return [];
|
|
2909
3043
|
}
|
|
@@ -2914,7 +3048,7 @@ function useAnnotations(options) {
|
|
|
2914
3048
|
return;
|
|
2915
3049
|
}
|
|
2916
3050
|
try {
|
|
2917
|
-
setAnnotations(
|
|
3051
|
+
setAnnotations(sortAnnotations(parseAnnotations(storage.getItem(key))));
|
|
2918
3052
|
} catch {
|
|
2919
3053
|
setAnnotations([]);
|
|
2920
3054
|
}
|
|
@@ -2934,7 +3068,7 @@ function useAnnotations(options) {
|
|
|
2934
3068
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
2935
3069
|
timerRef.current = setTimeout(() => {
|
|
2936
3070
|
try {
|
|
2937
|
-
storage.setItem(key,
|
|
3071
|
+
storage.setItem(key, serializeAnnotations(next));
|
|
2938
3072
|
} catch {
|
|
2939
3073
|
}
|
|
2940
3074
|
}, throttleMs);
|
|
@@ -2947,11 +3081,11 @@ function useAnnotations(options) {
|
|
|
2947
3081
|
(input) => {
|
|
2948
3082
|
const annotation = {
|
|
2949
3083
|
...input,
|
|
2950
|
-
id: input.id ??
|
|
3084
|
+
id: input.id ?? createAnnotationId(),
|
|
2951
3085
|
createdAt: input.createdAt ?? Date.now()
|
|
2952
3086
|
};
|
|
2953
3087
|
setAnnotations((current) => {
|
|
2954
|
-
const next =
|
|
3088
|
+
const next = sortAnnotations([...current, annotation]);
|
|
2955
3089
|
commit(next);
|
|
2956
3090
|
return next;
|
|
2957
3091
|
});
|
|
@@ -2979,7 +3113,7 @@ function useAnnotations(options) {
|
|
|
2979
3113
|
return { ...annotation, ...patch, id };
|
|
2980
3114
|
});
|
|
2981
3115
|
if (!changed) return current;
|
|
2982
|
-
const sorted =
|
|
3116
|
+
const sorted = sortAnnotations(next);
|
|
2983
3117
|
commit(sorted);
|
|
2984
3118
|
return sorted;
|
|
2985
3119
|
});
|
|
@@ -3004,14 +3138,14 @@ function useAnnotations(options) {
|
|
|
3004
3138
|
import {
|
|
3005
3139
|
EditableEpub as EditableEpub2
|
|
3006
3140
|
} from "@libraz/mejiro/epub";
|
|
3007
|
-
import { useCallback as useCallback11, useEffect as useEffect11, useRef as useRef15, useState as
|
|
3141
|
+
import { useCallback as useCallback11, useEffect as useEffect11, useMemo as useMemo8, useRef as useRef15, useState as useState14 } from "react";
|
|
3008
3142
|
function useEditableEpub(options = {}) {
|
|
3009
|
-
const [editor, setEditor] =
|
|
3010
|
-
const [loading, setLoading] =
|
|
3011
|
-
const [exporting, setExporting] =
|
|
3012
|
-
const [error, setError] =
|
|
3013
|
-
const [revision, setRevision] =
|
|
3014
|
-
const [selection, setSelectionState] =
|
|
3143
|
+
const [editor, setEditor] = useState14(null);
|
|
3144
|
+
const [loading, setLoading] = useState14(false);
|
|
3145
|
+
const [exporting, setExporting] = useState14(false);
|
|
3146
|
+
const [error, setError] = useState14(null);
|
|
3147
|
+
const [revision, setRevision] = useState14(0);
|
|
3148
|
+
const [selection, setSelectionState] = useState14({
|
|
3015
3149
|
chapter: 0,
|
|
3016
3150
|
paragraph: 0
|
|
3017
3151
|
});
|
|
@@ -3099,7 +3233,10 @@ function useEditableEpub(options = {}) {
|
|
|
3099
3233
|
}, [options.defaultUrl, loadUrl]);
|
|
3100
3234
|
const book = editor?.book ?? null;
|
|
3101
3235
|
const selectedParagraph = book?.chapters[selection.chapter]?.paragraphs[selection.paragraph] ?? null;
|
|
3102
|
-
const previewBook =
|
|
3236
|
+
const previewBook = useMemo8(() => {
|
|
3237
|
+
void revision;
|
|
3238
|
+
return book ? cloneBook2(book) : null;
|
|
3239
|
+
}, [book, revision]);
|
|
3103
3240
|
const history = editor?.history ?? null;
|
|
3104
3241
|
const setSelection = useCallback11(
|
|
3105
3242
|
(nextSelection) => {
|
|
@@ -3222,20 +3359,22 @@ import {
|
|
|
3222
3359
|
EpubProject as EpubProject2,
|
|
3223
3360
|
parseEpub as parseEpub2
|
|
3224
3361
|
} from "@libraz/mejiro/epub";
|
|
3225
|
-
import { useCallback as useCallback12, useEffect as useEffect12, useMemo as
|
|
3362
|
+
import { useCallback as useCallback12, useEffect as useEffect12, useMemo as useMemo9, useRef as useRef16, useState as useState15 } from "react";
|
|
3226
3363
|
function useEpubProject(options = {}) {
|
|
3227
|
-
const
|
|
3364
|
+
const defaultTitle = options.defaultChapterTitle;
|
|
3365
|
+
const defaultBody = options.defaultChapterBody;
|
|
3366
|
+
const [metadata, setMetadataState] = useState15({
|
|
3228
3367
|
title: "\u65B0\u3057\u3044\u4F5C\u54C1",
|
|
3229
3368
|
language: "ja",
|
|
3230
3369
|
...options.metadata
|
|
3231
3370
|
});
|
|
3232
|
-
const [chapters, setChaptersState] =
|
|
3233
|
-
options.chapters?.length ? options.chapters : [defaultChapter3(0)]
|
|
3371
|
+
const [chapters, setChaptersState] = useState15(
|
|
3372
|
+
options.chapters?.length ? options.chapters : [defaultChapter3(0, defaultTitle, defaultBody)]
|
|
3234
3373
|
);
|
|
3235
|
-
const [selectedChapter, setSelectedChapterState] =
|
|
3236
|
-
const [previewBook, setPreviewBook] =
|
|
3237
|
-
const [previewError, setPreviewError] =
|
|
3238
|
-
const [previewing, setPreviewing] =
|
|
3374
|
+
const [selectedChapter, setSelectedChapterState] = useState15(0);
|
|
3375
|
+
const [previewBook, setPreviewBook] = useState15(null);
|
|
3376
|
+
const [previewError, setPreviewError] = useState15(null);
|
|
3377
|
+
const [previewing, setPreviewing] = useState15(false);
|
|
3239
3378
|
const previewRequestIdRef = useRef16(0);
|
|
3240
3379
|
const onPreviewRef = useRef16(options.onPreview);
|
|
3241
3380
|
const onExportRef = useRef16(options.onExport);
|
|
@@ -3292,7 +3431,7 @@ function useEpubProject(options = {}) {
|
|
|
3292
3431
|
}, []);
|
|
3293
3432
|
const setChapters = useCallback12(
|
|
3294
3433
|
(next) => {
|
|
3295
|
-
const normalized = next.length ? next : [defaultChapter3(0)];
|
|
3434
|
+
const normalized = next.length ? next : [defaultChapter3(0, defaultTitle, defaultBody)];
|
|
3296
3435
|
setChaptersState(normalized);
|
|
3297
3436
|
setSelectedChapterState((current) => {
|
|
3298
3437
|
const selectedId = chapters[current]?.id;
|
|
@@ -3300,7 +3439,7 @@ function useEpubProject(options = {}) {
|
|
|
3300
3439
|
return nextIndex >= 0 ? nextIndex : Math.max(0, Math.min(current, normalized.length - 1));
|
|
3301
3440
|
});
|
|
3302
3441
|
},
|
|
3303
|
-
[chapters]
|
|
3442
|
+
[chapters, defaultBody, defaultTitle]
|
|
3304
3443
|
);
|
|
3305
3444
|
const setSelectedChapter = useCallback12(
|
|
3306
3445
|
(index) => {
|
|
@@ -3315,21 +3454,24 @@ function useEpubProject(options = {}) {
|
|
|
3315
3454
|
)
|
|
3316
3455
|
);
|
|
3317
3456
|
}, []);
|
|
3318
|
-
const addChapter = useCallback12(
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3457
|
+
const addChapter = useCallback12(
|
|
3458
|
+
(chapter = {}) => {
|
|
3459
|
+
setChaptersState((current) => {
|
|
3460
|
+
const generated = defaultChapter3(current.length, defaultTitle, defaultBody);
|
|
3461
|
+
const next = [
|
|
3462
|
+
...current,
|
|
3463
|
+
{
|
|
3464
|
+
id: chapter.id ?? generated.id,
|
|
3465
|
+
title: chapter.title ?? generated.title,
|
|
3466
|
+
body: chapter.body ?? generated.body
|
|
3467
|
+
}
|
|
3468
|
+
];
|
|
3469
|
+
setSelectedChapterState(next.length - 1);
|
|
3470
|
+
return next;
|
|
3471
|
+
});
|
|
3472
|
+
},
|
|
3473
|
+
[defaultBody, defaultTitle]
|
|
3474
|
+
);
|
|
3333
3475
|
const removeChapter = useCallback12(
|
|
3334
3476
|
(index = selectedChapter) => {
|
|
3335
3477
|
setChaptersState((current) => {
|
|
@@ -3367,7 +3509,7 @@ function useEpubProject(options = {}) {
|
|
|
3367
3509
|
onExportRef.current?.(buffer);
|
|
3368
3510
|
return buffer;
|
|
3369
3511
|
}, [buildProject]);
|
|
3370
|
-
return
|
|
3512
|
+
return useMemo9(
|
|
3371
3513
|
() => ({
|
|
3372
3514
|
metadata,
|
|
3373
3515
|
chapters,
|
|
@@ -3406,11 +3548,11 @@ function useEpubProject(options = {}) {
|
|
|
3406
3548
|
]
|
|
3407
3549
|
);
|
|
3408
3550
|
}
|
|
3409
|
-
function defaultChapter3(index) {
|
|
3551
|
+
function defaultChapter3(index, titleFor = (i) => i === 0 ? "\u7B2C\u4E00\u8A71" : `\u7B2C${i + 1}\u8A71`, bodyFor = (i) => i === 0 ? "\u3053\u308C\u306F\uFF5C\u6F22\u5B57\u300A\u304B\u3093\u3058\u300B\u306E\u30EB\u30D3\u4F8B\u3067\u3059\u3002\n\n\u672C\u6587\u3092\u3053\u3053\u306B\u8CBC\u308A\u4ED8\u3051\u307E\u3059\u3002" : "") {
|
|
3410
3552
|
return {
|
|
3411
3553
|
id: `chapter-${Date.now()}-${index}`,
|
|
3412
|
-
title: index
|
|
3413
|
-
body: index
|
|
3554
|
+
title: titleFor(index),
|
|
3555
|
+
body: bodyFor(index)
|
|
3414
3556
|
};
|
|
3415
3557
|
}
|
|
3416
3558
|
|
|
@@ -3420,7 +3562,7 @@ import {
|
|
|
3420
3562
|
useCallback as useCallback13,
|
|
3421
3563
|
useEffect as useEffect13,
|
|
3422
3564
|
useRef as useRef17,
|
|
3423
|
-
useState as
|
|
3565
|
+
useState as useState16
|
|
3424
3566
|
} from "react";
|
|
3425
3567
|
function useImageOverlay(layout, spreadIdx, onUpdate, options) {
|
|
3426
3568
|
const defW = options?.defaultWidth ?? 120;
|
|
@@ -3428,7 +3570,7 @@ function useImageOverlay(layout, spreadIdx, onUpdate, options) {
|
|
|
3428
3570
|
const defX = options?.defaultX ?? 80;
|
|
3429
3571
|
const defY = options?.defaultY ?? 100;
|
|
3430
3572
|
const margin = options?.margin;
|
|
3431
|
-
const [imageRect, setImageRect] =
|
|
3573
|
+
const [imageRect, setImageRect] = useState16(null);
|
|
3432
3574
|
const rectRef = useRef17(imageRect);
|
|
3433
3575
|
rectRef.current = imageRect;
|
|
3434
3576
|
const layoutRef = useRef17(layout);
|
|
@@ -3550,14 +3692,14 @@ function useImageOverlay(layout, spreadIdx, onUpdate, options) {
|
|
|
3550
3692
|
}
|
|
3551
3693
|
|
|
3552
3694
|
// src/useLibrary.ts
|
|
3553
|
-
import { useCallback as useCallback14, useEffect as useEffect14, useMemo as
|
|
3695
|
+
import { useCallback as useCallback14, useEffect as useEffect14, useMemo as useMemo10, useState as useState17 } from "react";
|
|
3554
3696
|
function useLibrary(options) {
|
|
3555
3697
|
const { volumes, initialVolumeId, onChange } = options;
|
|
3556
3698
|
const initialIndex = initialVolumeId != null ? Math.max(
|
|
3557
3699
|
0,
|
|
3558
3700
|
volumes.findIndex((v) => v.id === initialVolumeId)
|
|
3559
3701
|
) : 0;
|
|
3560
|
-
const [currentId, setCurrentId] =
|
|
3702
|
+
const [currentId, setCurrentId] = useState17(
|
|
3561
3703
|
volumes.length > 0 ? volumes[initialIndex]?.id ?? volumes[0]?.id ?? null : null
|
|
3562
3704
|
);
|
|
3563
3705
|
useEffect14(() => {
|
|
@@ -3570,13 +3712,13 @@ function useLibrary(options) {
|
|
|
3570
3712
|
setCurrentId(fallback?.id ?? null);
|
|
3571
3713
|
if (fallback) onChange?.(fallback);
|
|
3572
3714
|
}, [currentId, volumes, onChange]);
|
|
3573
|
-
const index =
|
|
3715
|
+
const index = useMemo10(() => {
|
|
3574
3716
|
if (volumes.length === 0) return -1;
|
|
3575
3717
|
if (currentId == null) return 0;
|
|
3576
3718
|
const found = volumes.findIndex((v) => v.id === currentId);
|
|
3577
3719
|
return found >= 0 ? found : 0;
|
|
3578
3720
|
}, [currentId, volumes]);
|
|
3579
|
-
const current =
|
|
3721
|
+
const current = useMemo10(
|
|
3580
3722
|
() => index >= 0 && index < volumes.length ? volumes[index] : null,
|
|
3581
3723
|
[index, volumes]
|
|
3582
3724
|
);
|
|
@@ -3618,19 +3760,17 @@ function useLibrary(options) {
|
|
|
3618
3760
|
}
|
|
3619
3761
|
|
|
3620
3762
|
// src/useManuscriptLayout.ts
|
|
3621
|
-
import { useCallback as useCallback15, useEffect as useEffect15, useLayoutEffect as
|
|
3763
|
+
import { useCallback as useCallback15, useEffect as useEffect15, useLayoutEffect as useLayoutEffect5, useRef as useRef18, useState as useState18 } from "react";
|
|
3622
3764
|
function useManuscriptLayout(book, chapter, surface, options = {}) {
|
|
3623
3765
|
const dialect = options.dialect ?? "mejiro";
|
|
3624
3766
|
const enableResize = options.enableResize ?? true;
|
|
3625
3767
|
const resizeDebounce = options.resizeDebounce ?? 120;
|
|
3626
|
-
const [layout, setLayout] =
|
|
3627
|
-
const [pageWidth, setPageWidth] =
|
|
3628
|
-
const [pageHeight, setPageHeight] =
|
|
3629
|
-
const [contentHeight, setContentHeight] =
|
|
3630
|
-
const [elapsedMs, setElapsedMs] =
|
|
3631
|
-
const layoutRef = useRef18(null);
|
|
3768
|
+
const [layout, setLayout] = useState18(null);
|
|
3769
|
+
const [pageWidth, setPageWidth] = useState18(0);
|
|
3770
|
+
const [pageHeight, setPageHeight] = useState18(0);
|
|
3771
|
+
const [contentHeight, setContentHeight] = useState18(0);
|
|
3772
|
+
const [elapsedMs, setElapsedMs] = useState18(0);
|
|
3632
3773
|
const requestIdRef = useRef18(0);
|
|
3633
|
-
layoutRef.current = layout;
|
|
3634
3774
|
const recompute = useCallback15(async () => {
|
|
3635
3775
|
const requestId = ++requestIdRef.current;
|
|
3636
3776
|
if (!(chapter && surface.current)) {
|
|
@@ -3653,7 +3793,7 @@ function useManuscriptLayout(book, chapter, surface, options = {}) {
|
|
|
3653
3793
|
setLayout(next);
|
|
3654
3794
|
setElapsedMs(performance.now() - t0);
|
|
3655
3795
|
}, [book, chapter, dialect, surface]);
|
|
3656
|
-
|
|
3796
|
+
useLayoutEffect5(() => {
|
|
3657
3797
|
requestIdRef.current++;
|
|
3658
3798
|
setLayout(null);
|
|
3659
3799
|
setPageWidth(0);
|
|
@@ -3669,95 +3809,85 @@ function useManuscriptLayout(book, chapter, surface, options = {}) {
|
|
|
3669
3809
|
}, [recompute]);
|
|
3670
3810
|
useEffect15(() => {
|
|
3671
3811
|
if (!enableResize) return;
|
|
3812
|
+
const el = surface.current;
|
|
3813
|
+
if (!el) return;
|
|
3672
3814
|
let timer = null;
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
if (timer)
|
|
3815
|
+
let observed = false;
|
|
3816
|
+
const clear = () => {
|
|
3817
|
+
if (timer) {
|
|
3818
|
+
clearTimeout(timer);
|
|
3819
|
+
timer = null;
|
|
3820
|
+
}
|
|
3821
|
+
};
|
|
3822
|
+
const scheduleReflow = (immediate) => {
|
|
3823
|
+
clear();
|
|
3824
|
+
if (immediate) {
|
|
3825
|
+
void recompute();
|
|
3826
|
+
return;
|
|
3827
|
+
}
|
|
3676
3828
|
timer = setTimeout(() => {
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
setPageWidth(dims.pageWidth);
|
|
3680
|
-
setPageHeight(dims.pageHeight);
|
|
3681
|
-
setContentHeight(dims.contentHeight);
|
|
3682
|
-
layoutRef.current.resize({
|
|
3683
|
-
pageWidth: dims.pageWidth,
|
|
3684
|
-
lineWidth: dims.contentHeight - book.getOptions().fontSize * 0.5
|
|
3685
|
-
});
|
|
3829
|
+
timer = null;
|
|
3830
|
+
void recompute();
|
|
3686
3831
|
}, resizeDebounce);
|
|
3687
3832
|
};
|
|
3688
|
-
|
|
3833
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
3834
|
+
const observer = new ResizeObserver(() => {
|
|
3835
|
+
const immediate = !observed;
|
|
3836
|
+
observed = true;
|
|
3837
|
+
scheduleReflow(immediate);
|
|
3838
|
+
});
|
|
3839
|
+
observer.observe(el);
|
|
3840
|
+
return () => {
|
|
3841
|
+
observer.disconnect();
|
|
3842
|
+
clear();
|
|
3843
|
+
};
|
|
3844
|
+
}
|
|
3845
|
+
const onWindowResize = () => scheduleReflow(false);
|
|
3846
|
+
window.addEventListener("resize", onWindowResize);
|
|
3689
3847
|
return () => {
|
|
3690
|
-
window.removeEventListener("resize",
|
|
3691
|
-
|
|
3848
|
+
window.removeEventListener("resize", onWindowResize);
|
|
3849
|
+
clear();
|
|
3692
3850
|
};
|
|
3693
|
-
}, [
|
|
3851
|
+
}, [surface, enableResize, resizeDebounce, recompute]);
|
|
3694
3852
|
return { layout, pageWidth, pageHeight, contentHeight, elapsedMs, recompute };
|
|
3695
3853
|
}
|
|
3696
3854
|
|
|
3697
3855
|
// src/useReadingPosition.ts
|
|
3698
|
-
import {
|
|
3699
|
-
|
|
3856
|
+
import {
|
|
3857
|
+
parseReadingPosition,
|
|
3858
|
+
serializeReadingPosition
|
|
3859
|
+
} from "@libraz/mejiro";
|
|
3860
|
+
import { useCallback as useCallback16, useEffect as useEffect16, useRef as useRef19, useState as useState19 } from "react";
|
|
3700
3861
|
function resolveDefaultStorage2() {
|
|
3701
3862
|
if (typeof globalThis === "undefined") return null;
|
|
3702
3863
|
const w = globalThis.localStorage;
|
|
3703
3864
|
return w ?? null;
|
|
3704
3865
|
}
|
|
3705
|
-
function parsePosition(raw) {
|
|
3706
|
-
if (!raw) return null;
|
|
3707
|
-
let parsed;
|
|
3708
|
-
try {
|
|
3709
|
-
parsed = JSON.parse(raw);
|
|
3710
|
-
} catch {
|
|
3711
|
-
return null;
|
|
3712
|
-
}
|
|
3713
|
-
if (!parsed || typeof parsed !== "object") return null;
|
|
3714
|
-
const p = parsed;
|
|
3715
|
-
if (typeof p.chapter !== "number") return null;
|
|
3716
|
-
if (p.version === STORAGE_VERSION2) {
|
|
3717
|
-
if (typeof p.paragraph !== "number" || typeof p.charIndex !== "number") return null;
|
|
3718
|
-
return { chapter: p.chapter, paragraph: p.paragraph, charIndex: p.charIndex };
|
|
3719
|
-
}
|
|
3720
|
-
if (typeof p.spreadIdx === "number") {
|
|
3721
|
-
if (typeof console !== "undefined" && typeof console.warn === "function") {
|
|
3722
|
-
console.warn(
|
|
3723
|
-
"[mejiro] useReadingPosition: migrating legacy {chapter, spreadIdx} format. spreadIdx has been dropped because it does not survive reflow; the position now resumes at the start of the chapter. Persist new positions via save() to upgrade."
|
|
3724
|
-
);
|
|
3725
|
-
}
|
|
3726
|
-
return { chapter: p.chapter, paragraph: 0, charIndex: 0 };
|
|
3727
|
-
}
|
|
3728
|
-
return null;
|
|
3729
|
-
}
|
|
3730
|
-
function serializePosition(value) {
|
|
3731
|
-
const payload = {
|
|
3732
|
-
version: STORAGE_VERSION2,
|
|
3733
|
-
chapter: value.chapter,
|
|
3734
|
-
paragraph: value.paragraph,
|
|
3735
|
-
charIndex: value.charIndex
|
|
3736
|
-
};
|
|
3737
|
-
return JSON.stringify(payload);
|
|
3738
|
-
}
|
|
3739
3866
|
function useReadingPosition(options) {
|
|
3740
3867
|
const { key, throttleMs = 250 } = options;
|
|
3741
3868
|
const storage = options.storage ?? resolveDefaultStorage2();
|
|
3742
|
-
const [position, setPosition] =
|
|
3869
|
+
const [position, setPosition] = useState19(() => {
|
|
3743
3870
|
if (!storage) return null;
|
|
3744
3871
|
try {
|
|
3745
|
-
return
|
|
3872
|
+
return parseReadingPosition(storage.getItem(key));
|
|
3746
3873
|
} catch {
|
|
3747
3874
|
return null;
|
|
3748
3875
|
}
|
|
3749
3876
|
});
|
|
3877
|
+
const storageRef = useRef19(storage);
|
|
3878
|
+
storageRef.current = storage;
|
|
3750
3879
|
useEffect16(() => {
|
|
3751
|
-
|
|
3880
|
+
const currentStorage = storageRef.current;
|
|
3881
|
+
if (!currentStorage) {
|
|
3752
3882
|
setPosition(null);
|
|
3753
3883
|
return;
|
|
3754
3884
|
}
|
|
3755
3885
|
try {
|
|
3756
|
-
setPosition(
|
|
3886
|
+
setPosition(parseReadingPosition(currentStorage.getItem(key)));
|
|
3757
3887
|
} catch {
|
|
3758
3888
|
setPosition(null);
|
|
3759
3889
|
}
|
|
3760
|
-
}, [
|
|
3890
|
+
}, [key]);
|
|
3761
3891
|
const timerRef = useRef19(null);
|
|
3762
3892
|
useEffect16(
|
|
3763
3893
|
() => () => {
|
|
@@ -3770,30 +3900,32 @@ function useReadingPosition(options) {
|
|
|
3770
3900
|
const save = useCallback16(
|
|
3771
3901
|
(next) => {
|
|
3772
3902
|
setPosition(next);
|
|
3773
|
-
|
|
3903
|
+
const currentStorage = storageRef.current;
|
|
3904
|
+
if (currentStorage) {
|
|
3774
3905
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
3775
3906
|
timerRef.current = setTimeout(() => {
|
|
3776
3907
|
try {
|
|
3777
|
-
|
|
3908
|
+
currentStorage.setItem(key, serializeReadingPosition(next));
|
|
3778
3909
|
} catch {
|
|
3779
3910
|
}
|
|
3780
3911
|
}, throttleMs);
|
|
3781
3912
|
}
|
|
3782
3913
|
onChangeRef.current?.(next);
|
|
3783
3914
|
},
|
|
3784
|
-
[
|
|
3915
|
+
[key, throttleMs]
|
|
3785
3916
|
);
|
|
3786
3917
|
const clear = useCallback16(() => {
|
|
3787
3918
|
setPosition(null);
|
|
3788
3919
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
3789
|
-
|
|
3920
|
+
const currentStorage = storageRef.current;
|
|
3921
|
+
if (currentStorage) {
|
|
3790
3922
|
try {
|
|
3791
|
-
|
|
3923
|
+
currentStorage.removeItem(key);
|
|
3792
3924
|
} catch {
|
|
3793
3925
|
}
|
|
3794
3926
|
}
|
|
3795
3927
|
onChangeRef.current?.(null);
|
|
3796
|
-
}, [
|
|
3928
|
+
}, [key]);
|
|
3797
3929
|
return { position, save, clear };
|
|
3798
3930
|
}
|
|
3799
3931
|
|
|
@@ -3824,7 +3956,7 @@ export {
|
|
|
3824
3956
|
MejiroSpread,
|
|
3825
3957
|
MejiroStats,
|
|
3826
3958
|
MejiroToc,
|
|
3827
|
-
enMessages,
|
|
3959
|
+
enMessages2 as enMessages,
|
|
3828
3960
|
format,
|
|
3829
3961
|
jaMessages,
|
|
3830
3962
|
resolveMessages,
|