All checks were successful
Deploy / deploy (push) Successful in 1m20s
- Add yaml-loader; import data/spells.yml at build time - Add SpellTemplate/SpellsFile types to globals.d.ts - Add 'Add Template Spell' button that opens a modal picker pre-filling all spell fields from the YAML data - Move spell data files into data/ directory - Split spell rows into inputs row + full-width notes row (colspan=6) - Shrink delete column to fit-content; bold spell name input - Add class column to spell table; change MP cost to free-text input - Auto-resize spell notes textarea on load and on input - Add 10px padding between spells for visual separation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
162 lines
4.4 KiB
JavaScript
162 lines
4.4 KiB
JavaScript
const path = require("path");
|
|
const fs = require("fs");
|
|
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");
|
|
|
|
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 bookTemplate(title, logoText, dir) {
|
|
const pages = readPages(dir);
|
|
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) => {
|
|
const isProd = argv.mode === "production";
|
|
|
|
return {
|
|
entry: {
|
|
sheet: "./src/sheet-main.tsx",
|
|
book: "./src/book.tsx",
|
|
},
|
|
output: {
|
|
filename: isProd ? "[name].[contenthash].js" : "[name].js",
|
|
path: path.resolve(__dirname, "dist"),
|
|
clean: true,
|
|
iife: false,
|
|
},
|
|
module: {
|
|
rules: [
|
|
{
|
|
test: /\.(js|jsx|ts|tsx)$/,
|
|
exclude: /node_modules/,
|
|
use: {
|
|
loader: "babel-loader",
|
|
options: {
|
|
presets: ["@babel/preset-react", "@babel/preset-typescript"],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
test: /\.ya?ml$/,
|
|
use: "yaml-loader",
|
|
},
|
|
{
|
|
test: /\.css$/,
|
|
use: [
|
|
isProd ? MiniCssExtractPlugin.loader : "style-loader",
|
|
"css-loader",
|
|
],
|
|
},
|
|
],
|
|
},
|
|
resolve: {
|
|
extensions: [".ts", ".tsx", ".js", ".jsx"],
|
|
},
|
|
plugins: [
|
|
...(isProd
|
|
? [new MiniCssExtractPlugin({ filename: "[name].[contenthash].css" })]
|
|
: []),
|
|
new HtmlWebpackPlugin({
|
|
template: "./src/fabula-ultima-sheet.html",
|
|
filename: "index.html",
|
|
chunks: ["sheet"],
|
|
scriptLoading: "blocking",
|
|
}),
|
|
new HtmlWebpackPlugin({
|
|
templateContent: bookTemplate(
|
|
"Fabula Ultima - Core Rulebook",
|
|
"Core Rules",
|
|
"./books/core"
|
|
),
|
|
filename: "books/core/index.html",
|
|
chunks: ["book"],
|
|
scriptLoading: "blocking",
|
|
}),
|
|
new HtmlWebpackPlugin({
|
|
templateContent: bookTemplate(
|
|
"Fabula Ultima - Natural Fantasy Atlas",
|
|
"Natural Fantasy Atlas",
|
|
"./books/natural-fantasy-atlas"
|
|
),
|
|
filename: "books/natural-fantasy-atlas/index.html",
|
|
chunks: ["book"],
|
|
scriptLoading: "blocking",
|
|
}),
|
|
new CopyWebpackPlugin({
|
|
patterns: [
|
|
{
|
|
from: "books",
|
|
to: "books",
|
|
globOptions: { ignore: ["**/index.html"] },
|
|
},
|
|
],
|
|
}),
|
|
new CopyWebpackPlugin({
|
|
patterns: [
|
|
{
|
|
from: "css",
|
|
to: "css",
|
|
globOptions: { ignore: ["**/index.html"] },
|
|
},
|
|
],
|
|
}),
|
|
],
|
|
optimization: {
|
|
minimizer: ["...", new CssMinimizerPlugin()],
|
|
splitChunks: {
|
|
cacheGroups: {
|
|
styles: {
|
|
name: "styles",
|
|
type: "css/mini-extract",
|
|
chunks: "all",
|
|
enforce: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
devServer: {
|
|
static: [
|
|
{ directory: path.resolve(__dirname, "dist") },
|
|
{ directory: path.resolve(__dirname, "books"), publicPath: "/books" },
|
|
{ directory: path.resolve(__dirname, "css"), publicPath: "/css" },
|
|
],
|
|
port: 8080,
|
|
open: true,
|
|
historyApiFallback: {
|
|
rewrites: [
|
|
{ from: /^\/book$/, to: "/book/index.html" },
|
|
],
|
|
},
|
|
},
|
|
};
|
|
};
|