0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-03-22 08:58:11 +00:00
This commit is contained in:
Víctor Losada Hernández
2026-03-03 23:44:02 +01:00
parent 828bba61de
commit 8d18529c6d
24 changed files with 2679 additions and 2687 deletions

View File

@@ -1,6 +1,6 @@
import { createRoot } from "react-dom/client"; import { createRoot } from 'react-dom/client';
import Admin from "./admin.jsx"; import Admin from './admin.jsx';
const props = window.__INITIAL_PROPS__ || {}; const props = window.__INITIAL_PROPS__ || {};
createRoot(document.getElementById("reactRoot")).render(<Admin {...props} />); createRoot(document.getElementById('reactRoot')).render(<Admin {...props} />);

View File

@@ -89,7 +89,7 @@ const Combobox = createReactClass({
} }
}} }}
onKeyDown={(e)=>{ onKeyDown={(e)=>{
if (e.key === "Enter") { if(e.key === 'Enter') {
e.preventDefault(); e.preventDefault();
this.props.onEntry(e); this.props.onEntry(e);
} }

View File

@@ -88,7 +88,7 @@ const Editor = createReactClass({
const snippetBar = document.querySelector('.editor > .snippetBar'); const snippetBar = document.querySelector('.editor > .snippetBar');
if(!snippetBar) return; if(!snippetBar) return;
this.resizeObserver = new ResizeObserver(entries=>{ this.resizeObserver = new ResizeObserver((entries)=>{
const height = document.querySelector('.editor > .snippetBar').offsetHeight; const height = document.querySelector('.editor > .snippetBar').offsetHeight;
this.setState({ snippetBarHeight: height }); this.setState({ snippetBarHeight: height });
}); });

View File

@@ -338,9 +338,9 @@ const MetadataEditor = createReactClass({
{this.renderThumbnail()} {this.renderThumbnail()}
</div> </div>
<div className="field tags"> <div className='field tags'>
<label>Tags</label> <label>Tags</label>
<div className="value" > <div className='value' >
<TagInput <TagInput
label='tags' label='tags'
valuePatterns={/^\s*(?:(?:group|meta|system|type)\s*:\s*)?[A-Za-z0-9][A-Za-z0-9 \/\\.&_\-]{0,40}\s*$/} valuePatterns={/^\s*(?:(?:group|meta|system|type)\s*:\s*)?[A-Za-z0-9][A-Za-z0-9 \/\\.&_\-]{0,40}\s*$/}
@@ -363,9 +363,9 @@ const MetadataEditor = createReactClass({
{this.renderAuthors()} {this.renderAuthors()}
<div className="field invitedAuthors"> <div className='field invitedAuthors'>
<label>Invited authors</label> <label>Invited authors</label>
<div className="value"> <div className='value'>
<TagInput <TagInput
label='invited authors' label='invited authors'
valuePatterns={/.+/} valuePatterns={/.+/}

View File

@@ -1,210 +1,210 @@
export default [ export default [
// ############################## Systems // ############################## Systems
// D&D // D&D
"system:D&D Original", 'system:D&D Original',
"system:D&D Basic", 'system:D&D Basic',
"system:AD&D 1e", 'system:AD&D 1e',
"system:AD&D 2e", 'system:AD&D 2e',
"system:D&D 3e", 'system:D&D 3e',
"system:D&D 3.5e", 'system:D&D 3.5e',
"system:D&D 4e", 'system:D&D 4e',
"system:D&D 5e", 'system:D&D 5e',
"system:D&D 5e 2024", 'system:D&D 5e 2024',
"system:BD&D (B/X)", 'system:BD&D (B/X)',
"system:D&D Essentials", 'system:D&D Essentials',
// Other Famous RPGs // Other Famous RPGs
"system:Pathfinder 1e", 'system:Pathfinder 1e',
"system:Pathfinder 2e", 'system:Pathfinder 2e',
"system:Vampire: The Masquerade", 'system:Vampire: The Masquerade',
"system:Werewolf: The Apocalypse", 'system:Werewolf: The Apocalypse',
"system:Mage: The Ascension", 'system:Mage: The Ascension',
"system:Call of Cthulhu", 'system:Call of Cthulhu',
"system:Shadowrun", 'system:Shadowrun',
"system:Star Wars RPG (D6/D20/Edge of the Empire)", 'system:Star Wars RPG (D6/D20/Edge of the Empire)',
"system:Warhammer Fantasy Roleplay", 'system:Warhammer Fantasy Roleplay',
"system:Cyberpunk 2020", 'system:Cyberpunk 2020',
"system:Blades in the Dark", 'system:Blades in the Dark',
"system:Daggerheart", 'system:Daggerheart',
"system:Draw Steel", 'system:Draw Steel',
"system:Mutants and Masterminds", 'system:Mutants and Masterminds',
// Meta // Meta
"meta:V3", 'meta:V3',
"meta:Legacy", 'meta:Legacy',
"meta:Template", 'meta:Template',
"meta:Theme", 'meta:Theme',
"meta:free", 'meta:free',
"meta:Character Sheet", 'meta:Character Sheet',
"meta:Documentation", 'meta:Documentation',
"meta:NPC", 'meta:NPC',
"meta:Guide", 'meta:Guide',
"meta:Resource", 'meta:Resource',
"meta:Notes", 'meta:Notes',
"meta:Example", 'meta:Example',
// Book type // Book type
"type:Campaign", 'type:Campaign',
"type:Campaign Setting", 'type:Campaign Setting',
"type:Adventure", 'type:Adventure',
"type:One-Shot", 'type:One-Shot',
"type:Setting", 'type:Setting',
"type:World", 'type:World',
"type:Lore", 'type:Lore',
"type:History", 'type:History',
"type:Dungeon Master", 'type:Dungeon Master',
"type:Encounter Pack", 'type:Encounter Pack',
"type:Encounter", 'type:Encounter',
"type:Session Notes", 'type:Session Notes',
"type:reference", 'type:reference',
"type:Handbook", 'type:Handbook',
"type:Manual", 'type:Manual',
"type:Manuals", 'type:Manuals',
"type:Compendium", 'type:Compendium',
"type:Bestiary", 'type:Bestiary',
// ###################################### RPG Keywords // ###################################### RPG Keywords
// Classes / Subclasses / Archetypes // Classes / Subclasses / Archetypes
"Class", 'Class',
"Subclass", 'Subclass',
"Archetype", 'Archetype',
"Martial", 'Martial',
"Half-Caster", 'Half-Caster',
"Full Caster", 'Full Caster',
"Artificer", 'Artificer',
"Barbarian", 'Barbarian',
"Bard", 'Bard',
"Cleric", 'Cleric',
"Druid", 'Druid',
"Fighter", 'Fighter',
"Monk", 'Monk',
"Paladin", 'Paladin',
"Rogue", 'Rogue',
"Sorcerer", 'Sorcerer',
"Warlock", 'Warlock',
"Wizard", 'Wizard',
// Races / Species / Lineages // Races / Species / Lineages
"Race", 'Race',
"Ancestry", 'Ancestry',
"Lineage", 'Lineage',
"Aasimar", 'Aasimar',
"Beastfolk", 'Beastfolk',
"Dragonborn", 'Dragonborn',
"Dwarf", 'Dwarf',
"Elf", 'Elf',
"Goblin", 'Goblin',
"Half-Elf", 'Half-Elf',
"Half-Orc", 'Half-Orc',
"Human", 'Human',
"Kobold", 'Kobold',
"Lizardfolk", 'Lizardfolk',
"Lycan", 'Lycan',
"Orc", 'Orc',
"Tiefling", 'Tiefling',
"Vampire", 'Vampire',
"Yuan-Ti", 'Yuan-Ti',
// Magic / Spells / Items // Magic / Spells / Items
"Magic", 'Magic',
"Magic Item", 'Magic Item',
"Magic Items", 'Magic Items',
"Wondrous Item", 'Wondrous Item',
"Magic Weapon", 'Magic Weapon',
"Artifact", 'Artifact',
"Spell", 'Spell',
"Spells", 'Spells',
"Cantrip", 'Cantrip',
"Cantrips", 'Cantrips',
"Eldritch", 'Eldritch',
"Eldritch Invocation", 'Eldritch Invocation',
"Invocation", 'Invocation',
"Invocations", 'Invocations',
"Pact boon", 'Pact boon',
"Pact Boon", 'Pact Boon',
"Spellcaster", 'Spellcaster',
"Spellblade", 'Spellblade',
"Magical Tattoos", 'Magical Tattoos',
"Enchantment", 'Enchantment',
"Enchanted", 'Enchanted',
"Attunement", 'Attunement',
"Requires Attunement", 'Requires Attunement',
"Rune", 'Rune',
"Runes", 'Runes',
"Wand", 'Wand',
"Rod", 'Rod',
"Scroll", 'Scroll',
"Potion", 'Potion',
"Potions", 'Potions',
"Item", 'Item',
"Items", 'Items',
"Bag of Holding", 'Bag of Holding',
// Monsters / Creatures / Enemies // Monsters / Creatures / Enemies
"Monster", 'Monster',
"Creatures", 'Creatures',
"Creature", 'Creature',
"Beast", 'Beast',
"Beasts", 'Beasts',
"Humanoid", 'Humanoid',
"Undead", 'Undead',
"Fiend", 'Fiend',
"Aberration", 'Aberration',
"Ooze", 'Ooze',
"Giant", 'Giant',
"Dragon", 'Dragon',
"Monstrosity", 'Monstrosity',
"Demon", 'Demon',
"Devil", 'Devil',
"Elemental", 'Elemental',
"Construct", 'Construct',
"Constructs", 'Constructs',
"Boss", 'Boss',
"BBEG", 'BBEG',
// ############################# Media / Pop Culture // ############################# Media / Pop Culture
"One Piece", 'One Piece',
"Dragon Ball", 'Dragon Ball',
"Dragon Ball Z", 'Dragon Ball Z',
"Naruto", 'Naruto',
"Jujutsu Kaisen", 'Jujutsu Kaisen',
"Fairy Tail", 'Fairy Tail',
"Final Fantasy", 'Final Fantasy',
"Kingdom Hearts", 'Kingdom Hearts',
"Elder Scrolls", 'Elder Scrolls',
"Skyrim", 'Skyrim',
"WoW", 'WoW',
"World of Warcraft", 'World of Warcraft',
"Marvel Comics", 'Marvel Comics',
"DC Comics", 'DC Comics',
"Pokemon", 'Pokemon',
"League of Legends", 'League of Legends',
"Runeterra", 'Runeterra',
"Arcane", 'Arcane',
"Yu-Gi-Oh", 'Yu-Gi-Oh',
"Minecraft", 'Minecraft',
"Don't Starve", 'Don\'t Starve',
"Witcher", 'Witcher',
"Witcher 3", 'Witcher 3',
"Cyberpunk", 'Cyberpunk',
"Cyberpunk 2077", 'Cyberpunk 2077',
"Fallout", 'Fallout',
"Divinity Original Sin 2", 'Divinity Original Sin 2',
"Fullmetal Alchemist", 'Fullmetal Alchemist',
"Fullmetal Alchemist Brotherhood", 'Fullmetal Alchemist Brotherhood',
"Lobotomy Corporation", 'Lobotomy Corporation',
"Bloodborne", 'Bloodborne',
"Dragonlance", 'Dragonlance',
"Shackled City Adventure Path", 'Shackled City Adventure Path',
"Baldurs Gate 3", 'Baldurs Gate 3',
"Library of Ruina", 'Library of Ruina',
"Radiant Citadel", 'Radiant Citadel',
"Ravenloft", 'Ravenloft',
"Forgotten Realms", 'Forgotten Realms',
"Exandria", 'Exandria',
"Critical Role", 'Critical Role',
"Star Wars", 'Star Wars',
"SW5e", 'SW5e',
"Star Wars 5e", 'Star Wars 5e',
]; ];

View File

@@ -1,15 +1,15 @@
import "./tagInput.less"; import './tagInput.less';
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from 'react';
import Combobox from "../../../components/combobox.jsx"; import Combobox from '../../../components/combobox.jsx';
import tagSuggestionList from "./curatedTagSuggestionList.js"; import tagSuggestionList from './curatedTagSuggestionList.js';
const TagInput = ({tooltip, label, valuePatterns, values = [], unique = true, placeholder = "", smallText = "", onChange }) => { const TagInput = ({ tooltip, label, valuePatterns, values = [], unique = true, placeholder = '', smallText = '', onChange })=>{
const [tagList, setTagList] = useState( const [tagList, setTagList] = useState(
values.map((value)=>({ values.map((value)=>({
value, value,
editing : false, editing : false,
draft: "", draft : '',
})), })),
); );
@@ -37,11 +37,11 @@ const TagInput = ({tooltip, label, valuePatterns, values = [], unique = true, pl
// substrings to be normalized to the first value on the array // substrings to be normalized to the first value on the array
const duplicateGroups = [ const duplicateGroups = [
["5e 2024", "5.5e", "5e'24", "5.24", "5e24", "5.5"], ['5e 2024', '5.5e', '5e\'24', '5.24', '5e24', '5.5'],
["5e", "5th Edition"], ['5e', '5th Edition'],
["Dungeons & Dragons", "Dungeons and Dragons", "Dungeons n dragons"], ['Dungeons & Dragons', 'Dungeons and Dragons', 'Dungeons n dragons'],
["D&D", "DnD", "dnd", "Dnd", "dnD", "d&d", "d&D", "D&d"], ['D&D', 'DnD', 'dnd', 'Dnd', 'dnD', 'd&d', 'd&D', 'D&d'],
["P2e", "p2e", "P2E", "Pathfinder 2e"], ['P2e', 'p2e', 'P2E', 'Pathfinder 2e'],
]; ];
const normalizeValue = (input)=>{ const normalizeValue = (input)=>{
@@ -60,8 +60,8 @@ const TagInput = ({tooltip, label, valuePatterns, values = [], unique = true, pl
} }
} }
if (normalizedTag.includes(":")) { if(normalizedTag.includes(':')) {
const [rawType, rawValue = ""] = normalizedTag.split(":"); const [rawType, rawValue = ''] = normalizedTag.split(':');
const tagType = rawType.trim().toLowerCase(); const tagType = rawType.trim().toLowerCase();
const tagValue = rawValue.trim(); const tagValue = rawValue.trim();
@@ -102,28 +102,28 @@ const TagInput = ({tooltip, label, valuePatterns, values = [], unique = true, pl
}; };
const stopEditing = (index)=>{ const stopEditing = (index)=>{
setTagList((prev) => prev.map((t, i) => (i === index ? { ...t, editing: false, draft: "" } : t))); setTagList((prev)=>prev.map((t, i)=>(i === index ? { ...t, editing: false, draft: '' } : t)));
}; };
const suggestionOptions = tagSuggestionList.map((tag)=>{ const suggestionOptions = tagSuggestionList.map((tag)=>{
const tagType = tag.split(":"); const tagType = tag.split(':');
let classes = "item"; let classes = 'item';
switch (tagType[0]) { switch (tagType[0]) {
case "type": case 'type':
classes = "item type"; classes = 'item type';
break; break;
case "group": case 'group':
classes = "item group"; classes = 'item group';
break; break;
case "meta": case 'meta':
classes = "item meta"; classes = 'item meta';
break; break;
case "system": case 'system':
classes = "item system"; classes = 'item system';
break; break;
default: default:
classes = "item"; classes = 'item';
break; break;
} }
@@ -135,54 +135,50 @@ const TagInput = ({tooltip, label, valuePatterns, values = [], unique = true, pl
}); });
return ( return (
<div className="tagInputWrap"> <div className='tagInputWrap'>
<Combobox <Combobox
trigger="click" trigger='click'
className="tagInput-dropdown" className='tagInput-dropdown'
default="" default=''
placeholder={placeholder} placeholder={placeholder}
options={label === "tags" ? suggestionOptions : []} options={label === 'tags' ? suggestionOptions : []}
tooltip={tooltip} tooltip={tooltip}
autoSuggest={ autoSuggest={
label === "tags" label === 'tags'
? { ? {
suggestMethod: "startsWith", suggestMethod : 'startsWith',
clearAutoSuggestOnClick : true, clearAutoSuggestOnClick : true,
filterOn: ["value", "title"], filterOn : ['value', 'title'],
} }
: { suggestMethod: "includes", clearAutoSuggestOnClick: true, filterOn: [] } : { suggestMethod: 'includes', clearAutoSuggestOnClick: true, filterOn: [] }
} }
valuePatterns={valuePatterns.source} valuePatterns={valuePatterns.source}
onSelect={(value)=>submitTag(value)} onSelect={(value)=>submitTag(value)}
onEntry={(e)=>{ onEntry={(e)=>{
if (e.key === "Enter") { if(e.key === 'Enter') {
e.preventDefault(); e.preventDefault();
submitTag(e.target.value); submitTag(e.target.value);
} }
}} }}
/> />
<ul className="list"> <ul className='list'>
{tagList.map((t, i) => {tagList.map((t, i)=>t.editing ? (
t.editing ? (
<input <input
key={i} key={i}
type="text" type='text'
value={t.draft} // always use draft value={t.draft} // always use draft
pattern={valuePatterns.source} pattern={valuePatterns.source}
onChange={(e) => onChange={(e)=>setTagList((prev)=>prev.map((tag, idx)=>(idx === i ? { ...tag, draft: e.target.value } : tag)),
setTagList((prev) =>
prev.map((tag, idx) => (idx === i ? { ...tag, draft: e.target.value } : tag)),
) )
} }
onKeyDown={(e)=>{ onKeyDown={(e)=>{
if (e.key === "Enter") { if(e.key === 'Enter') {
e.preventDefault(); e.preventDefault();
submitTag(t.draft, i); // submit draft submitTag(t.draft, i); // submit draft
setTagList((prev) => setTagList((prev)=>prev.map((tag, idx)=>(idx === i ? { ...tag, draft: '' } : tag)),
prev.map((tag, idx) => (idx === i ? { ...tag, draft: "" } : tag)),
); );
} }
if (e.key === "Escape") { if(e.key === 'Escape') {
stopEditing(i); stopEditing(i);
e.target.blur(); e.target.blur();
} }
@@ -190,15 +186,15 @@ const TagInput = ({tooltip, label, valuePatterns, values = [], unique = true, pl
autoFocus autoFocus
/> />
) : ( ) : (
<li key={i} className="tag" onClick={() => editTag(i)}> <li key={i} className='tag' onClick={()=>editTag(i)}>
{t.value} {t.value}
<button <button
type="button" type='button'
onClick={(e)=>{ onClick={(e)=>{
e.stopPropagation(); e.stopPropagation();
removeTag(i); removeTag(i);
}}> }}>
<i className="fa fa-times fa-fw" /> <i className='fa fa-times fa-fw' />
</button> </button>
</li> </li>
), ),

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
import { createRoot } from "react-dom/client"; import { createRoot } from 'react-dom/client';
import Homebrew from "./homebrew.jsx"; import Homebrew from './homebrew.jsx';
const props = window.__INITIAL_PROPS__ || {}; const props = window.__INITIAL_PROPS__ || {};
createRoot(document.getElementById("reactRoot")).render(<Homebrew {...props} />); createRoot(document.getElementById('reactRoot')).render(<Homebrew {...props} />);

View File

@@ -8,7 +8,7 @@ import Markdown from '@shared/markdown.js';
import _ from 'lodash'; import _ from 'lodash';
import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js'; import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js';
import { printCurrentBrew, fetchThemeBundle, splitTextStyleAndMetadata } from '@shared/helpers.js'; import { printCurrentBrew, fetchThemeBundle } from '@shared/helpers.js';
import SplitPane from '../../../components/splitPane/splitPane.jsx'; import SplitPane from '../../../components/splitPane/splitPane.jsx';
import Editor from '../../editor/editor.jsx'; import Editor from '../../editor/editor.jsx';

View File

@@ -1,4 +1,4 @@
/* eslint-disable max-lines */
import './homePage.less'; import './homePage.less';
// Common imports // Common imports
@@ -8,7 +8,7 @@ import Markdown from '@shared/markdown.js';
import _ from 'lodash'; import _ from 'lodash';
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js'; import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
import { printCurrentBrew, fetchThemeBundle, splitTextStyleAndMetadata } from '@shared/helpers.js'; import { printCurrentBrew, fetchThemeBundle } from '@shared/helpers.js';
import SplitPane from '../../../components/splitPane/splitPane.jsx'; import SplitPane from '../../../components/splitPane/splitPane.jsx';
import Editor from '../../editor/editor.jsx'; import Editor from '../../editor/editor.jsx';

View File

@@ -1,9 +1,9 @@
import DB from "./server/db.js"; import DB from './server/db.js';
import createApp from "./server/app.js"; import createApp from './server/app.js';
import config from "./server/config.js"; import config from './server/config.js';
import { createServer as createViteServer } from "vite"; import { createServer as createViteServer } from 'vite';
const isDev = process.env.NODE_ENV === "local"; const isDev = process.env.NODE_ENV === 'local';
async function start() { async function start() {
let vite; let vite;
@@ -11,23 +11,23 @@ async function start() {
if(isDev) { if(isDev) {
vite = await createViteServer({ vite = await createViteServer({
server : { middlewareMode: true }, server : { middlewareMode: true },
appType: "custom", appType : 'custom',
}); });
} }
await DB.connect(config).catch((err)=>{ await DB.connect(config).catch((err)=>{
console.error("Database connection failed:", err); console.error('Database connection failed:', err);
process.exit(1); process.exit(1);
}); });
const app = await createApp(vite); const app = await createApp(vite);
const PORT = process.env.PORT || config.get("web_port") || 3000; const PORT = process.env.PORT || config.get('web_port') || 3000;
app.listen(PORT, ()=>{ app.listen(PORT, ()=>{
const reset = "\x1b[0m"; // Reset to default style const reset = '\x1b[0m'; // Reset to default style
const bright = "\x1b[1m"; // Bright (bold) style const bright = '\x1b[1m'; // Bright (bold) style
const cyan = "\x1b[36m"; // Cyan color const cyan = '\x1b[36m'; // Cyan color
const underline = "\x1b[4m"; // Underlined style const underline = '\x1b[4m'; // Underlined style
console.log(`\n\tserver started at: ${new Date().toLocaleString()}`); console.log(`\n\tserver started at: ${new Date().toLocaleString()}`);
console.log(`\tserver on port: ${PORT}`); console.log(`\tserver on port: ${PORT}`);

View File

@@ -1,4 +1,4 @@
/* eslint-disable max-depth */
/* eslint-disable max-lines */ /* eslint-disable max-lines */
import _ from 'lodash'; import _ from 'lodash';
import { marked as Marked } from 'marked'; import { marked as Marked } from 'marked';

View File

@@ -1,6 +1,3 @@
import dedent from 'dedent';
// Mongoose Publishing Licenses // Mongoose Publishing Licenses
export default { export default {

View File

@@ -1,38 +1,38 @@
// vite.config.js // vite.config.js
import { defineConfig } from "vite"; import { defineConfig } from 'vite';
import react from "@vitejs/plugin-react"; import react from '@vitejs/plugin-react';
import path from "path"; import path from 'path';
import { generateAssetsPlugin } from "./vitePlugins/generateAssetsPlugin.js"; import { generateAssetsPlugin } from './vitePlugins/generateAssetsPlugin.js';
export default defineConfig({ export default defineConfig({
plugins : [react(), generateAssetsPlugin()], plugins : [react(), generateAssetsPlugin()],
resolve : { resolve : {
alias : { alias : {
"@vitreum": path.resolve(__dirname, "./vitreum"), '@vitreum' : path.resolve(__dirname, './vitreum'),
"@shared": path.resolve(__dirname, "./shared"), '@shared' : path.resolve(__dirname, './shared'),
"@sharedStyles": path.resolve(__dirname, "./shared/naturalcrit/styles"), '@sharedStyles' : path.resolve(__dirname, './shared/naturalcrit/styles'),
"@navbar": path.resolve(__dirname, "./client/homebrew/navbar"), '@navbar' : path.resolve(__dirname, './client/homebrew/navbar'),
"@themes": path.resolve(__dirname, "./themes"), '@themes' : path.resolve(__dirname, './themes'),
}, },
}, },
build : { build : {
outDir: "build", outDir : 'build',
emptyOutDir : false, emptyOutDir : false,
rollupOptions : { rollupOptions : {
output : { output : {
entryFileNames: "[name]/bundle.js", entryFileNames : '[name]/bundle.js',
chunkFileNames: "[name]/[name]-[hash].js", chunkFileNames : '[name]/[name]-[hash].js',
assetFileNames: "[name]/[name].[ext]", assetFileNames : '[name]/[name].[ext]',
}, },
}, },
}, },
define : { define : {
global: "window.__INITIAL_PROPS__", global : 'window.__INITIAL_PROPS__',
}, },
server : { server : {
port : 8000, port : 8000,
fs : { fs : {
allow: ["."], allow : ['.'],
}, },
}, },
}); });

View File

@@ -1,41 +1,41 @@
// vite-plugins/generateAssetsPlugin.js // vite-plugins/generateAssetsPlugin.js
import fs from "fs-extra"; import fs from 'fs-extra';
import path from "path"; import path from 'path';
import less from "less"; import less from 'less';
export function generateAssetsPlugin(isDev = false) { export function generateAssetsPlugin(isDev = false) {
return { return {
name: "generate-assets", name : 'generate-assets',
async buildStart() { async buildStart() {
const buildDir = path.resolve(process.cwd(), "build"); const buildDir = path.resolve(process.cwd(), 'build');
// Copy favicon // Copy favicon
await fs.copy("./client/homebrew/favicon.ico", `${buildDir}/assets/favicon.ico`); await fs.copy('./client/homebrew/favicon.ico', `${buildDir}/assets/favicon.ico`);
// Copy shared styles/fonts // Copy shared styles/fonts
const assets = fs.readdirSync("./shared/naturalcrit/styles"); const assets = fs.readdirSync('./shared/naturalcrit/styles');
for (const file of assets) { for (const file of assets) {
await fs.copy(`./shared/naturalcrit/styles/${file}`, `${buildDir}/fonts/${file}`); await fs.copy(`./shared/naturalcrit/styles/${file}`, `${buildDir}/fonts/${file}`);
} }
// Compile Legacy themes // Compile Legacy themes
const themes = { Legacy: {}, V3: {} }; const themes = { Legacy: {}, V3: {} };
const legacyDirs = fs.readdirSync("./themes/Legacy"); const legacyDirs = fs.readdirSync('./themes/Legacy');
for (const dir of legacyDirs) { for (const dir of legacyDirs) {
const themeData = JSON.parse(fs.readFileSync(`./themes/Legacy/${dir}/settings.json`, "utf-8")); const themeData = JSON.parse(fs.readFileSync(`./themes/Legacy/${dir}/settings.json`, 'utf-8'));
themeData.path = dir; themeData.path = dir;
themes.Legacy[dir] = themeData; themes.Legacy[dir] = themeData;
const src = `./themes/Legacy/${dir}/style.less`; const src = `./themes/Legacy/${dir}/style.less`;
const outputDir = `${buildDir}/themes/Legacy/${dir}/style.css`; const outputDir = `${buildDir}/themes/Legacy/${dir}/style.css`;
const lessOutput = await less.render(fs.readFileSync(src, "utf-8"), { compress: !isDev }); const lessOutput = await less.render(fs.readFileSync(src, 'utf-8'), { compress: !isDev });
await fs.outputFile(outputDir, lessOutput.css); await fs.outputFile(outputDir, lessOutput.css);
} }
// Compile V3 themes // Compile V3 themes
const v3Dirs = fs.readdirSync("./themes/V3"); const v3Dirs = fs.readdirSync('./themes/V3');
for (const dir of v3Dirs) { for (const dir of v3Dirs) {
const themeData = JSON.parse(fs.readFileSync(`./themes/V3/${dir}/settings.json`, "utf-8")); const themeData = JSON.parse(fs.readFileSync(`./themes/V3/${dir}/settings.json`, 'utf-8'));
themeData.path = dir; themeData.path = dir;
themes.V3[dir] = themeData; themes.V3[dir] = themeData;
@@ -50,30 +50,30 @@ export function generateAssetsPlugin(isDev = false) {
const src = `./themes/V3/${dir}/style.less`; const src = `./themes/V3/${dir}/style.less`;
const outputDir = `${buildDir}/themes/V3/${dir}/style.css`; const outputDir = `${buildDir}/themes/V3/${dir}/style.css`;
const lessOutput = await less.render(fs.readFileSync(src, "utf-8"), { compress: !isDev }); const lessOutput = await less.render(fs.readFileSync(src, 'utf-8'), { compress: !isDev });
await fs.outputFile(outputDir, lessOutput.css); await fs.outputFile(outputDir, lessOutput.css);
} }
// Write themes.json // Write themes.json
await fs.outputFile("./themes/themes.json", JSON.stringify(themes, null, 2)); await fs.outputFile('./themes/themes.json', JSON.stringify(themes, null, 2));
// Copy fonts/assets/icons // Copy fonts/assets/icons
await fs.copy("./themes/fonts", `${buildDir}/fonts`); await fs.copy('./themes/fonts', `${buildDir}/fonts`);
await fs.copy("./themes/assets", `${buildDir}/assets`); await fs.copy('./themes/assets', `${buildDir}/assets`);
await fs.copy("./client/icons", `${buildDir}/icons`); await fs.copy('./client/icons', `${buildDir}/icons`);
// Compile CodeMirror editor themes // Compile CodeMirror editor themes
const editorThemesBuildDir = `${buildDir}/homebrew/cm-themes`; const editorThemesBuildDir = `${buildDir}/homebrew/cm-themes`;
await fs.copy("./node_modules/codemirror/theme", editorThemesBuildDir); await fs.copy('./node_modules/codemirror/theme', editorThemesBuildDir);
await fs.copy("./themes/codeMirror/customThemes", editorThemesBuildDir); await fs.copy('./themes/codeMirror/customThemes', editorThemesBuildDir);
const editorThemeFiles = fs.readdirSync(editorThemesBuildDir); const editorThemeFiles = fs.readdirSync(editorThemesBuildDir);
await fs.outputFile(`${buildDir}/homebrew/codeMirror/editorThemes.json`, await fs.outputFile(`${buildDir}/homebrew/codeMirror/editorThemes.json`,
JSON.stringify(["default", ...editorThemeFiles.map((f) => f.slice(0, -4))], null, 2), JSON.stringify(['default', ...editorThemeFiles.map((f)=>f.slice(0, -4))], null, 2),
); );
// Copy remaining CodeMirror assets // Copy remaining CodeMirror assets
await fs.copy("./themes/codeMirror", `${buildDir}/homebrew/codeMirror`); await fs.copy('./themes/codeMirror', `${buildDir}/homebrew/codeMirror`);
}, },
}; };
} }

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from "react"; import React, { useEffect } from 'react';
//old vitreum file, still imported in some pages //old vitreum file, still imported in some pages
@@ -9,12 +9,11 @@ const injectTag = (tag, props, children) => {
document.getElementsByTagName('head')[0].appendChild(injectNode); document.getElementsByTagName('head')[0].appendChild(injectNode);
}; };
const obj2props = (obj) => const obj2props = (obj)=>Object.entries(obj)
Object.entries(obj)
.map(([k, v])=>`${k}="${v}"`) .map(([k, v])=>`${k}="${v}"`)
.join(" "); .join(' ');
const toStr = (chld) => (Array.isArray(chld) ? chld.join("") : chld); const toStr = (chld)=>(Array.isArray(chld) ? chld.join('') : chld);
const onServer = typeof window === "undefined"; const onServer = typeof window === 'undefined';
let NamedTags = {}; let NamedTags = {};
let UnnamedTags = []; let UnnamedTags = [];
@@ -27,7 +26,7 @@ export const HeadComponents = {
}, [children]); }, [children]);
return null; return null;
}, },
Favicon({ type = "image/png", href = "", rel = "icon", id = "favicon" }) { Favicon({ type = 'image/png', href = '', rel = 'icon', id = 'favicon' }) {
if(onServer) NamedTags.favicon = `<link rel='shortcut icon' type="${type}" id="${id}" href="${href}" />`; if(onServer) NamedTags.favicon = `<link rel='shortcut icon' type="${type}" id="${id}" href="${href}" />`;
useEffect(()=>{ useEffect(()=>{
document.getElementById(id).href = href; document.getElementById(id).href = href;
@@ -53,16 +52,16 @@ export const HeadComponents = {
return null; return null;
}, },
Meta(props) { Meta(props) {
let tag = `<meta ${obj2props(props)} />`; const tag = `<meta ${obj2props(props)} />`;
props.property || props.name ? (NamedTags[props.property || props.name] = tag) : UnnamedTags.push(tag); props.property || props.name ? (NamedTags[props.property || props.name] = tag) : UnnamedTags.push(tag);
useEffect(()=>{ useEffect(()=>{
document document
.getElementsByTagName("head")[0] .getElementsByTagName('head')[0]
.insertAdjacentHTML("beforeend", Object.values(NamedTags).join("\n")); .insertAdjacentHTML('beforeend', Object.values(NamedTags).join('\n'));
}, [NamedTags]); }, [NamedTags]);
return null; return null;
}, },
Style({ children, type = "text/css" }) { Style({ children, type = 'text/css' }) {
if(onServer) UnnamedTags.push(`<style type="${type}">${toStr(children)}</style>`); if(onServer) UnnamedTags.push(`<style type="${type}">${toStr(children)}</style>`);
return null; return null;
}, },
@@ -75,7 +74,7 @@ export const Inject = ({ tag, children, ...props }) => {
return null; return null;
}; };
export const generate = () => Object.values(NamedTags).concat(UnnamedTags).join("\n"); export const generate = ()=>Object.values(NamedTags).concat(UnnamedTags).join('\n');
export const flush = ()=>{ export const flush = ()=>{
NamedTags = {}; NamedTags = {};