refactor: Convert book viewer to idiomatic React
Replace the SSR + injected vanilla-JS navigation script with a proper client-side React app. BookIndex now uses useState/useEffect/useCallback for all navigation state, scroll tracking, keyboard shortcuts, and history management. webpack.config.js switches from renderToStaticMarkup to embedding page data as window.__BOOK_DATA__ JSON; book.js becomes a createRoot entry point. Also adds babel-loader for JSX bundling, fixes #root display:contents so the flex height chain is preserved, and restores missing CSS for header, .logo, .toolbar, and .tab buttons. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,9 @@
|
||||
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
|
||||
@@ -24,11 +19,24 @@ function readPages(dir) {
|
||||
});
|
||||
}
|
||||
|
||||
function bookTemplateContent(title, logoText, dir) {
|
||||
function bookTemplate(title, logoText, dir) {
|
||||
const pages = readPages(dir);
|
||||
return () =>
|
||||
"<!DOCTYPE html>" +
|
||||
renderToStaticMarkup(React.createElement(BookIndex, { title, logoText, pages }));
|
||||
const data = JSON.stringify({ title, logoText, pages });
|
||||
return `<!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" />
|
||||
<link rel="stylesheet" href="/css/book-page.css" />
|
||||
<link rel="stylesheet" href="/css/book-layout.css" />
|
||||
</head>
|
||||
<body>
|
||||
<script>window.__BOOK_DATA__ = ${data};</script>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
@@ -37,7 +45,6 @@ module.exports = (env, argv) => {
|
||||
return {
|
||||
entry: {
|
||||
sheet: "./fabula-ultima-sheet.js",
|
||||
// Imports the shared CSS so HtmlWebpackPlugin can inject it into the book page
|
||||
book: "./book.js",
|
||||
},
|
||||
output: {
|
||||
@@ -48,6 +55,14 @@ module.exports = (env, argv) => {
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: "babel-loader",
|
||||
options: { presets: ["@babel/preset-react"] },
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
@@ -57,6 +72,9 @@ module.exports = (env, argv) => {
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".js", ".jsx"],
|
||||
},
|
||||
plugins: [
|
||||
...(isProd
|
||||
? [new MiniCssExtractPlugin({ filename: "[name].[contenthash].css" })]
|
||||
@@ -68,7 +86,7 @@ module.exports = (env, argv) => {
|
||||
scriptLoading: "blocking",
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
templateContent: bookTemplateContent(
|
||||
templateContent: bookTemplate(
|
||||
"Fabula Ultima - Core Rulebook",
|
||||
"Core Rules",
|
||||
"./books/core"
|
||||
@@ -78,7 +96,7 @@ module.exports = (env, argv) => {
|
||||
scriptLoading: "blocking",
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
templateContent: bookTemplateContent(
|
||||
templateContent: bookTemplate(
|
||||
"Fabula Ultima - Natural Fantasy Atlas",
|
||||
"Natural Fantasy Atlas",
|
||||
"./books/natural-fantasy-atlas"
|
||||
@@ -87,7 +105,6 @@ module.exports = (env, argv) => {
|
||||
chunks: ["book"],
|
||||
scriptLoading: "blocking",
|
||||
}),
|
||||
// Copy book pages to dist/books/ (excluding index.html, managed by HtmlWebpackPlugin)
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
@@ -97,7 +114,6 @@ module.exports = (env, argv) => {
|
||||
},
|
||||
],
|
||||
}),
|
||||
// Copy static CSS
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
@@ -112,7 +128,6 @@ module.exports = (env, argv) => {
|
||||
minimizer: ["...", new CssMinimizerPlugin()],
|
||||
splitChunks: {
|
||||
cacheGroups: {
|
||||
// Merge CSS from all entries into a single shared file
|
||||
styles: {
|
||||
name: "styles",
|
||||
type: "css/mini-extract",
|
||||
|
||||
Reference in New Issue
Block a user