feat: Replace build.py with JSX-based webpack-native book index generation
Port the Python HTML-generation script to a React component (src/BookIndex.jsx) rendered at build time via renderToStaticMarkup, removing the need to run build.py separately before webpack. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
325
build.py
325
build.py
@@ -1,325 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Combine all numbered page files into a single index.html."""
|
||||
import os
|
||||
import re
|
||||
|
||||
def build_index(title, DIR):
|
||||
# Collect numbered pages, sorted numerically
|
||||
page_nums = sorted(
|
||||
int(m.group(1))
|
||||
for f in os.listdir(DIR)
|
||||
if (m := re.match(r'^(\d+)\.html$', f))
|
||||
)
|
||||
|
||||
def read_page(n):
|
||||
with open(os.path.join(DIR, f'{n}.html'), encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
content = re.sub(r'[ \t]*<link[^>]+>\n?', '', content)
|
||||
return content.strip()
|
||||
|
||||
sections = [(n, read_page(n)) for n in page_nums]
|
||||
|
||||
sidebar_items = '\n '.join(
|
||||
f'<li data-page="{n}"><button onclick="goTo({n},false,true)">'
|
||||
f'<span class="page-num">{n}</span><span>Page {n}</span></button></li>'
|
||||
for n in page_nums
|
||||
)
|
||||
|
||||
sections_html = '\n\n '.join(
|
||||
f'<section id="page-{n}" class="page-section">\n{content}\n </section>'
|
||||
for n, content in sections
|
||||
)
|
||||
|
||||
pages_js = '[' + ','.join(str(n) for n in page_nums) + ']'
|
||||
|
||||
html = f'''\
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{title}</title>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;600;700&family=Crimson+Text:ital,wght@0,400;0,600;1,400&family=Inconsolata:wght@400;600&display=swap"
|
||||
rel="stylesheet"
|
||||
>
|
||||
'''
|
||||
html += f'''
|
||||
<link rel="stylesheet" href="/css/book-page.css">
|
||||
<style>
|
||||
/* Reset body to a layout container (book-page.css targets body for page content) */
|
||||
html, body {{
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
max-width: unset;
|
||||
margin: 0;
|
||||
background-image: none;
|
||||
}}
|
||||
|
||||
#layout {{
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}}
|
||||
|
||||
/* ── Sidebar ── */
|
||||
#sidebar {{
|
||||
width: 270px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--surface);
|
||||
border-right: 1px solid var(--border-bright);
|
||||
overflow: hidden;
|
||||
}}
|
||||
|
||||
#sidebar-header {{
|
||||
padding: 14px 16px;
|
||||
background: var(--surface2);
|
||||
border-bottom: 1px solid var(--border-bright);
|
||||
flex-shrink: 0;
|
||||
}}
|
||||
|
||||
#sidebar-header p {{
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.7rem;
|
||||
color: var(--text-dim);
|
||||
margin-top: 4px;
|
||||
}}
|
||||
|
||||
#page-list {{
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
list-style: none;
|
||||
padding: 4px 0;
|
||||
}}
|
||||
|
||||
#page-list li button {{
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding: 6px 14px;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--text-dim);
|
||||
font-family: var(--font-body);
|
||||
font-size: 0.88rem;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: baseline;
|
||||
transition: background 0.15s, color 0.15s;
|
||||
}}
|
||||
|
||||
#page-list li button:hover {{
|
||||
background: var(--surface2);
|
||||
color: var(--text);
|
||||
}}
|
||||
|
||||
#page-list li.active button {{
|
||||
background: var(--surface3);
|
||||
color: var(--teal);
|
||||
}}
|
||||
|
||||
.page-num {{
|
||||
flex-shrink: 0;
|
||||
min-width: 24px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.72rem;
|
||||
color: var(--border-bright);
|
||||
}}
|
||||
|
||||
li.active .page-num {{
|
||||
color: var(--teal-dim);
|
||||
}}
|
||||
|
||||
/* ── Main area ── */
|
||||
#main {{
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}}
|
||||
|
||||
/* ── Nav bar ── */
|
||||
#nav {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 20px;
|
||||
background: var(--surface);
|
||||
border-bottom: 1px solid var(--border-bright);
|
||||
flex-shrink: 0;
|
||||
}}
|
||||
|
||||
#page-title {{
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-family: var(--font-display);
|
||||
font-size: 0.65rem;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}}
|
||||
|
||||
#page-indicator {{
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.72rem;
|
||||
color: var(--text-dim);
|
||||
white-space: nowrap;
|
||||
}}
|
||||
|
||||
/* ── Scrollable content ── */
|
||||
#content {{
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
background: var(--bg);
|
||||
background-image:
|
||||
radial-gradient(ellipse at 20% 10%, rgba(78,205,196,.04) 0%, transparent 50%),
|
||||
radial-gradient(ellipse at 80% 90%, rgba(201,168,76,.04) 0%, transparent 50%);
|
||||
}}
|
||||
|
||||
/* ── Page sections ── */
|
||||
.page-section {{
|
||||
max-width: 860px;
|
||||
margin: 0 auto;
|
||||
padding: 32px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}}
|
||||
|
||||
.page-section:last-child {{
|
||||
border-bottom: none;
|
||||
min-height: calc(100vh - 80px);
|
||||
}}
|
||||
|
||||
/* Override book-page.css header rule inside sections */
|
||||
.page-section header {{
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding-bottom: 0.8em;
|
||||
margin-bottom: 1.4em;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<div class="logo">Core Rules</div>
|
||||
<div class="toolbar">
|
||||
<span id="page-indicator"></span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div id="layout">
|
||||
<nav id="sidebar">
|
||||
<div id="sidebar-header">
|
||||
<div class="section-title" style="margin-bottom:0; border-bottom:none; padding-bottom:0;">
|
||||
<span class="icon">✦</span> Pages
|
||||
</div>
|
||||
<p>{len(page_nums)} pages</p>
|
||||
</div>
|
||||
<ul id="page-list">
|
||||
{sidebar_items}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div id="main">
|
||||
<div id="nav">
|
||||
<button id="btn-prev" class="tab" disabled>← Prev</button>
|
||||
<span id="page-title"></span>
|
||||
<button id="btn-next" class="tab">Next →</button>
|
||||
</div>
|
||||
<div id="content">
|
||||
{sections_html}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const PAGES = {pages_js};
|
||||
const total = PAGES.length;
|
||||
let currentIdx = 0;
|
||||
|
||||
const content = document.getElementById('content');
|
||||
const btnPrev = document.getElementById('btn-prev');
|
||||
const btnNext = document.getElementById('btn-next');
|
||||
const indicator = document.getElementById('page-indicator');
|
||||
const titleEl = document.getElementById('page-title');
|
||||
|
||||
function updateNav(idx, push) {{
|
||||
if (idx === currentIdx && indicator.textContent) return;
|
||||
currentIdx = idx;
|
||||
const n = PAGES[idx];
|
||||
indicator.textContent = (idx + 1) + ' / ' + total;
|
||||
titleEl.textContent = 'Page ' + n;
|
||||
btnPrev.disabled = idx === 0;
|
||||
btnNext.disabled = idx === total - 1;
|
||||
document.querySelectorAll('#page-list li').forEach(li => {{
|
||||
li.classList.toggle('active', Number(li.dataset.page) === n);
|
||||
}});
|
||||
document.querySelector('#page-list li[data-page="' + n + '"]')
|
||||
?.scrollIntoView({{ block: 'nearest' }});
|
||||
if (push) history.pushState(null, '', '#page-' + n);
|
||||
else history.replaceState(null, '', '#page-' + n);
|
||||
}}
|
||||
|
||||
function goTo(n, smooth, push) {{
|
||||
const idx = PAGES.indexOf(n);
|
||||
if (idx === -1) return;
|
||||
const sec = document.getElementById('page-' + n);
|
||||
if (!sec) return;
|
||||
sec.scrollIntoView({{ behavior: smooth ? 'smooth' : 'instant', block: 'start' }});
|
||||
updateNav(idx, push);
|
||||
}}
|
||||
|
||||
btnPrev.addEventListener('click', () => {{ if (currentIdx > 0) goTo(PAGES[currentIdx - 1], true, true); }});
|
||||
btnNext.addEventListener('click', () => {{ if (currentIdx < total - 1) goTo(PAGES[currentIdx + 1], true, true); }});
|
||||
|
||||
document.addEventListener('keydown', e => {{
|
||||
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
||||
if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {{ if (currentIdx > 0) goTo(PAGES[currentIdx - 1], true, true); }}
|
||||
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {{ if (currentIdx < total - 1) goTo(PAGES[currentIdx + 1], true, true); }}
|
||||
}});
|
||||
|
||||
// Track current page by scroll position (replaceState — no new history entry)
|
||||
content.addEventListener('scroll', () => {{
|
||||
const containerTop = content.getBoundingClientRect().top;
|
||||
let found = 0;
|
||||
for (let i = 0; i < PAGES.length; i++) {{
|
||||
const sec = document.getElementById('page-' + PAGES[i]);
|
||||
if (sec && sec.getBoundingClientRect().top - containerTop <= 40) found = i;
|
||||
else break;
|
||||
}}
|
||||
if (found !== currentIdx) updateNav(found, false);
|
||||
}}, {{ passive: true }});
|
||||
|
||||
// Browser back/forward
|
||||
window.addEventListener('popstate', () => {{
|
||||
const pm = location.hash.match(/^#page-(\\d+)$/);
|
||||
if (pm) {{
|
||||
const n = parseInt(pm[1], 10);
|
||||
if (PAGES.includes(n)) goTo(n, false, false);
|
||||
}}
|
||||
}});
|
||||
|
||||
// Initial navigation from URL hash
|
||||
const m = location.hash.match(/^#page-(\\d+)$/);
|
||||
const startPage = m ? parseInt(m[1], 10) : PAGES[0];
|
||||
goTo(PAGES.includes(startPage) ? startPage : PAGES[0], false, false);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
out = os.path.join(DIR, 'index.html')
|
||||
with open(out, 'w', encoding='utf-8') as f:
|
||||
f.write(html)
|
||||
|
||||
print(f'Generated {out} with {len(page_nums)} pages ({os.path.getsize(out) // 1024} KB)')
|
||||
|
||||
build_index("Fabula Ultima - Core Rulebook", "./books/core")
|
||||
build_index("Fabula Ultima - Natural Fantasy Atlas", "./books/natural-fantasy-atlas")
|
||||
701
package-lock.json
generated
701
package-lock.json
generated
@@ -5,18 +5,482 @@
|
||||
"packages": {
|
||||
"": {
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.29.7",
|
||||
"@babel/preset-react": "^7.29.7",
|
||||
"@babel/register": "^7.29.7",
|
||||
"copy-webpack-plugin": "^14.0.0",
|
||||
"css-loader": "^7.1.4",
|
||||
"css-minimizer-webpack-plugin": "^8.0.0",
|
||||
"html-webpack-plugin": "^5.6.7",
|
||||
"mini-css-extract-plugin": "^2.10.2",
|
||||
"prettier": "^3.8.3",
|
||||
"react": "^19.2.7",
|
||||
"react-dom": "^19.2.7",
|
||||
"style-loader": "^4.0.0",
|
||||
"webpack": "^5.107.2",
|
||||
"webpack-cli": "^7.0.3",
|
||||
"webpack-dev-server": "^5.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
|
||||
"integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.29.7",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/compat-data": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
|
||||
"integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/core": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
|
||||
"integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.7",
|
||||
"@babel/generator": "^7.29.7",
|
||||
"@babel/helper-compilation-targets": "^7.29.7",
|
||||
"@babel/helper-module-transforms": "^7.29.7",
|
||||
"@babel/helpers": "^7.29.7",
|
||||
"@babel/parser": "^7.29.7",
|
||||
"@babel/template": "^7.29.7",
|
||||
"@babel/traverse": "^7.29.7",
|
||||
"@babel/types": "^7.29.7",
|
||||
"@jridgewell/remapping": "^2.3.5",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"debug": "^4.1.0",
|
||||
"gensync": "^1.0.0-beta.2",
|
||||
"json5": "^2.2.3",
|
||||
"semver": "^6.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/babel"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/core/node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/core/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@babel/core/node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz",
|
||||
"integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.29.7",
|
||||
"@babel/types": "^7.29.7",
|
||||
"@jridgewell/gen-mapping": "^0.3.12",
|
||||
"@jridgewell/trace-mapping": "^0.3.28",
|
||||
"jsesc": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-annotate-as-pure": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz",
|
||||
"integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.29.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-compilation-targets": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
|
||||
"integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/compat-data": "^7.29.7",
|
||||
"@babel/helper-validator-option": "^7.29.7",
|
||||
"browserslist": "^4.24.0",
|
||||
"lru-cache": "^5.1.1",
|
||||
"semver": "^6.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-compilation-targets/node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-globals": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
|
||||
"integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-module-imports": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
|
||||
"integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/traverse": "^7.29.7",
|
||||
"@babel/types": "^7.29.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-module-transforms": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
|
||||
"integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-module-imports": "^7.29.7",
|
||||
"@babel/helper-validator-identifier": "^7.29.7",
|
||||
"@babel/traverse": "^7.29.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-plugin-utils": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
|
||||
"integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
|
||||
"integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
|
||||
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-option": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
|
||||
"integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helpers": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
|
||||
"integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.29.7",
|
||||
"@babel/types": "^7.29.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
|
||||
"integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.29.7"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-syntax-jsx": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz",
|
||||
"integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.29.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-react-display-name": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.29.7.tgz",
|
||||
"integrity": "sha512-+1wdDMGNb4UPeY3Q4L5yLiYe6TXPXubs4NjrgRFw13hPRLJfEMw2Q5OXkee6/IfdqePIeW4Jjwe3aBh7SdKz4Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.29.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-react-jsx": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.29.7.tgz",
|
||||
"integrity": "sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-annotate-as-pure": "^7.29.7",
|
||||
"@babel/helper-module-imports": "^7.29.7",
|
||||
"@babel/helper-plugin-utils": "^7.29.7",
|
||||
"@babel/plugin-syntax-jsx": "^7.29.7",
|
||||
"@babel/types": "^7.29.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-react-jsx-development": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.29.7.tgz",
|
||||
"integrity": "sha512-Xfy3UVMF04+ypnFbkhvfqtmvwfe92qwQdbGZVonhE+6v35GzlofmOnA1szaZqzb9xYWr0nl1e5EMmzi0DNON1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/plugin-transform-react-jsx": "^7.29.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-react-pure-annotations": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.29.7.tgz",
|
||||
"integrity": "sha512-H5E+HBgDpr6Q5t+Aj11tL7XkIui1jhbIoArVQnqjgXo5/3YxkN7ZEBcWF4RQlB0T4rrxJQbXS6kiFV6B7XTqUA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-annotate-as-pure": "^7.29.7",
|
||||
"@babel/helper-plugin-utils": "^7.29.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/preset-react": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.29.7.tgz",
|
||||
"integrity": "sha512-C+PV1TFUPTmBQGoPBL8j2QmLpZ117YTCwxIZeJOM96GbYMFSc7/pOXU5lVykwnZxyTqQxRsvoRk6f2FktZgGHA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.29.7",
|
||||
"@babel/helper-validator-option": "^7.29.7",
|
||||
"@babel/plugin-transform-react-display-name": "^7.29.7",
|
||||
"@babel/plugin-transform-react-jsx": "^7.29.7",
|
||||
"@babel/plugin-transform-react-jsx-development": "^7.29.7",
|
||||
"@babel/plugin-transform-react-pure-annotations": "^7.29.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/register": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/register/-/register-7.29.7.tgz",
|
||||
"integrity": "sha512-AMGJoWuES861riy6pcB0fphE1YXybtQnBYQMuIyPv6mKLiosfa79BKTnAOyx215c/3RJPJpdQwoHZ3earVH7AA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"clone-deep": "^4.0.1",
|
||||
"find-cache-dir": "^2.0.0",
|
||||
"make-dir": "^2.1.0",
|
||||
"pirates": "^4.0.6",
|
||||
"source-map-support": "^0.5.16"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
|
||||
"integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.7",
|
||||
"@babel/parser": "^7.29.7",
|
||||
"@babel/types": "^7.29.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz",
|
||||
"integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.7",
|
||||
"@babel/generator": "^7.29.7",
|
||||
"@babel/helper-globals": "^7.29.7",
|
||||
"@babel/parser": "^7.29.7",
|
||||
"@babel/template": "^7.29.7",
|
||||
"@babel/types": "^7.29.7",
|
||||
"debug": "^4.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse/node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
|
||||
"integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.29.7",
|
||||
"@babel/helper-validator-identifier": "^7.29.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@colordx/core": {
|
||||
"version": "5.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@colordx/core/-/core-5.4.3.tgz",
|
||||
@@ -91,6 +555,17 @@
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/remapping": {
|
||||
"version": "2.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
||||
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
@@ -1763,6 +2238,13 @@
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/commondir": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/compressible": {
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
|
||||
@@ -1828,6 +2310,13 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/convert-source-map": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||
@@ -2685,6 +3174,84 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/find-cache-dir": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
|
||||
"integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"commondir": "^1.0.1",
|
||||
"make-dir": "^2.0.0",
|
||||
"pkg-dir": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/find-cache-dir/node_modules/find-up": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
|
||||
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"locate-path": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/find-cache-dir/node_modules/locate-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
|
||||
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-locate": "^3.0.0",
|
||||
"path-exists": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/find-cache-dir/node_modules/p-locate": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
|
||||
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-limit": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/find-cache-dir/node_modules/path-exists": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
|
||||
"integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/find-cache-dir/node_modules/pkg-dir": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
|
||||
"integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"find-up": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/find-up": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||
@@ -2775,6 +3342,16 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/gensync": {
|
||||
"version": "1.0.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
||||
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
@@ -3424,6 +4001,26 @@
|
||||
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jsesc": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"jsesc": "bin/jsesc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
@@ -3431,6 +4028,19 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"json5": "lib/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
@@ -3523,6 +4133,40 @@
|
||||
"tslib": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
||||
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pify": "^4.0.1",
|
||||
"semver": "^5.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir/node_modules/semver": {
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
@@ -4014,6 +4658,26 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pify": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
|
||||
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/pirates": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
|
||||
"integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-dir": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
|
||||
@@ -4718,6 +5382,29 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.2.7",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
|
||||
"integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.2.7",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz",
|
||||
"integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.2.7"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
@@ -4926,6 +5613,13 @@
|
||||
"node": ">=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.27.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
||||
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/schema-utils": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
|
||||
@@ -6230,6 +6924,13 @@
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,17 @@
|
||||
"dev": "webpack serve --mode=development"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.29.7",
|
||||
"@babel/preset-react": "^7.29.7",
|
||||
"@babel/register": "^7.29.7",
|
||||
"copy-webpack-plugin": "^14.0.0",
|
||||
"css-loader": "^7.1.4",
|
||||
"css-minimizer-webpack-plugin": "^8.0.0",
|
||||
"html-webpack-plugin": "^5.6.7",
|
||||
"mini-css-extract-plugin": "^2.10.2",
|
||||
"prettier": "^3.8.3",
|
||||
"react": "^19.2.7",
|
||||
"react-dom": "^19.2.7",
|
||||
"style-loader": "^4.0.0",
|
||||
"webpack": "^5.107.2",
|
||||
"webpack-cli": "^7.0.3",
|
||||
|
||||
312
src/BookIndex.jsx
Normal file
312
src/BookIndex.jsx
Normal file
@@ -0,0 +1,312 @@
|
||||
import React from 'react';
|
||||
|
||||
const INLINE_CSS = `
|
||||
/* Reset body to a layout container (book-page.css targets body for page content) */
|
||||
html, body {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
max-width: unset;
|
||||
margin: 0;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
#layout {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ── Sidebar ── */
|
||||
#sidebar {
|
||||
width: 270px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--surface);
|
||||
border-right: 1px solid var(--border-bright);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#sidebar-header {
|
||||
padding: 14px 16px;
|
||||
background: var(--surface2);
|
||||
border-bottom: 1px solid var(--border-bright);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
#sidebar-header p {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.7rem;
|
||||
color: var(--text-dim);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
#page-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
list-style: none;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
#page-list li button {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding: 6px 14px;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--text-dim);
|
||||
font-family: var(--font-body);
|
||||
font-size: 0.88rem;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: baseline;
|
||||
transition: background 0.15s, color 0.15s;
|
||||
}
|
||||
|
||||
#page-list li button:hover {
|
||||
background: var(--surface2);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
#page-list li.active button {
|
||||
background: var(--surface3);
|
||||
color: var(--teal);
|
||||
}
|
||||
|
||||
.page-num {
|
||||
flex-shrink: 0;
|
||||
min-width: 24px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.72rem;
|
||||
color: var(--border-bright);
|
||||
}
|
||||
|
||||
li.active .page-num {
|
||||
color: var(--teal-dim);
|
||||
}
|
||||
|
||||
/* ── Main area ── */
|
||||
#main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ── Nav bar ── */
|
||||
#nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 20px;
|
||||
background: var(--surface);
|
||||
border-bottom: 1px solid var(--border-bright);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
#page-title {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-family: var(--font-display);
|
||||
font-size: 0.65rem;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#page-indicator {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.72rem;
|
||||
color: var(--text-dim);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ── Scrollable content ── */
|
||||
#content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
background: var(--bg);
|
||||
background-image:
|
||||
radial-gradient(ellipse at 20% 10%, rgba(78,205,196,.04) 0%, transparent 50%),
|
||||
radial-gradient(ellipse at 80% 90%, rgba(201,168,76,.04) 0%, transparent 50%);
|
||||
}
|
||||
|
||||
/* ── Page sections ── */
|
||||
.page-section {
|
||||
max-width: 860px;
|
||||
margin: 0 auto;
|
||||
padding: 32px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.page-section:last-child {
|
||||
border-bottom: none;
|
||||
min-height: calc(100vh - 80px);
|
||||
}
|
||||
|
||||
/* Override book-page.css header rule inside sections */
|
||||
.page-section header {
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding-bottom: 0.8em;
|
||||
margin-bottom: 1.4em;
|
||||
}
|
||||
`;
|
||||
|
||||
function buildScript(pageNums) {
|
||||
return `
|
||||
const PAGES = [${pageNums.join(',')}];
|
||||
const total = PAGES.length;
|
||||
let currentIdx = 0;
|
||||
|
||||
const content = document.getElementById('content');
|
||||
const btnPrev = document.getElementById('btn-prev');
|
||||
const btnNext = document.getElementById('btn-next');
|
||||
const indicator = document.getElementById('page-indicator');
|
||||
const titleEl = document.getElementById('page-title');
|
||||
|
||||
function updateNav(idx, push) {
|
||||
if (idx === currentIdx && indicator.textContent) return;
|
||||
currentIdx = idx;
|
||||
const n = PAGES[idx];
|
||||
indicator.textContent = (idx + 1) + ' / ' + total;
|
||||
titleEl.textContent = 'Page ' + n;
|
||||
btnPrev.disabled = idx === 0;
|
||||
btnNext.disabled = idx === total - 1;
|
||||
document.querySelectorAll('#page-list li').forEach(li => {
|
||||
li.classList.toggle('active', Number(li.dataset.page) === n);
|
||||
});
|
||||
document.querySelector('#page-list li[data-page="' + n + '"]')
|
||||
?.scrollIntoView({ block: 'nearest' });
|
||||
if (push) history.pushState(null, '', '#page-' + n);
|
||||
else history.replaceState(null, '', '#page-' + n);
|
||||
}
|
||||
|
||||
function goTo(n, smooth, push) {
|
||||
const idx = PAGES.indexOf(n);
|
||||
if (idx === -1) return;
|
||||
const sec = document.getElementById('page-' + n);
|
||||
if (!sec) return;
|
||||
sec.scrollIntoView({ behavior: smooth ? 'smooth' : 'instant', block: 'start' });
|
||||
updateNav(idx, push);
|
||||
}
|
||||
|
||||
btnPrev.addEventListener('click', () => { if (currentIdx > 0) goTo(PAGES[currentIdx - 1], true, true); });
|
||||
btnNext.addEventListener('click', () => { if (currentIdx < total - 1) goTo(PAGES[currentIdx + 1], true, true); });
|
||||
|
||||
document.getElementById('page-list').addEventListener('click', e => {
|
||||
const li = e.target.closest('li[data-page]');
|
||||
if (li) goTo(Number(li.dataset.page), false, true);
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
||||
if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') { if (currentIdx > 0) goTo(PAGES[currentIdx - 1], true, true); }
|
||||
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') { if (currentIdx < total - 1) goTo(PAGES[currentIdx + 1], true, true); }
|
||||
});
|
||||
|
||||
// Track current page by scroll position (replaceState — no new history entry)
|
||||
content.addEventListener('scroll', () => {
|
||||
const containerTop = content.getBoundingClientRect().top;
|
||||
let found = 0;
|
||||
for (let i = 0; i < PAGES.length; i++) {
|
||||
const sec = document.getElementById('page-' + PAGES[i]);
|
||||
if (sec && sec.getBoundingClientRect().top - containerTop <= 40) found = i;
|
||||
else break;
|
||||
}
|
||||
if (found !== currentIdx) updateNav(found, false);
|
||||
}, { passive: true });
|
||||
|
||||
// Browser back/forward
|
||||
window.addEventListener('popstate', () => {
|
||||
const pm = location.hash.match(/^#page-(\\d+)$/);
|
||||
if (pm) {
|
||||
const n = parseInt(pm[1], 10);
|
||||
if (PAGES.includes(n)) goTo(n, false, false);
|
||||
}
|
||||
});
|
||||
|
||||
// Initial navigation from URL hash
|
||||
const m = location.hash.match(/^#page-(\\d+)$/);
|
||||
const startPage = m ? parseInt(m[1], 10) : PAGES[0];
|
||||
goTo(PAGES.includes(startPage) ? startPage : PAGES[0], false, false);
|
||||
`;
|
||||
}
|
||||
|
||||
export default function BookIndex({ title, logoText, pages }) {
|
||||
const pageNums = pages.map(p => p.n);
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charSet="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{title}</title>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;600;700&family=Crimson+Text:ital,wght@0,400;0,600;1,400&family=Inconsolata:wght@400;600&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="stylesheet" href="/css/book-page.css" />
|
||||
<style dangerouslySetInnerHTML={{ __html: INLINE_CSS }} />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div className="logo">{logoText}</div>
|
||||
<div className="toolbar">
|
||||
<span id="page-indicator"></span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div id="layout">
|
||||
<nav id="sidebar">
|
||||
<div id="sidebar-header">
|
||||
<div
|
||||
className="section-title"
|
||||
style={{ marginBottom: 0, borderBottom: 'none', paddingBottom: 0 }}
|
||||
>
|
||||
<span className="icon">✦</span> Pages
|
||||
</div>
|
||||
<p>{pages.length} pages</p>
|
||||
</div>
|
||||
<ul id="page-list">
|
||||
{pages.map(({ n }) => (
|
||||
<li key={n} data-page={n}>
|
||||
<button>
|
||||
<span className="page-num">{n}</span>
|
||||
<span>Page {n}</span>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div id="main">
|
||||
<div id="nav">
|
||||
<button id="btn-prev" className="tab" disabled>← Prev</button>
|
||||
<span id="page-title"></span>
|
||||
<button id="btn-next" className="tab">Next →</button>
|
||||
</div>
|
||||
<div id="content">
|
||||
{pages.map(({ n, content }) => (
|
||||
<section
|
||||
key={n}
|
||||
id={`page-${n}`}
|
||||
className="page-section"
|
||||
dangerouslySetInnerHTML={{ __html: content }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script dangerouslySetInnerHTML={{ __html: buildScript(pageNums) }} />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,35 @@
|
||||
require("@babel/register")({ presets: ["@babel/preset-react"] });
|
||||
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const React = require("react");
|
||||
const { renderToStaticMarkup } = require("react-dom/server");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
const BookIndex = require("./src/BookIndex.jsx").default;
|
||||
|
||||
function readPages(dir) {
|
||||
const pageNums = fs
|
||||
.readdirSync(dir)
|
||||
.map(f => { const m = f.match(/^(\d+)\.html$/); return m ? parseInt(m[1], 10) : null; })
|
||||
.filter(n => n !== null)
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
return pageNums.map(n => {
|
||||
let content = fs.readFileSync(path.join(dir, `${n}.html`), "utf8");
|
||||
content = content.replace(/[ \t]*<link[^>]+>\n?/g, "").trim();
|
||||
return { n, content };
|
||||
});
|
||||
}
|
||||
|
||||
function bookTemplateContent(title, logoText, dir) {
|
||||
const pages = readPages(dir);
|
||||
return () =>
|
||||
"<!DOCTYPE html>" +
|
||||
renderToStaticMarkup(React.createElement(BookIndex, { title, logoText, pages }));
|
||||
}
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
const isProd = argv.mode === "production";
|
||||
@@ -17,7 +44,6 @@ module.exports = (env, argv) => {
|
||||
filename: isProd ? "[name].[contenthash].js" : "[name].js",
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
clean: true,
|
||||
// Disable IIFE wrapping so onclick= handlers can reach global functions
|
||||
iife: false,
|
||||
},
|
||||
module: {
|
||||
@@ -42,13 +68,21 @@ module.exports = (env, argv) => {
|
||||
scriptLoading: "blocking",
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
template: "./books/core/index.html",
|
||||
templateContent: bookTemplateContent(
|
||||
"Fabula Ultima - Core Rulebook",
|
||||
"Core Rules",
|
||||
"./books/core"
|
||||
),
|
||||
filename: "books/core/index.html",
|
||||
chunks: ["book"],
|
||||
scriptLoading: "blocking",
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
template: "./books/natural-fantasy-atlas/index.html",
|
||||
templateContent: bookTemplateContent(
|
||||
"Fabula Ultima - Natural Fantasy Atlas",
|
||||
"Natural Fantasy Atlas",
|
||||
"./books/natural-fantasy-atlas"
|
||||
),
|
||||
filename: "books/natural-fantasy-atlas/index.html",
|
||||
chunks: ["book"],
|
||||
scriptLoading: "blocking",
|
||||
@@ -91,7 +125,6 @@ module.exports = (env, argv) => {
|
||||
devServer: {
|
||||
static: [
|
||||
{ directory: path.resolve(__dirname, "dist") },
|
||||
// Serve raw html/ pages at /book in dev so they don't need to be copied
|
||||
{ directory: path.resolve(__dirname, "books"), publicPath: "/books" },
|
||||
{ directory: path.resolve(__dirname, "css"), publicPath: "/css" },
|
||||
],
|
||||
@@ -99,7 +132,6 @@ module.exports = (env, argv) => {
|
||||
open: true,
|
||||
historyApiFallback: {
|
||||
rewrites: [
|
||||
// /book (no trailing slash) → /book/index.html
|
||||
{ from: /^\/book$/, to: "/book/index.html" },
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user