create-asciidoc-multi-book-workspace 0.1.6 → 0.1.7

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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.7 - 2026-06-13
4
+
5
+ ### Added
6
+
7
+ - Add a `复制本书为纯文本` control to each generated book page so readers can copy the expanded single-file book text for saving, note-taking, or model analysis.
8
+ - Add a browser fallback that opens the expanded book text as a UTF-8 plain-text page when clipboard access is unavailable.
9
+
10
+ ### Changed
11
+
12
+ - Keep the generated book page controls self-contained by embedding the already-reduced book text in the HTML instead of adding another published source file.
13
+
3
14
  ## 0.1.6 - 2026-06-09
4
15
 
5
16
  ### Added
@@ -11,6 +11,7 @@ const ANCHOR_PATTERN = /^\[#([A-Za-z0-9_-]+)(?:[.,][^\]]*)?\]$/gm;
11
11
  const LOCAL_TARGET_PATTERN = /\b(?:href|src)="([^"]+)"/g;
12
12
  const SCHEME_PATTERN = /^[a-zA-Z][a-zA-Z0-9+.-]*:/;
13
13
  const HOME_MARKER = "data-multi-book-home";
14
+ const CONTROLS_MARKER = "data-multi-book-controls";
14
15
  const FETCH_DIAGRAMS_ENV = "ADOC_BOOKS_FETCH_DIAGRAMS";
15
16
  const CONFIG_FILE = "adoc-books.config.mjs";
16
17
  const DEFAULT_CONFIG = {
@@ -208,29 +209,35 @@ function escapeHtmlAttribute(value) {
208
209
  .replaceAll("<", "&lt;")
209
210
  .replaceAll(">", "&gt;");
210
211
  }
211
- function addHomeLinkToBookHtml(html, href, homeLink) {
212
- if (html.includes(HOME_MARKER))
212
+ function escapeJsonScript(value) {
213
+ return JSON.stringify(value)
214
+ .replaceAll("<", "\\u003c")
215
+ .replaceAll("\u2028", "\\u2028")
216
+ .replaceAll("\u2029", "\\u2029");
217
+ }
218
+ function addBookControlsToBookHtml(html, href, homeLink, bookSource) {
219
+ if (html.includes(CONTROLS_MARKER))
213
220
  return html;
214
221
  const marker = '<div id="toc" class="toc2">';
215
222
  const index = html.indexOf(marker);
216
223
  if (index === -1)
217
224
  throw new Error("book HTML is missing the left TOC container");
218
225
  const insertAt = index + marker.length;
219
- const homeBlock = `
226
+ const controlsBlock = `
220
227
  <style>
221
- .multi-book-home {
228
+ .multi-book-controls {
222
229
  margin: 0 0 1rem;
223
230
  padding-bottom: .75rem;
224
231
  border-bottom: 1px solid #e5e7eb;
225
232
  }
226
- .multi-book-home a {
233
+ .multi-book-home {
227
234
  color: #1f2937;
228
235
  display: block;
229
236
  font-weight: 600;
230
237
  line-height: 1.35;
231
238
  text-decoration: none;
232
239
  }
233
- .multi-book-home a:hover {
240
+ .multi-book-home:hover {
234
241
  color: #0f766e;
235
242
  text-decoration: underline;
236
243
  }
@@ -241,11 +248,92 @@ function addHomeLinkToBookHtml(html, href, homeLink) {
241
248
  font-weight: 400;
242
249
  margin-top: .15rem;
243
250
  }
251
+ .multi-book-copy-source {
252
+ appearance: none;
253
+ background: #ffffff;
254
+ border: 1px solid #cbd5e1;
255
+ border-radius: 4px;
256
+ color: #1f2937;
257
+ cursor: pointer;
258
+ display: block;
259
+ font-size: .82rem;
260
+ font-weight: 600;
261
+ line-height: 1.2;
262
+ margin-top: .75rem;
263
+ padding: .42rem .5rem;
264
+ text-align: center;
265
+ width: 100%;
266
+ }
267
+ .multi-book-copy-source:hover,
268
+ .multi-book-copy-source:focus {
269
+ border-color: #0f766e;
270
+ color: #0f766e;
271
+ }
272
+ .multi-book-copy-status {
273
+ color: #64748b;
274
+ display: block;
275
+ font-size: .75rem;
276
+ line-height: 1.35;
277
+ margin-top: .4rem;
278
+ min-height: 1em;
279
+ }
244
280
  </style>
245
- <div class="multi-book-home" ${HOME_MARKER}>
246
- <a href="${escapeHtmlAttribute(href)}">${escapeHtmlAttribute(homeLink.label)}<span>${escapeHtmlAttribute(homeLink.subtitle)}</span></a>
247
- </div>`;
248
- return `${html.slice(0, insertAt)}${homeBlock}${html.slice(insertAt)}`;
281
+ <div class="multi-book-controls" ${CONTROLS_MARKER}>
282
+ <a class="multi-book-home" ${HOME_MARKER} href="${escapeHtmlAttribute(href)}">${escapeHtmlAttribute(homeLink.label)}<span>${escapeHtmlAttribute(homeLink.subtitle)}</span></a>
283
+ <button type="button" class="multi-book-copy-source" data-multi-book-source-copy>复制本书为纯文本</button>
284
+ <span class="multi-book-copy-status" data-multi-book-source-status aria-live="polite"></span>
285
+ </div>
286
+ <script type="application/json" id="multi-book-source-data">${escapeJsonScript(bookSource)}</script>
287
+ <script>
288
+ (function () {
289
+ var sourceElement = document.getElementById("multi-book-source-data");
290
+ var button = document.querySelector("[data-multi-book-source-copy]");
291
+ var status = document.querySelector("[data-multi-book-source-status]");
292
+ if (!sourceElement || !button) return;
293
+
294
+ var source = "";
295
+ var fallbackMode = false;
296
+ try {
297
+ source = JSON.parse(sourceElement.textContent || '""');
298
+ } catch (error) {
299
+ if (status) status.textContent = "纯文本数据读取失败";
300
+ button.disabled = true;
301
+ return;
302
+ }
303
+
304
+ function setStatus(message) {
305
+ if (status) status.textContent = message;
306
+ }
307
+
308
+ function openSourcePage() {
309
+ var blob = new Blob([source], { type: "text/plain;charset=utf-8" });
310
+ var url = URL.createObjectURL(blob);
311
+ window.open(url, "_blank", "noopener");
312
+ setTimeout(function () { URL.revokeObjectURL(url); }, 60000);
313
+ setStatus("已尝试打开纯文本页;如果没有出现,请允许弹出窗口后再试");
314
+ }
315
+
316
+ button.addEventListener("click", async function () {
317
+ if (fallbackMode) {
318
+ openSourcePage();
319
+ return;
320
+ }
321
+
322
+ try {
323
+ if (!navigator.clipboard || !navigator.clipboard.writeText) throw new Error("clipboard unavailable");
324
+ await navigator.clipboard.writeText(source);
325
+ button.textContent = "已复制";
326
+ setStatus("");
327
+ setTimeout(function () { button.textContent = "复制本书为纯文本"; }, 1800);
328
+ } catch (error) {
329
+ fallbackMode = true;
330
+ button.textContent = "打开纯文本页";
331
+ openSourcePage();
332
+ }
333
+ });
334
+ }());
335
+ </script>`;
336
+ return `${html.slice(0, insertAt)}${controlsBlock}${html.slice(insertAt)}`;
249
337
  }
250
338
  async function addHomeLinks(rootDir, books, homeLink) {
251
339
  const catalog = path.join(rootDir, "build", "html", "catalog.html");
@@ -253,7 +341,8 @@ async function addHomeLinks(rootDir, books, homeLink) {
253
341
  const htmlFile = path.join(book.htmlOutputDir, "book.html");
254
342
  const html = await readFile(htmlFile, "utf8");
255
343
  const href = path.relative(path.dirname(htmlFile), catalog);
256
- await writeFile(htmlFile, addHomeLinkToBookHtml(html, href, homeLink), "utf8");
344
+ const source = await readFile(path.join(rootDir, "build", "adoc", "books", `${book.bookId}.adoc`), "utf8");
345
+ await writeFile(htmlFile, addBookControlsToBookHtml(html, href, homeLink, source), "utf8");
257
346
  }
258
347
  }
259
348
  async function writeRootIndex(rootDir, rootIndex) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-asciidoc-multi-book-workspace",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Create a complete AsciiDoc multi-book workspace.",
5
5
  "license": "Apache-2.0",
6
6
  "author": {