book-kit 0.0.0-stage → 0.6.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/LICENSE +21 -0
- package/NOTICE.md +72 -0
- package/README.md +251 -2
- package/bin/book-kit.js +115 -0
- package/commands/_emoji_io.py +32 -0
- package/commands/audit-chapter-lengths.js +244 -0
- package/commands/check-epub-size.js +68 -0
- package/commands/check-pdf-page-count.js +162 -0
- package/commands/composite-kindle-emoji.py +85 -0
- package/commands/composite-print-emoji.py +98 -0
- package/commands/conventions.js +76 -0
- package/commands/convert-pdfx.sh +194 -0
- package/commands/download-icc-profile.sh +105 -0
- package/commands/fonts.py +212 -0
- package/commands/generate-reference-docx.py +344 -0
- package/commands/kindle-fonts.js +173 -0
- package/commands/links-to-footnotes.js +366 -0
- package/commands/markdownlint.js +25 -0
- package/commands/minify-css.js +43 -0
- package/commands/optimize-images.js +73 -0
- package/commands/parity.sh +150 -0
- package/commands/postprocess-epub.js +541 -0
- package/commands/prep-print-src.js +77 -0
- package/commands/prose.js +38 -0
- package/commands/release-files.js +89 -0
- package/commands/reorder-print-frontmatter.js +79 -0
- package/commands/strip-page-rules.js +1168 -0
- package/commands/strip-property-rules.js +93 -0
- package/commands/update-key-takeaways.js +314 -0
- package/commands/validate.js +283 -0
- package/configs/markdownlint-cli2.jsonc +98 -0
- package/configs/textlintrc.json +22 -0
- package/css/theme/01-tokens.css +83 -0
- package/css/theme/02-fonts.css +79 -0
- package/css/theme/03-prose.css +53 -0
- package/css/theme/04-base.css +226 -0
- package/css/theme/05-components.css +546 -0
- package/css/theme/06-utilities.css +40 -0
- package/css/theme/07-ereader.css +245 -0
- package/css/theme/08-epub.css +865 -0
- package/css/theme/09-back-matter.css +47 -0
- package/css/theme/10-dark.css +302 -0
- package/css/theme/11-print.css +1229 -0
- package/css/theme.css +38 -0
- package/defaults/book.config.yml +318 -0
- package/defaults/pandoc-base.yml +19 -0
- package/defaults/pandoc-docx.yml +6 -0
- package/defaults/pandoc-epub-fonts.yml +6 -0
- package/defaults/pandoc-epub.yml +15 -0
- package/defaults/pandoc-kindle.yml +6 -0
- package/emoji/1f382.png +0 -0
- package/emoji/1f389.png +0 -0
- package/emoji/1f440.png +0 -0
- package/emoji/1f44d.png +0 -0
- package/emoji/1f44f.png +0 -0
- package/emoji/1f454.png +0 -0
- package/emoji/1f4a1.png +0 -0
- package/emoji/1f4bb.png +0 -0
- package/emoji/1f602.png +0 -0
- package/emoji/1f603.png +0 -0
- package/emoji/1f604.png +0 -0
- package/emoji/1f60a.png +0 -0
- package/emoji/1f60f.png +0 -0
- package/emoji/1f64c.png +0 -0
- package/emoji/1f64f.png +0 -0
- package/emoji/1f914.png +0 -0
- package/emoji/1f923.png +0 -0
- package/emoji/2714.png +0 -0
- package/emoji/274c.png +0 -0
- package/filters/add-div-titles.lua +196 -0
- package/filters/arc-notice.lua +36 -0
- package/filters/body-emoji-images.lua +48 -0
- package/filters/callout-classes.lua +40 -0
- package/filters/lib/book_kit.lua +110 -0
- package/filters/revision.lua +17 -0
- package/filters/strip-comments.lua +40 -0
- package/filters/strip-emoji-kindle.lua +134 -0
- package/fonts/LICENSE-source-code-pro.md +93 -0
- package/fonts/LICENSE-source-sans.md +93 -0
- package/fonts/LICENSE-source-serif.md +93 -0
- package/fonts/recipe.json +41 -0
- package/just/book-kit.just +229 -0
- package/lib/acronyms.js +47 -0
- package/lib/callouts.js +17 -0
- package/lib/config.js +211 -0
- package/lib/is-main.js +21 -0
- package/lib/lint.js +148 -0
- package/lib/pandoc.js +148 -0
- package/lib/paragraphs.js +173 -0
- package/lib/revision.js +35 -0
- package/lib/structure.js +39 -0
- package/lib/textlint-github-formatter.js +58 -0
- package/lib/validate.js +202 -0
- package/package.json +98 -4
- package/presets/business-nonfiction/CONVENTIONS.md +35 -0
- package/presets/business-nonfiction/preset.yml +128 -0
- package/profiles/pdfx-def.ps +42 -0
- package/requirements.txt +14 -0
- package/rules/no-ai-like-patterns.js +209 -0
- package/rules/no-assumptive.js +76 -0
- package/rules/no-bare-acronyms.js +129 -0
- package/rules/no-cliches.js +544 -0
- package/rules/no-cultural-idioms.js +422 -0
- package/rules/no-vague-intensifiers.js +94 -0
- package/templates/template-epub.html +79 -0
- package/templates/template.html +73 -0
- package/toolchain.json +42 -0
- package/validators/acronym-expansion.js +94 -0
- package/validators/bold-keywords.js +56 -0
- package/validators/callout-balance.js +74 -0
- package/validators/callout-divs.js +66 -0
- package/validators/callout-labels.js +102 -0
- package/validators/chapter-lengths.js +14 -0
- package/validators/chapter.js +57 -0
- package/validators/cross-references.js +45 -0
- package/validators/doubled-words.js +76 -0
- package/validators/duplicate-headings.js +52 -0
- package/validators/em-dashes.js +73 -0
- package/validators/escaped-smallcaps.js +46 -0
- package/validators/footnotes.js +78 -0
- package/validators/heading-hierarchy.js +58 -0
- package/validators/hypothetical-hooks.js +103 -0
- package/validators/index-integrity.js +28 -0
- package/validators/index.js +73 -0
- package/validators/list-punctuation.js +123 -0
- package/validators/min-opener-length.js +87 -0
- package/validators/no-bare-url-link-text.js +38 -0
- package/validators/no-todo-markers.js +49 -0
- package/validators/no-transition-todos.js +34 -0
- package/validators/number-range-dashes.js +72 -0
- package/validators/paragraph-length.js +62 -0
- package/validators/quote-punctuation.js +85 -0
- package/validators/section-intro-final-lead.js +90 -0
- package/validators/section-intro-has-lead.js +41 -0
- package/validators/section-intro-no-tldr.js +37 -0
- package/validators/section-intros.js +33 -0
- package/validators/sentence-starters.js +132 -0
- package/validators/smallcaps.js +67 -0
- package/validators/tldr-length.js +58 -0
package/css/theme.css
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/* book-kit theme - Tailwind CSS v4 stylesheet
|
|
2
|
+
(imported by a book's src/style.css after @import "tailwindcss")
|
|
3
|
+
Custom styles for multi-format book output (HTML, EPUB, PDF)
|
|
4
|
+
Following Tailwind v4 CSS-first configuration best practices */
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
CSS Pipeline:
|
|
8
|
+
src/style.css (this file)
|
|
9
|
+
→ PostCSS (npm run build:css) → dist/style.css
|
|
10
|
+
├── HTML: used directly with full CSS feature support
|
|
11
|
+
├── PDF: WeasyPrint renders @media print rules (CSS Paged Media)
|
|
12
|
+
└── EPUB: commands/strip-page-rules.js → dist/style-epub.css
|
|
13
|
+
(strips @page, @layer, var(), calc(), oklch() etc.)
|
|
14
|
+
└── Kindle: commands/kindle-fonts.js replaces variable fonts
|
|
15
|
+
with static weight-specific font declarations
|
|
16
|
+
|
|
17
|
+
Format-specific notes:
|
|
18
|
+
• @media print — PDF/WeasyPrint: absolute pt sizes, float:footnote, @page rules
|
|
19
|
+
• @media not screen — EPUB overrides: em headings, inherit body typography, hyphens
|
|
20
|
+
• @media (prefers-color-scheme: dark) — Apple Books, Kobo dark mode adaptation
|
|
21
|
+
• WeasyPrint ignores variable font-weight ranges — explicit @font-face needed
|
|
22
|
+
• EPUB strips ::before labels — add-div-titles.lua injects text nodes instead
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/* The theme is split by section into css/theme/, imported here in cascade
|
|
26
|
+
order. A book can import this file for all of it, or the parts it wants
|
|
27
|
+
(keeping this order), e.g. everything but 09-back-matter.css. */
|
|
28
|
+
@import "./theme/01-tokens.css";
|
|
29
|
+
@import "./theme/02-fonts.css";
|
|
30
|
+
@import "./theme/03-prose.css";
|
|
31
|
+
@import "./theme/04-base.css";
|
|
32
|
+
@import "./theme/05-components.css";
|
|
33
|
+
@import "./theme/06-utilities.css";
|
|
34
|
+
@import "./theme/07-ereader.css";
|
|
35
|
+
@import "./theme/08-epub.css";
|
|
36
|
+
@import "./theme/09-back-matter.css";
|
|
37
|
+
@import "./theme/10-dark.css";
|
|
38
|
+
@import "./theme/11-print.css";
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# book-kit defaults. Every key here can be overridden in a book's
|
|
2
|
+
# `book.config.yml`. Maps are deep-merged; lists replace the default wholesale.
|
|
3
|
+
#
|
|
4
|
+
# This file doubles as the config reference: each key is documented where it's
|
|
5
|
+
# defined, so there's one place to look.
|
|
6
|
+
|
|
7
|
+
# Where the book's files live, relative to the directory holding book.config.yml.
|
|
8
|
+
paths:
|
|
9
|
+
src: src
|
|
10
|
+
dist: dist
|
|
11
|
+
index: index.yml # pandoc defaults file listing `input-files` in reading order
|
|
12
|
+
css: src/style.css # Tailwind entry point (imports the kit theme)
|
|
13
|
+
cover: img/cover.jpeg
|
|
14
|
+
img: img
|
|
15
|
+
fonts: fonts # if missing, the build symlinks the kit's fonts here (gitignore it)
|
|
16
|
+
version: VERSION # plain-text semver, stamped into the build as "<version> (<sha>)"
|
|
17
|
+
epub_metadata: epub.xml # Dublin Core supplement for the EPUB OPF; generated from metadata if absent
|
|
18
|
+
|
|
19
|
+
# Output files are dist/<basename>.epub, <basename>.pdf, <basename>-kindle.epub, etc.
|
|
20
|
+
output:
|
|
21
|
+
basename: book
|
|
22
|
+
# The PDF/X-1a file is dist/<basename><pdfx_suffix>.pdf. A book that already
|
|
23
|
+
# ships it under another name (e.g. -printer) can keep that name.
|
|
24
|
+
pdfx_suffix: -pdfx
|
|
25
|
+
|
|
26
|
+
# Bibliographic metadata. Passed to pandoc as metadata, so it lands in the
|
|
27
|
+
# EPUB OPF, the HTML <head>, and the PDF info dictionary.
|
|
28
|
+
metadata:
|
|
29
|
+
title: Untitled Book
|
|
30
|
+
subtitle: ""
|
|
31
|
+
description: ""
|
|
32
|
+
author: []
|
|
33
|
+
lang: en-US
|
|
34
|
+
rights: ""
|
|
35
|
+
publisher: ""
|
|
36
|
+
isbn_paperback: ""
|
|
37
|
+
isbn_ebook: ""
|
|
38
|
+
pub_date: "" # YYYY-MM-DD
|
|
39
|
+
copyright_year: ""
|
|
40
|
+
uuid: "" # stable EPUB identifier; mint once, never change
|
|
41
|
+
bisac: [] # BISAC codes, e.g. REF000000
|
|
42
|
+
keywords: [] # retailer keyword phrases
|
|
43
|
+
subjects: [] # <dc:subject> lines for the generated epub.xml
|
|
44
|
+
|
|
45
|
+
# Reserved: no kit command reads this yet. Public site for the book, if any,
|
|
46
|
+
# for books' own tooling. Leave empty if there isn't one.
|
|
47
|
+
site_url: ""
|
|
48
|
+
|
|
49
|
+
# EPUB accessibility metadata (schema.org properties in the OPF). The kit's
|
|
50
|
+
# markup is built for WCAG 2.1 AA (semantic structure, DPUB-ARIA roles, alt
|
|
51
|
+
# text, reflowable text), but a conformance claim is the publisher's to make:
|
|
52
|
+
# conforms_to, summary, and certified_by are empty until the book sets them,
|
|
53
|
+
# after checking its own content (e.g. with Ace by DAISY).
|
|
54
|
+
accessibility:
|
|
55
|
+
access_modes: [textual]
|
|
56
|
+
access_mode_sufficient: [textual]
|
|
57
|
+
features: [alternativeText, readingOrder, structuralNavigation, tableOfContents, displayTransformability, ARIA]
|
|
58
|
+
hazards: [none]
|
|
59
|
+
summary: "" # e.g. "This publication conforms to WCAG 2.1 Level AA. ..."
|
|
60
|
+
conforms_to: "" # e.g. "EPUB Accessibility 1.1 - WCAG 2.1 Level AA"
|
|
61
|
+
certified_by: "" # who makes the claim; defaults to metadata.publisher once conforms_to is set
|
|
62
|
+
conformance_url: "" # public accessibility statement, e.g. https://example.com/accessibility/
|
|
63
|
+
# The summary in other languages, keyed by primary language subtag, for
|
|
64
|
+
# translated editions (an EPUB's dc:language "pt-BR" uses pt). Used only
|
|
65
|
+
# when conforms_to is set, since these describe a WCAG 2.1 AA claim. The
|
|
66
|
+
# kit's are drafts: have a native speaker review one before shipping it.
|
|
67
|
+
summary_translations:
|
|
68
|
+
pt: Esta publicação está em conformidade com as WCAG 2.1 Nível AA. É bem estruturada, com ordem de leitura lógica e sumário navegável, e utiliza marcação semântica em todo o conteúdo. Todo o conteúdo é textual.
|
|
69
|
+
es: Esta publicación cumple con las WCAG 2.1 Nivel AA. Está bien estructurada, con un orden de lectura lógico y una tabla de contenidos navegable, y utiliza marcado semántico en todo el contenido. Todo el contenido es textual.
|
|
70
|
+
de: Diese Publikation erfüllt die WCAG 2.1 Stufe AA. Sie ist gut strukturiert, mit logischer Lesereihenfolge und navigierbarem Inhaltsverzeichnis, und verwendet durchgängig semantisches Markup. Alle Inhalte sind textbasiert.
|
|
71
|
+
|
|
72
|
+
# Text the build adds to the book, for books in other languages.
|
|
73
|
+
strings:
|
|
74
|
+
copyright_nav: Copyright # the copyright page's entry in the EPUB table of contents
|
|
75
|
+
cover_label: "Book cover:" # screen-reader label before the title on the EPUB cover page
|
|
76
|
+
# The notice the ARC builds (`just arc`) put at the top of the copyright page.
|
|
77
|
+
arc_notice:
|
|
78
|
+
heading: Advance Reader Copy — not for sale or redistribution.
|
|
79
|
+
body: This advance copy is licensed to you for review purposes only. Please don’t forward, upload, share, or resell this file. Brief quotations in a review are welcome and encouraged.
|
|
80
|
+
|
|
81
|
+
# Extra pandoc metadata merged into every format, for anything the keys above
|
|
82
|
+
# don't cover (e.g. belongs-to-collection).
|
|
83
|
+
pandoc_metadata: {}
|
|
84
|
+
|
|
85
|
+
# Print (PDF) settings.
|
|
86
|
+
print:
|
|
87
|
+
trim: [6, 9] # inches; must match the @page size in the CSS (`just check` verifies the PDF)
|
|
88
|
+
page_count: null # set to lock the PDF page count in CI (null = no lock)
|
|
89
|
+
pdfx_title: "" # PDF/X-1a info-dictionary title; defaults to metadata.title
|
|
90
|
+
|
|
91
|
+
# Format checks (`just check`).
|
|
92
|
+
checks:
|
|
93
|
+
# Size budget for each EPUB, retail and ARC, in MB. Retailers charge delivery
|
|
94
|
+
# fees by file size and some cap uploads, so a jump usually means an image
|
|
95
|
+
# went in uncompressed.
|
|
96
|
+
epub_max_mb: 2
|
|
97
|
+
|
|
98
|
+
# Callout registry, in priority order. Each entry is a fenced div class:
|
|
99
|
+
#
|
|
100
|
+
# ::: {.tip custom-style="tip"}
|
|
101
|
+
# ...
|
|
102
|
+
# :::
|
|
103
|
+
#
|
|
104
|
+
# class fenced-div class name
|
|
105
|
+
# label visible prefix the build injects ("Tip:"); omit for none
|
|
106
|
+
# aria aria-label for the div (role="doc-notice")
|
|
107
|
+
# emoji emoji shown before the label in EPUB
|
|
108
|
+
# emoji_cp Twemoji codepoint for the PNG used in Kindle/PDF (e-ink and
|
|
109
|
+
# WeasyPrint can't render color emoji text)
|
|
110
|
+
# emoji_alt alt text for that PNG
|
|
111
|
+
# pair_with another callout class a chapter using this one should also
|
|
112
|
+
# use; the callout-balance validator warns when it doesn't
|
|
113
|
+
# (list it on both entries for a two-way pair)
|
|
114
|
+
# generic false to style this class yourself instead of the theme's
|
|
115
|
+
# generic callout look (below)
|
|
116
|
+
#
|
|
117
|
+
# The labels are injected by the build; writers never type them. Validators
|
|
118
|
+
# flag a callout whose body starts with its own label. The kit theme styles
|
|
119
|
+
# these three and the business-nonfiction preset's six (tldr, pro-tip,
|
|
120
|
+
# for-managers, for-ics, common-objection, takeaways) each their own way. A
|
|
121
|
+
# class the book adds gets the .note look and its label in every format
|
|
122
|
+
# (filters/callout-classes.lua); restyle it in the book's CSS if you like.
|
|
123
|
+
callouts:
|
|
124
|
+
- class: note
|
|
125
|
+
label: "Note:"
|
|
126
|
+
aria: Note
|
|
127
|
+
- class: tip
|
|
128
|
+
label: "Tip:"
|
|
129
|
+
aria: Tip
|
|
130
|
+
- class: warning
|
|
131
|
+
label: "Warning:"
|
|
132
|
+
aria: Warning
|
|
133
|
+
|
|
134
|
+
# Emoji images. Color emoji don't render on Kindle e-ink and crash WeasyPrint
|
|
135
|
+
# in CSS content:, so those builds swap in Twemoji PNGs (CC-BY 4.0). The kit's
|
|
136
|
+
# composite scripts derive per-format variants from source_dir.
|
|
137
|
+
emoji:
|
|
138
|
+
source_dir: "" # empty = the kit's emoji/ directory
|
|
139
|
+
kindle_dir: dist/.book-kit/emoji-kindle/ # book-relative (EPUB embeds it)
|
|
140
|
+
pdf_callout_dir: .book-kit/emoji-print-callout/ # relative to dist/ (the PDF base URL)
|
|
141
|
+
pdf_body_dir: .book-kit/emoji-print-body/ # relative to dist/
|
|
142
|
+
# Emoji in body text that the Kindle and PDF builds swap for a PNG from
|
|
143
|
+
# source_dir: emoji (the exact character sequence) -> cp (the PNG's file
|
|
144
|
+
# name) and alt (its alt text). kindle: false keeps one as text on Kindle.
|
|
145
|
+
# A key can include a variation selector ("\u2744\uFE0F") so the selector
|
|
146
|
+
# is consumed with the emoji instead of rendering as a missing glyph.
|
|
147
|
+
# A book adds an emoji with an entry here plus its PNG in source_dir (copy
|
|
148
|
+
# the kit's emoji/ there first). On Kindle, emoji not listed are dropped.
|
|
149
|
+
images:
|
|
150
|
+
"\U0001F44D": { cp: 1f44d, alt: thumbs-up }
|
|
151
|
+
"\U0001F389": { cp: 1f389, alt: party }
|
|
152
|
+
"\U0001F602": { cp: 1f602, alt: laughing }
|
|
153
|
+
"\U0001F440": { cp: 1f440, alt: eyes }
|
|
154
|
+
"\U0001F604": { cp: 1f604, alt: grin }
|
|
155
|
+
"\U0001F60A": { cp: 1f60a, alt: smile }
|
|
156
|
+
"\U0001F603": { cp: 1f603, alt: smiley }
|
|
157
|
+
"\U0001F923": { cp: 1f923, alt: rofl }
|
|
158
|
+
"\U0001F44F": { cp: 1f44f, alt: clap }
|
|
159
|
+
"\U0001F64F": { cp: 1f64f, alt: folded-hands }
|
|
160
|
+
"\U0001F64C": { cp: 1f64c, alt: raised-hands }
|
|
161
|
+
"\U0001F914": { cp: 1f914, alt: thinking }
|
|
162
|
+
"\U0001F60F": { cp: 1f60f, alt: smirk }
|
|
163
|
+
"\U0001F382": { cp: 1f382, alt: cake }
|
|
164
|
+
"\U0001F4A1": { cp: 1f4a1, alt: light-bulb }
|
|
165
|
+
"\U0001F454": { cp: 1f454, alt: necktie }
|
|
166
|
+
"\U0001F4BB": { cp: 1f4bb, alt: laptop }
|
|
167
|
+
# Do/don't markers. Their text glyphs render poorly in both builds (tofu on
|
|
168
|
+
# Kindle, small and high in WeasyPrint). ✅ uses 2714, a heavy check with
|
|
169
|
+
# no box, not its own 2705 glyph, which is a check on a filled square.
|
|
170
|
+
"❌": { cp: 274c, alt: cross }
|
|
171
|
+
"✅": { cp: "2714", alt: check }
|
|
172
|
+
|
|
173
|
+
# Chapter structure conventions used by the validators, dashboard, and audits.
|
|
174
|
+
structure:
|
|
175
|
+
# Pages that aren't narrative chapters (matched by file slug; a trailing *
|
|
176
|
+
# matches a prefix). The prose validators and audits skip them, as they do
|
|
177
|
+
# takeaways collections and *-scratch.md drafts.
|
|
178
|
+
structural_slugs: [copyright, dedication, acknowledgements, about-the-author, back-cover, disclaimer]
|
|
179
|
+
# Drafts: files whose name ends with this suffix (before .md) aren't part of
|
|
180
|
+
# the book yet. The validators, audits, markdownlint, and the Claude chapter
|
|
181
|
+
# hook skip them.
|
|
182
|
+
draft_suffix: -scratch
|
|
183
|
+
# Chapters whose filename ends with this suffix collect the section's TL;DRs
|
|
184
|
+
# (generated by `just key-takeaways`); the validators skip them.
|
|
185
|
+
takeaways_suffix: -key-takeaways
|
|
186
|
+
# index.yml sections (comment names) that don't get a takeaways chapter.
|
|
187
|
+
takeaways_skip_sections: [Front matter, Appendix, Conclusion, Back cover]
|
|
188
|
+
# Every chapter must open with this callout class (null to disable;
|
|
189
|
+
# business-nonfiction sets tldr).
|
|
190
|
+
required_callout: null
|
|
191
|
+
# Heading id where Kindle's "Start Reading" lands. Empty = the first file in
|
|
192
|
+
# index.yml that isn't a structural page.
|
|
193
|
+
start_reading: ""
|
|
194
|
+
|
|
195
|
+
# Reserved: no kit command reads these yet (the kit ships no LLM review tools;
|
|
196
|
+
# the Claude Code agents read the book's CLAUDE.md instead). Kept so books
|
|
197
|
+
# that set them, or their own tooling, keep working. Neutral by default.
|
|
198
|
+
llm:
|
|
199
|
+
title: "" # defaults to metadata.title
|
|
200
|
+
voice: the author's voice
|
|
201
|
+
audience: ""
|
|
202
|
+
|
|
203
|
+
# Linting and validation.
|
|
204
|
+
# Paths in lint and validators are relative to the book root, as in index.yml
|
|
205
|
+
# (e.g. src/intro.md).
|
|
206
|
+
lint:
|
|
207
|
+
acronyms: [] # extra acronyms that must be written [ACRONYM]{.smallcaps}
|
|
208
|
+
universal_acronyms: [] # extra acronyms readers know without expansion
|
|
209
|
+
exclude: [] # files the per-chapter checks skip (index.yml, TODO, required callout)
|
|
210
|
+
generated: [] # machine-generated pages the lexical validators skip
|
|
211
|
+
dictionary: dictionary.txt # reserved: no kit command reads this yet
|
|
212
|
+
# `just prose` (book-kit prose) runs textlint with configs/textlintrc.json,
|
|
213
|
+
# whose rules are mechanical (misspellings, doubled spaces, unmatched
|
|
214
|
+
# pairs), deep-merged with this map. Add or tune any installed textlint
|
|
215
|
+
# rule here, e.g. {rules: {max-comma: {max: 4}}}.
|
|
216
|
+
textlint: {}
|
|
217
|
+
# The kit's own textlint rules (rules/), by name: true, false, or an options
|
|
218
|
+
# object (each takes `allow`, a list of findings to skip by message text).
|
|
219
|
+
# Off by default; business-nonfiction turns them on. Rules in a preset's
|
|
220
|
+
# rules/ directory are on unless set to false here.
|
|
221
|
+
rules:
|
|
222
|
+
no-ai-like-patterns: false
|
|
223
|
+
no-assumptive: false
|
|
224
|
+
no-bare-acronyms: false
|
|
225
|
+
no-cliches: false
|
|
226
|
+
no-cultural-idioms: false
|
|
227
|
+
no-vague-intensifiers: false
|
|
228
|
+
# `just lint` (book-kit markdownlint) runs markdownlint-cli2 with
|
|
229
|
+
# configs/markdownlint-cli2.jsonc, whose `config` this map is deep-merged
|
|
230
|
+
# into, e.g. {proper-names: {names: [GitHub]}}.
|
|
231
|
+
markdownlint: {}
|
|
232
|
+
# A book with its own .textlintrc or .markdownlint-cli2.jsonc uses that file
|
|
233
|
+
# instead, and these three keys don't apply to it.
|
|
234
|
+
|
|
235
|
+
validators:
|
|
236
|
+
# error | warn | on | off, keyed by validator name (`book-kit validate
|
|
237
|
+
# --list`). "on" runs a validator at its own severity, for turning one back
|
|
238
|
+
# on after a lower layer turned it off.
|
|
239
|
+
severity:
|
|
240
|
+
# A link to a missing anchor fails EPUBCheck (RSC-012); catch it here
|
|
241
|
+
# first, with the file and line.
|
|
242
|
+
cross-references: error
|
|
243
|
+
# House-style checks: off by default, on in the business-nonfiction
|
|
244
|
+
# preset. They encode one style (section-opener intros, TL;DRs, role
|
|
245
|
+
# callouts, smallcaps acronyms, dash and quote rules, prose habits)
|
|
246
|
+
# rather than correctness.
|
|
247
|
+
section-intros: "off"
|
|
248
|
+
section-intro-no-tldr: "off"
|
|
249
|
+
section-intro-has-lead: "off"
|
|
250
|
+
section-intro-final-lead: "off"
|
|
251
|
+
no-transition-todos: "off"
|
|
252
|
+
min-opener-length: "off"
|
|
253
|
+
chapter-lengths: "off"
|
|
254
|
+
smallcaps: "off"
|
|
255
|
+
escaped-smallcaps: "off"
|
|
256
|
+
callout-balance: "off"
|
|
257
|
+
tldr-length: "off"
|
|
258
|
+
acronym-expansion: "off"
|
|
259
|
+
em-dashes: "off"
|
|
260
|
+
number-range-dashes: "off"
|
|
261
|
+
quote-punctuation: "off"
|
|
262
|
+
sentence-starters: "off"
|
|
263
|
+
list-punctuation: "off"
|
|
264
|
+
bold-keywords: "off"
|
|
265
|
+
hypothetical-hooks: "off"
|
|
266
|
+
paragraph-length: "off"
|
|
267
|
+
# Intentional exceptions, keyed by validator name. Entries are "file:line"
|
|
268
|
+
# (sentence-starters, paragraph-length, hypothetical-hooks) or file paths
|
|
269
|
+
# (callout-balance).
|
|
270
|
+
allow: {}
|
|
271
|
+
# Tunable thresholds, keyed by validator name and merged over the
|
|
272
|
+
# validator's own defaults (maps merge; lists replace). `book-kit validate
|
|
273
|
+
# --list --verbose` shows every validator. The kit's options:
|
|
274
|
+
# min-opener-length min_words: 60
|
|
275
|
+
# tldr-length min_words: 8, max_words: 80
|
|
276
|
+
# chapter-lengths min_words: 500, max_words: 2500
|
|
277
|
+
# paragraph-length max_words: 120
|
|
278
|
+
# sentence-starters threshold: 3 (consecutive sentences, same first word)
|
|
279
|
+
# doubled-words allowlist: [had, that]
|
|
280
|
+
# hypothetical-hooks extra_patterns: [] (regex sources, case-insensitive)
|
|
281
|
+
# callout-balance names: {for-ics: For ICs} (class -> name in warnings)
|
|
282
|
+
# e.g. options: { tldr-length: { max_words: 60 } }
|
|
283
|
+
options: {}
|
|
284
|
+
# Book-specific validators, run after the kit's and any preset's: ES
|
|
285
|
+
# modules exporting { name, tags, defaults, run(ctx) }, like the built-ins in
|
|
286
|
+
# the kit's validators/. Each entry is a book-relative path (./checks/x.js)
|
|
287
|
+
# or an npm package name installed in the book.
|
|
288
|
+
plugins: []
|
|
289
|
+
|
|
290
|
+
# Hooks for book-specific build steps.
|
|
291
|
+
build:
|
|
292
|
+
# The Lua filter chain for each format, in order. An entry is a kit filter
|
|
293
|
+
# name (filters/<name>.lua; a preset's filters/ directory can add or shadow
|
|
294
|
+
# one), a book-relative path ("./filters/mine.lua"), or the literal
|
|
295
|
+
# extra_filters, which stands for that format's extra_filters below. Lists
|
|
296
|
+
# replace wholesale, so a book that sets one restates the whole chain.
|
|
297
|
+
# arc-notice is a no-op unless the ARC recipes pass --metadata arc=true.
|
|
298
|
+
filters:
|
|
299
|
+
epub: [callout-classes, revision, add-div-titles, extra_filters, arc-notice, strip-comments]
|
|
300
|
+
kindle: [callout-classes, revision, add-div-titles, strip-emoji-kindle, extra_filters, arc-notice, strip-comments]
|
|
301
|
+
html: [callout-classes, revision, extra_filters, strip-comments]
|
|
302
|
+
pdf: [callout-classes, revision, add-div-titles, body-emoji-images, extra_filters]
|
|
303
|
+
docx: [callout-classes, revision, add-div-titles, extra_filters]
|
|
304
|
+
# pandoc templates per format: a kit or preset template name (templates/),
|
|
305
|
+
# or a book-relative path. A format without one uses pandoc's default
|
|
306
|
+
# (the PDF build's HTML, and DOCX, which uses reference.docx instead).
|
|
307
|
+
templates:
|
|
308
|
+
epub: template-epub.html
|
|
309
|
+
kindle: template-epub.html
|
|
310
|
+
html: template.html
|
|
311
|
+
extra_filters: # extra Lua filters (book-relative paths), per format; slot in where filters lists extra_filters
|
|
312
|
+
epub: []
|
|
313
|
+
kindle: []
|
|
314
|
+
html: []
|
|
315
|
+
pdf: []
|
|
316
|
+
docx: []
|
|
317
|
+
print_exclude: [] # src-relative files dropped from PDF/Kindle builds
|
|
318
|
+
print_inject: {} # index.yml marker comment -> src-relative file, PDF only
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Shared pandoc options for the HTML and PDF builds. Identity metadata (title,
|
|
2
|
+
# author, ISBN, ...) is NOT here: the build generates it from book.config.yml
|
|
3
|
+
# into dist/.book-kit/pandoc-<format>.yml, passed after this file so it wins.
|
|
4
|
+
from: markdown+yaml_metadata_block+smart+auto_identifiers+footnotes+strikeout+implicit_figures+emoji+fenced_divs+inline_code_attributes+raw_attribute-citations
|
|
5
|
+
table-of-contents: true
|
|
6
|
+
toc-depth: 2
|
|
7
|
+
standalone: true
|
|
8
|
+
pdf-engine: weasyprint
|
|
9
|
+
metadata:
|
|
10
|
+
documentclass: book
|
|
11
|
+
css: style.css
|
|
12
|
+
document-css: false
|
|
13
|
+
top-level-division: chapter
|
|
14
|
+
embed-resources: true
|
|
15
|
+
pdf-engine-opts:
|
|
16
|
+
- --base-url=./dist/
|
|
17
|
+
- --pdf-version=1.4
|
|
18
|
+
- --dpi=300
|
|
19
|
+
- --optimize-images
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# DOCX for editorial workflows (track changes with an editor).
|
|
2
|
+
from: markdown+yaml_metadata_block+smart+auto_identifiers+footnotes+strikeout+implicit_figures+emoji+fenced_divs+inline_code_attributes+raw_attribute-citations
|
|
3
|
+
table-of-contents: true
|
|
4
|
+
toc-depth: 2
|
|
5
|
+
standalone: true
|
|
6
|
+
top-level-division: chapter
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Font embedding for standard EPUB (Apple Books, Kobo, etc.): variable WOFF2
|
|
2
|
+
# with the full weight range and optical sizing. Split from pandoc-epub.yml
|
|
3
|
+
# because Kindle needs static fonts and pandoc merges (not replaces)
|
|
4
|
+
# epub-fonts across defaults files.
|
|
5
|
+
epub-fonts:
|
|
6
|
+
- "./fonts/*.woff2"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# EPUB 3 options (KDP, Apple Books, Kobo, D2D, IngramSpark). Identity metadata
|
|
2
|
+
# comes from the generated dist/.book-kit/pandoc-epub.yml.
|
|
3
|
+
from: markdown+yaml_metadata_block+smart+auto_identifiers+footnotes+strikeout+implicit_figures+emoji+fenced_divs+inline_code_attributes+raw_attribute-citations
|
|
4
|
+
table-of-contents: true
|
|
5
|
+
toc-depth: 2
|
|
6
|
+
standalone: true
|
|
7
|
+
epub-chapter-level: 1
|
|
8
|
+
metadata:
|
|
9
|
+
documentclass: book
|
|
10
|
+
css: style-epub.css
|
|
11
|
+
document-css: false
|
|
12
|
+
ibooks:
|
|
13
|
+
version: '1.0'
|
|
14
|
+
top-level-division: chapter
|
|
15
|
+
embed-resources: true
|
package/emoji/1f382.png
ADDED
|
Binary file
|
package/emoji/1f389.png
ADDED
|
Binary file
|
package/emoji/1f440.png
ADDED
|
Binary file
|
package/emoji/1f44d.png
ADDED
|
Binary file
|
package/emoji/1f44f.png
ADDED
|
Binary file
|
package/emoji/1f454.png
ADDED
|
Binary file
|
package/emoji/1f4a1.png
ADDED
|
Binary file
|
package/emoji/1f4bb.png
ADDED
|
Binary file
|
package/emoji/1f602.png
ADDED
|
Binary file
|
package/emoji/1f603.png
ADDED
|
Binary file
|
package/emoji/1f604.png
ADDED
|
Binary file
|
package/emoji/1f60a.png
ADDED
|
Binary file
|
package/emoji/1f60f.png
ADDED
|
Binary file
|
package/emoji/1f64c.png
ADDED
|
Binary file
|
package/emoji/1f64f.png
ADDED
|
Binary file
|
package/emoji/1f914.png
ADDED
|
Binary file
|
package/emoji/1f923.png
ADDED
|
Binary file
|
package/emoji/2714.png
ADDED
|
Binary file
|
package/emoji/274c.png
ADDED
|
Binary file
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
-- Lua filter that injects callout labels and ARIA roles.
|
|
2
|
+
--
|
|
3
|
+
-- Callout prefixes ("TL;DR:", "💡 Pro-Tip:", ...) are drawn by CSS ::before in
|
|
4
|
+
-- HTML, but EPUB readers and DOCX don't support pseudo-elements, so the build
|
|
5
|
+
-- adds them to the content here. For PDF, emoji text in CSS content: crashes
|
|
6
|
+
-- WeasyPrint (Pango ZeroDivisionError), so Kindle and PDF get Twemoji <img>s.
|
|
7
|
+
--
|
|
8
|
+
-- The callouts themselves come from the `callouts` registry in book.config.yml
|
|
9
|
+
-- (see the kit's defaults/book.config.yml), read from the JSON the build writes
|
|
10
|
+
-- to $BOOK_KIT_CONFIG.
|
|
11
|
+
--
|
|
12
|
+
-- Metadata flags:
|
|
13
|
+
-- --metadata kindle=true → Kindle Twemoji images (emoji.kindle_dir)
|
|
14
|
+
-- --metadata pdf=true → PDF Twemoji images (emoji.pdf_callout_dir)
|
|
15
|
+
-- Translated builds can override labels with `callout-labels` / `callout-aria`
|
|
16
|
+
-- metadata maps keyed by callout class.
|
|
17
|
+
|
|
18
|
+
package.path = pandoc.path.directory(PANDOC_SCRIPT_FILE) .. "/lib/?.lua;" .. package.path
|
|
19
|
+
local book_kit = require("book_kit")
|
|
20
|
+
|
|
21
|
+
local config = book_kit.config()
|
|
22
|
+
local callouts = config.callouts or {}
|
|
23
|
+
local emoji_dirs = config.emoji or {}
|
|
24
|
+
local output_mode = "epub" -- default
|
|
25
|
+
|
|
26
|
+
-- First pass: read metadata to detect output mode + pick up localized labels.
|
|
27
|
+
local function readMeta(meta)
|
|
28
|
+
if meta.kindle then
|
|
29
|
+
output_mode = "kindle"
|
|
30
|
+
elseif meta.pdf then
|
|
31
|
+
output_mode = "pdf"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
local labels, aria = meta['callout-labels'], meta['callout-aria']
|
|
35
|
+
for _, c in ipairs(callouts) do
|
|
36
|
+
if labels and labels[c.class] then c.label = pandoc.utils.stringify(labels[c.class]) end
|
|
37
|
+
if aria and aria[c.class] then c.aria = pandoc.utils.stringify(aria[c.class]) end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
-- Helper function to check if content starts with a specific text
|
|
42
|
+
local function startsWithText(content, text)
|
|
43
|
+
if #content == 0 then return false end
|
|
44
|
+
|
|
45
|
+
-- Stringify the first element to handle any inline type
|
|
46
|
+
-- (Str, Strong, SmallCaps, Emph, etc.)
|
|
47
|
+
local first_str = pandoc.utils.stringify(content[1])
|
|
48
|
+
-- Use literal prefix comparison (not Lua patterns) to avoid
|
|
49
|
+
-- metacharacter issues with labels like "Pro-Tip:"
|
|
50
|
+
return first_str:sub(1, #text) == text
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
-- The symbol for a callout in the current output mode: emoji text for EPUB,
|
|
54
|
+
-- a Twemoji image spec for Kindle/PDF, or nil when the callout has none.
|
|
55
|
+
local function symbolFor(c)
|
|
56
|
+
if not c.emoji then return nil end
|
|
57
|
+
if output_mode == "epub" then return c.emoji end
|
|
58
|
+
local dir = output_mode == "kindle" and emoji_dirs.kindle_dir or emoji_dirs.pdf_callout_dir
|
|
59
|
+
return {cp = c.emoji_cp, alt = c.emoji_alt, dir = dir}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
-- Helper: prepend a symbol + label to a div's first paragraph
|
|
63
|
+
local function prependCalloutLabel(div, symbol_char, label_text)
|
|
64
|
+
-- Uses callout-label class instead of SmallCaps because e-readers
|
|
65
|
+
-- synthesize small caps poorly (crude all-caps), while PDF uses
|
|
66
|
+
-- real OpenType small caps via CSS ::before. The callout-label class
|
|
67
|
+
-- provides a clean sans-serif label that approximates the PDF look.
|
|
68
|
+
local label = pandoc.Strong({pandoc.Span({pandoc.Str(label_text)}, pandoc.Attr("", {"callout-label"}, {}))})
|
|
69
|
+
local space = pandoc.Space()
|
|
70
|
+
|
|
71
|
+
local prefix
|
|
72
|
+
if type(symbol_char) == "table" then
|
|
73
|
+
-- Kindle/PDF: inline Twemoji image + label on same line
|
|
74
|
+
local img_size = output_mode == "kindle" and "24" or "16"
|
|
75
|
+
local img = pandoc.Image(
|
|
76
|
+
{pandoc.Str(symbol_char.alt)},
|
|
77
|
+
symbol_char.dir .. symbol_char.cp .. ".png",
|
|
78
|
+
"",
|
|
79
|
+
pandoc.Attr("", {"emoji", "callout-emoji"}, {{"width", img_size}, {"height", img_size}})
|
|
80
|
+
)
|
|
81
|
+
prefix = {img, pandoc.Space(), label, space}
|
|
82
|
+
else
|
|
83
|
+
-- EPUB: inline emoji (if any) + label on same line
|
|
84
|
+
prefix = {}
|
|
85
|
+
if symbol_char then
|
|
86
|
+
table.insert(prefix, pandoc.Str(symbol_char))
|
|
87
|
+
table.insert(prefix, pandoc.Space())
|
|
88
|
+
end
|
|
89
|
+
table.insert(prefix, label)
|
|
90
|
+
table.insert(prefix, space)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
if #div.content > 0 and div.content[1].t == "Para" then
|
|
94
|
+
for i = #prefix, 1, -1 do
|
|
95
|
+
table.insert(div.content[1].content, 1, prefix[i])
|
|
96
|
+
end
|
|
97
|
+
elseif #div.content > 0 then
|
|
98
|
+
table.insert(div.content, 1, pandoc.Para(prefix))
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
-- Second pass: process Div elements
|
|
103
|
+
local function processDiv(div)
|
|
104
|
+
-- Labels: every matching callout gets its prefix, unless the writer
|
|
105
|
+
-- already typed it (or its emoji).
|
|
106
|
+
for _, c in ipairs(callouts) do
|
|
107
|
+
if c.label and div.classes:includes(c.class) then
|
|
108
|
+
local already_has_prefix = false
|
|
109
|
+
if #div.content > 0 and div.content[1].t == "Para" then
|
|
110
|
+
local first = div.content[1].content
|
|
111
|
+
already_has_prefix = (c.emoji and startsWithText(first, c.emoji)) or
|
|
112
|
+
startsWithText(first, c.label)
|
|
113
|
+
end
|
|
114
|
+
if not already_has_prefix then
|
|
115
|
+
prependCalloutLabel(div, symbolFor(c), c.label)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
-- ARIA: the first matching callout in registry order wins.
|
|
121
|
+
-- Use doc-notice (DPUB-ARIA) for all callout types; doc-tip is not a valid EPUB3 role
|
|
122
|
+
for _, c in ipairs(callouts) do
|
|
123
|
+
if div.classes:includes(c.class) then
|
|
124
|
+
div.attributes["role"] = "doc-notice"
|
|
125
|
+
if c.aria then div.attributes["aria-label"] = c.aria end
|
|
126
|
+
break
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
-- Add DPUB-ARIA roles for special structural sections
|
|
131
|
+
if div.classes:includes("dedication") then
|
|
132
|
+
div.attributes["role"] = "doc-dedication"
|
|
133
|
+
end
|
|
134
|
+
if div.identifier == "copyright-page" then
|
|
135
|
+
div.attributes["role"] = "doc-colophon"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
return div
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
-- Strip role="doc-*" and epub:type from headings. Pandoc's EPUB writer
|
|
142
|
+
-- sometimes copies epub:type onto headings and synthesizes a matching
|
|
143
|
+
-- role attribute (e.g., <h1 epub:type="prologue" role="doc-prologue">),
|
|
144
|
+
-- but EPUBCheck's HTML role validation only allows "doc-subtitle",
|
|
145
|
+
-- "heading", "none", "presentation", or "tab" on heading elements, and
|
|
146
|
+
-- ACE requires any epub:type to have a matching role. Removing both from
|
|
147
|
+
-- the heading resolves the conflict: pandoc already emits identical
|
|
148
|
+
-- epub:type and role on the containing <section>, which is the correct
|
|
149
|
+
-- location per DPUB-ARIA and which both validators accept.
|
|
150
|
+
local function processHeader(h)
|
|
151
|
+
local role = h.attributes["role"]
|
|
152
|
+
if role and role:match("^doc%-") and role ~= "doc-subtitle" then
|
|
153
|
+
h.attributes["role"] = nil
|
|
154
|
+
h.attributes["epub:type"] = nil
|
|
155
|
+
end
|
|
156
|
+
return h
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
-- Insert zero-width spaces (U+200B) inside URL-like substrings so long URLs
|
|
160
|
+
-- in PDF footnotes can wrap mid-URL instead of being pushed onto their own
|
|
161
|
+
-- line. Triggered by links-to-footnotes.js rewriting `[text](url)` to
|
|
162
|
+
-- `text (url)` — the URL becomes one unbreakable Str token, and even with
|
|
163
|
+
-- `overflow-wrap: anywhere` WeasyPrint won't break it when there's a usable
|
|
164
|
+
-- space before the opening paren. ZWSPs after path separators give the line
|
|
165
|
+
-- breaker explicit opportunities to use only when they improve the line.
|
|
166
|
+
local ZWSP = "\226\128\139"
|
|
167
|
+
local function insertUrlBreakpoints(text)
|
|
168
|
+
return (text:gsub("https?://[^%)%s]+", function(url)
|
|
169
|
+
return (url:gsub("([/%?&=])", "%1" .. ZWSP))
|
|
170
|
+
end))
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
-- For PDF: convert pandoc Note elements (endnotes) to inline Span elements
|
|
174
|
+
-- with class "fn-inline" so CSS float:footnote renders them at page bottom.
|
|
175
|
+
-- This is a no-op for EPUB/Kindle/screen (only active when pdf=true).
|
|
176
|
+
local function convertNote(note)
|
|
177
|
+
if output_mode ~= "pdf" then return nil end
|
|
178
|
+
local inlines = pandoc.List:new()
|
|
179
|
+
for _, block in ipairs(note.content) do
|
|
180
|
+
if block.t == "Para" or block.t == "Plain" then
|
|
181
|
+
if #inlines > 0 then
|
|
182
|
+
inlines:insert(pandoc.Space())
|
|
183
|
+
end
|
|
184
|
+
inlines:extend(block.content)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
inlines = inlines:map(function(inline)
|
|
188
|
+
if inline.t == "Str" then
|
|
189
|
+
return pandoc.Str(insertUrlBreakpoints(inline.text))
|
|
190
|
+
end
|
|
191
|
+
return inline
|
|
192
|
+
end)
|
|
193
|
+
return pandoc.Span(inlines, pandoc.Attr("", {"fn-inline"}, {}))
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
return {{Meta = readMeta}, {Div = processDiv, Header = processHeader, Note = convertNote}}
|