mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-05-07 18:48:39 +00:00
Merge branch 'master' into HTMLDownload
This commit is contained in:
@@ -49,4 +49,4 @@ const Admin = ()=>{
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = Admin;
|
||||
export default Admin;
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
@import 'naturalcrit/styles/reset.less';
|
||||
@import 'naturalcrit/styles/elements.less';
|
||||
@import 'naturalcrit/styles/animations.less';
|
||||
@import 'naturalcrit/styles/colors.less';
|
||||
@import 'naturalcrit/styles/tooltip.less';
|
||||
@import './themes/fonts/iconFonts/fontAwesome.less';
|
||||
|
||||
@import 'font-awesome/css/font-awesome.css';
|
||||
@import '@sharedStyles/reset.less';
|
||||
@import '@sharedStyles/elements.less';
|
||||
@import '@sharedStyles/animations.less';
|
||||
@import '@sharedStyles/colors.less';
|
||||
@import '@sharedStyles/tooltip.less';
|
||||
@import '@themes/fonts/iconFonts/fontAwesome.less';
|
||||
|
||||
html,body, #reactContainer, .naturalCrit { min-height : 100%; }
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@import '@sharedStyles/colors.less';
|
||||
|
||||
.brewUtil {
|
||||
.result {
|
||||
margin-top : 20px;
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import Admin from './admin.jsx';
|
||||
|
||||
const props = window.__INITIAL_PROPS__ || {};
|
||||
|
||||
createRoot(document.getElementById('reactRoot')).render(<Admin {...props} />);
|
||||
@@ -1,7 +1,7 @@
|
||||
import diceFont from 'themes/fonts/iconFonts/diceFont.js';
|
||||
import elderberryInn from 'themes/fonts/iconFonts/elderberryInn.js';
|
||||
import fontAwesome from 'themes/fonts/iconFonts/fontAwesome.js';
|
||||
import gameIcons from 'themes/fonts/iconFonts/gameIcons.js';
|
||||
import diceFont from '@themes/fonts/iconFonts/diceFont.js';
|
||||
import elderberryInn from '@themes/fonts/iconFonts/elderberryInn.js';
|
||||
import fontAwesome from '@themes/fonts/iconFonts/fontAwesome.js';
|
||||
import gameIcons from '@themes/fonts/iconFonts/gameIcons.js';
|
||||
|
||||
const emojis = {
|
||||
...diceFont,
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
@import (less) 'codemirror/addon/hint/show-hint.css';
|
||||
|
||||
//Icon fonts included so they can appear in emoji autosuggest dropdown
|
||||
@import (less) './themes/fonts/iconFonts/diceFont.less';
|
||||
@import (less) './themes/fonts/iconFonts/elderberryInn.less';
|
||||
@import (less) './themes/fonts/iconFonts/gameIcons.less';
|
||||
@import (less) './themes/fonts/iconFonts/fontAwesome.less';
|
||||
@import (less) '@themes/fonts/iconFonts/diceFont.less';
|
||||
@import (less) '@themes/fonts/iconFonts/elderberryInn.less';
|
||||
@import (less) '@themes/fonts/iconFonts/gameIcons.less';
|
||||
@import (less) '@themes/fonts/iconFonts/fontAwesome.less';
|
||||
|
||||
@keyframes sourceMoveAnimation {
|
||||
50% { color : white;background-color : red;}
|
||||
|
||||
@@ -11,16 +11,17 @@ const Combobox = createReactClass({
|
||||
trigger : 'hover',
|
||||
default : '',
|
||||
placeholder : '',
|
||||
tooltip: '',
|
||||
tooltip : '',
|
||||
autoSuggest : {
|
||||
clearAutoSuggestOnClick : true,
|
||||
suggestMethod : 'includes',
|
||||
filterOn : [] // should allow as array to filter on multiple attributes, or even custom filter
|
||||
},
|
||||
valuePatterns: /.+/
|
||||
valuePatterns : /.+/
|
||||
};
|
||||
},
|
||||
getInitialState : function() {
|
||||
this.dropdownRef = React.createRef();
|
||||
return {
|
||||
showDropdown : false,
|
||||
value : '',
|
||||
@@ -41,7 +42,7 @@ const Combobox = createReactClass({
|
||||
},
|
||||
handleClickOutside : function(e){
|
||||
// Close dropdown when clicked outside
|
||||
if(this.refs.dropdown && !this.refs.dropdown.contains(e.target)) {
|
||||
if(this.dropdownRef.current && !this.dropdownRef.current.contains(e.target)) {
|
||||
this.handleDropdown(false);
|
||||
}
|
||||
},
|
||||
@@ -88,7 +89,7 @@ const Combobox = createReactClass({
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e)=>{
|
||||
if (e.key === "Enter") {
|
||||
if(e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this.props.onEntry(e);
|
||||
}
|
||||
@@ -128,7 +129,7 @@ const Combobox = createReactClass({
|
||||
});
|
||||
return (
|
||||
<div className={`dropdown-container ${this.props.className}`}
|
||||
ref='dropdown'
|
||||
ref={this.dropdownRef}
|
||||
onMouseLeave={this.props.trigger == 'hover' ? ()=>{this.handleDropdown(false);} : undefined}>
|
||||
{this.renderTextInput()}
|
||||
{this.renderDropdown(dropdownChildren)}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@import '@sharedStyles/colors.less';
|
||||
|
||||
.renderWarnings {
|
||||
position : relative;
|
||||
float : right;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@import '@sharedStyles/core.less';
|
||||
|
||||
.splitPane {
|
||||
position : relative;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
/*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/
|
||||
import brewRendererStylesUrl from './brewRenderer.less?url';
|
||||
import headerNavStylesUrl from './headerNav/headerNav.less?url';
|
||||
import './brewRenderer.less';
|
||||
import React, { useState, useRef, useMemo, useEffect } from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import MarkdownLegacy from '../../../shared/markdownLegacy.js';
|
||||
import Markdown from '../../../shared/markdown.js';
|
||||
import MarkdownLegacy from '@shared/markdownLegacy.js';
|
||||
import Markdown from '@shared/markdown.js';
|
||||
import ErrorBar from './errorBar/errorBar.jsx';
|
||||
import ToolBar from './toolBar/toolBar.jsx';
|
||||
|
||||
@@ -13,10 +15,10 @@ import RenderWarnings from '../../components/renderWarnings/renderWarnings.jsx';
|
||||
import NotificationPopup from './notificationPopup/notificationPopup.jsx';
|
||||
import Frame from 'react-frame-component';
|
||||
import dedent from 'dedent';
|
||||
import { printCurrentBrew } from '../../../shared/helpers.js';
|
||||
import { printCurrentBrew } from '@shared/helpers.js';
|
||||
|
||||
import HeaderNav from './headerNav/headerNav.jsx';
|
||||
import { safeHTML } from './safeHTML.js';
|
||||
import safeHTML from './safeHTML.js';
|
||||
|
||||
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
|
||||
const PAGEBREAK_REGEX_LEGACY = /\\page(?:break)?/m;
|
||||
@@ -29,6 +31,8 @@ const INITIAL_CONTENT = dedent`
|
||||
<!DOCTYPE html><html><head>
|
||||
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
|
||||
<link href='/homebrew/bundle.css' type="text/css" rel='stylesheet' />
|
||||
<link href="${brewRendererStylesUrl}" rel="stylesheet" />
|
||||
<link href="${headerNavStylesUrl}" rel="stylesheet" />
|
||||
<base target=_blank>
|
||||
</head><body style='overflow: hidden'><div></div></body></html>`;
|
||||
|
||||
@@ -343,6 +347,9 @@ const BrewRenderer = (props)=>{
|
||||
</div>
|
||||
{headerState ? <HeaderNav ref={pagesRef} /> : <></>}
|
||||
</Frame>
|
||||
{state.isMounted &&
|
||||
<div id='brewRendered'></div>
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import (multiple, less) 'shared/naturalcrit/styles/reset.less';
|
||||
@import '@sharedStyles/core.less';
|
||||
|
||||
.brewRenderer {
|
||||
height : 100vh;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@import '@sharedStyles/colors.less';
|
||||
|
||||
.errorBar {
|
||||
position : absolute;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import './notificationPopup.less';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import request from '../../utils/request-middleware.js';
|
||||
import Markdown from 'markdown.js';
|
||||
import Markdown from '@shared/markdown.js';
|
||||
|
||||
import Dialog from '../../../components/dialog.jsx';
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@import './client/homebrew/navbar/navbar.less';
|
||||
|
||||
.popups {
|
||||
position : fixed;
|
||||
top : calc(@navbarHeight + @viewerToolsHeight);
|
||||
|
||||
@@ -43,4 +43,4 @@ function safeHTML(htmlString) {
|
||||
return div.innerHTML;
|
||||
};
|
||||
|
||||
module.exports.safeHTML = safeHTML;
|
||||
export default safeHTML;
|
||||
@@ -4,7 +4,7 @@ import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import _ from 'lodash';
|
||||
import dedent from 'dedent';
|
||||
import Markdown from '../../../shared/markdown.js';
|
||||
import Markdown from '@shared/markdown.js';
|
||||
|
||||
import CodeEditor from '../../components/codeEditor/codeEditor.jsx';
|
||||
import SnippetBar from './snippetbar/snippetbar.jsx';
|
||||
@@ -88,7 +88,7 @@ const Editor = createReactClass({
|
||||
const snippetBar = document.querySelector('.editor > .snippetBar');
|
||||
if(!snippetBar) return;
|
||||
|
||||
this.resizeObserver = new ResizeObserver(entries=>{
|
||||
this.resizeObserver = new ResizeObserver((entries)=>{
|
||||
const height = document.querySelector('.editor > .snippetBar').offsetHeight;
|
||||
this.setState({ snippetBarHeight: height });
|
||||
});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
@import 'themes/codeMirror/customEditorStyles.less';
|
||||
@import '@sharedStyles/core.less';
|
||||
@import '@themes/codeMirror/customEditorStyles.less';
|
||||
|
||||
.editor {
|
||||
position : relative;
|
||||
width : 100%;
|
||||
|
||||
@@ -7,7 +7,8 @@ import request from '../../utils/request-middleware.js';
|
||||
import Combobox from '../../../components/combobox.jsx';
|
||||
import TagInput from '../tagInput/tagInput.jsx';
|
||||
|
||||
import Themes from 'themes/themes.json';
|
||||
|
||||
import Themes from '@themes/themes.json';
|
||||
import validations from './validations.js';
|
||||
|
||||
import homebreweryThumbnail from '../../thumbnail.png';
|
||||
@@ -337,9 +338,9 @@ const MetadataEditor = createReactClass({
|
||||
{this.renderThumbnail()}
|
||||
</div>
|
||||
|
||||
<div className="field tags">
|
||||
<div className='field tags'>
|
||||
<label>Tags</label>
|
||||
<div className="value" >
|
||||
<div className='value' >
|
||||
<TagInput
|
||||
label='tags'
|
||||
valuePatterns={/^\s*(?:(?:group|meta|system|type)\s*:\s*)?[A-Za-z0-9][A-Za-z0-9 \/\\.&_\-]{0,40}\s*$/}
|
||||
@@ -350,7 +351,7 @@ const MetadataEditor = createReactClass({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{this.renderLanguageDropdown()}
|
||||
|
||||
@@ -362,9 +363,9 @@ const MetadataEditor = createReactClass({
|
||||
|
||||
{this.renderAuthors()}
|
||||
|
||||
<div className="field invitedAuthors">
|
||||
<div className='field invitedAuthors'>
|
||||
<label>Invited authors</label>
|
||||
<div className="value">
|
||||
<div className='value'>
|
||||
<TagInput
|
||||
label='invited authors'
|
||||
valuePatterns={/.+/}
|
||||
@@ -377,7 +378,7 @@ const MetadataEditor = createReactClass({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<h2>Privacy</h2>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import 'naturalcrit/styles/colors.less';
|
||||
@import '@sharedStyles/core.less';
|
||||
|
||||
.userThemeName {
|
||||
padding-right : 10px;
|
||||
|
||||
@@ -7,13 +7,13 @@ import _ from 'lodash';
|
||||
import cx from 'classnames';
|
||||
|
||||
import { loadHistory } from '../../utils/versionHistory.js';
|
||||
import { brewSnippetsToJSON } from '../../../../shared/helpers.js';
|
||||
import { brewSnippetsToJSON } from '@shared/helpers.js';
|
||||
|
||||
import Legacy5ePHB from 'themes/Legacy/5ePHB/snippets.js';
|
||||
import V3_5ePHB from 'themes/V3/5ePHB/snippets.js';
|
||||
import V3_5eDMG from 'themes/V3/5eDMG/snippets.js';
|
||||
import V3_Journal from 'themes/V3/Journal/snippets.js';
|
||||
import V3_Blank from 'themes/V3/Blank/snippets.js';
|
||||
import Legacy5ePHB from '@themes/Legacy/5ePHB/snippets.js';
|
||||
import V3_5ePHB from '@themes/V3/5ePHB/snippets.js';
|
||||
import V3_5eDMG from '@themes/V3/5eDMG/snippets.js';
|
||||
import V3_Journal from '@themes/V3/Journal/snippets.js';
|
||||
import V3_Blank from '@themes/V3/Blank/snippets.js';
|
||||
|
||||
const ThemeSnippets = {
|
||||
Legacy_5ePHB : Legacy5ePHB,
|
||||
@@ -23,7 +23,7 @@ const ThemeSnippets = {
|
||||
V3_Blank : V3_Blank,
|
||||
};
|
||||
|
||||
import EditorThemes from 'build/homebrew/codeMirror/editorThemes.json';
|
||||
import EditorThemes from '../../../../build/homebrew/codeMirror/editorThemes.json';
|
||||
|
||||
const execute = function(val, props){
|
||||
if(_.isFunction(val)) return val(props);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@import '@sharedStyles/core.less';
|
||||
@import (less) './client/icons/customIcons.less';
|
||||
@import (less) '././././themes/fonts/5e/fonts.less';
|
||||
@import (less) '@themes/fonts/5e/fonts.less';
|
||||
|
||||
.snippetBar {
|
||||
@menuHeight : 25px;
|
||||
|
||||
@@ -1,210 +1,219 @@
|
||||
export default [
|
||||
export const tagSuggestionList = [
|
||||
// ############################## Systems
|
||||
// D&D
|
||||
"system:D&D Original",
|
||||
"system:D&D Basic",
|
||||
"system:AD&D 1e",
|
||||
"system:AD&D 2e",
|
||||
"system:D&D 3e",
|
||||
"system:D&D 3.5e",
|
||||
"system:D&D 4e",
|
||||
"system:D&D 5e",
|
||||
"system:D&D 5e 2024",
|
||||
"system:BD&D (B/X)",
|
||||
"system:D&D Essentials",
|
||||
'system:D&D Original',
|
||||
'system:D&D Basic',
|
||||
'system:AD&D 1e',
|
||||
'system:AD&D 2e',
|
||||
'system:D&D 3e',
|
||||
'system:D&D 3.5e',
|
||||
'system:D&D 4e',
|
||||
'system:D&D 5e',
|
||||
'system:D&D 5e 2024',
|
||||
'system:BD&D (B/X)',
|
||||
'system:D&D Essentials',
|
||||
|
||||
// Other Famous RPGs
|
||||
"system:Pathfinder 1e",
|
||||
"system:Pathfinder 2e",
|
||||
"system:Vampire: The Masquerade",
|
||||
"system:Werewolf: The Apocalypse",
|
||||
"system:Mage: The Ascension",
|
||||
"system:Call of Cthulhu",
|
||||
"system:Shadowrun",
|
||||
"system:Star Wars RPG (D6/D20/Edge of the Empire)",
|
||||
"system:Warhammer Fantasy Roleplay",
|
||||
"system:Cyberpunk 2020",
|
||||
"system:Blades in the Dark",
|
||||
"system:Daggerheart",
|
||||
"system:Draw Steel",
|
||||
"system:Mutants and Masterminds",
|
||||
'system:Pathfinder 1e',
|
||||
'system:Pathfinder 2e',
|
||||
'system:Vampire: The Masquerade',
|
||||
'system:Werewolf: The Apocalypse',
|
||||
'system:Mage: The Ascension',
|
||||
'system:Call of Cthulhu',
|
||||
'system:Shadowrun',
|
||||
'system:Star Wars RPG (D6/D20/Edge of the Empire)',
|
||||
'system:Warhammer Fantasy Roleplay',
|
||||
'system:Cyberpunk 2020',
|
||||
'system:Blades in the Dark',
|
||||
'system:Daggerheart',
|
||||
'system:Draw Steel',
|
||||
'system:Mutants and Masterminds',
|
||||
|
||||
// Meta
|
||||
"meta:V3",
|
||||
"meta:Legacy",
|
||||
"meta:Template",
|
||||
"meta:Theme",
|
||||
"meta:free",
|
||||
"meta:Character Sheet",
|
||||
"meta:Documentation",
|
||||
"meta:NPC",
|
||||
"meta:Guide",
|
||||
"meta:Resource",
|
||||
"meta:Notes",
|
||||
"meta:Example",
|
||||
'meta:V3',
|
||||
'meta:Legacy',
|
||||
'meta:Template',
|
||||
'meta:Theme',
|
||||
'meta:free',
|
||||
'meta:Character Sheet',
|
||||
'meta:Documentation',
|
||||
'meta:NPC',
|
||||
'meta:Guide',
|
||||
'meta:Resource',
|
||||
'meta:Notes',
|
||||
'meta:Example',
|
||||
|
||||
// Book type
|
||||
"type:Campaign",
|
||||
"type:Campaign Setting",
|
||||
"type:Adventure",
|
||||
"type:One-Shot",
|
||||
"type:Setting",
|
||||
"type:World",
|
||||
"type:Lore",
|
||||
"type:History",
|
||||
"type:Dungeon Master",
|
||||
"type:Encounter Pack",
|
||||
"type:Encounter",
|
||||
"type:Session Notes",
|
||||
"type:reference",
|
||||
"type:Handbook",
|
||||
"type:Manual",
|
||||
"type:Manuals",
|
||||
"type:Compendium",
|
||||
"type:Bestiary",
|
||||
'type:Campaign',
|
||||
'type:Campaign Setting',
|
||||
'type:Adventure',
|
||||
'type:One-Shot',
|
||||
'type:Setting',
|
||||
'type:World',
|
||||
'type:Lore',
|
||||
'type:History',
|
||||
'type:Dungeon Master',
|
||||
'type:Encounter Pack',
|
||||
'type:Encounter',
|
||||
'type:Session Notes',
|
||||
'type:reference',
|
||||
'type:Handbook',
|
||||
'type:Manual',
|
||||
'type:Manuals',
|
||||
'type:Compendium',
|
||||
'type:Bestiary',
|
||||
|
||||
// ###################################### RPG Keywords
|
||||
|
||||
// Classes / Subclasses / Archetypes
|
||||
"Class",
|
||||
"Subclass",
|
||||
"Archetype",
|
||||
"Martial",
|
||||
"Half-Caster",
|
||||
"Full Caster",
|
||||
"Artificer",
|
||||
"Barbarian",
|
||||
"Bard",
|
||||
"Cleric",
|
||||
"Druid",
|
||||
"Fighter",
|
||||
"Monk",
|
||||
"Paladin",
|
||||
"Rogue",
|
||||
"Sorcerer",
|
||||
"Warlock",
|
||||
"Wizard",
|
||||
'Class',
|
||||
'Subclass',
|
||||
'Archetype',
|
||||
'Martial',
|
||||
'Half-Caster',
|
||||
'Full Caster',
|
||||
'Artificer',
|
||||
'Barbarian',
|
||||
'Bard',
|
||||
'Cleric',
|
||||
'Druid',
|
||||
'Fighter',
|
||||
'Monk',
|
||||
'Paladin',
|
||||
'Rogue',
|
||||
'Sorcerer',
|
||||
'Warlock',
|
||||
'Wizard',
|
||||
|
||||
// Races / Species / Lineages
|
||||
"Race",
|
||||
"Ancestry",
|
||||
"Lineage",
|
||||
"Aasimar",
|
||||
"Beastfolk",
|
||||
"Dragonborn",
|
||||
"Dwarf",
|
||||
"Elf",
|
||||
"Goblin",
|
||||
"Half-Elf",
|
||||
"Half-Orc",
|
||||
"Human",
|
||||
"Kobold",
|
||||
"Lizardfolk",
|
||||
"Lycan",
|
||||
"Orc",
|
||||
"Tiefling",
|
||||
"Vampire",
|
||||
"Yuan-Ti",
|
||||
'Race',
|
||||
'Ancestry',
|
||||
'Lineage',
|
||||
'Aasimar',
|
||||
'Beastfolk',
|
||||
'Dragonborn',
|
||||
'Dwarf',
|
||||
'Elf',
|
||||
'Goblin',
|
||||
'Half-Elf',
|
||||
'Half-Orc',
|
||||
'Human',
|
||||
'Kobold',
|
||||
'Lizardfolk',
|
||||
'Lycan',
|
||||
'Orc',
|
||||
'Tiefling',
|
||||
'Vampire',
|
||||
'Yuan-Ti',
|
||||
|
||||
// Magic / Spells / Items
|
||||
"Magic",
|
||||
"Magic Item",
|
||||
"Magic Items",
|
||||
"Wondrous Item",
|
||||
"Magic Weapon",
|
||||
"Artifact",
|
||||
"Spell",
|
||||
"Spells",
|
||||
"Cantrip",
|
||||
"Cantrips",
|
||||
"Eldritch",
|
||||
"Eldritch Invocation",
|
||||
"Invocation",
|
||||
"Invocations",
|
||||
"Pact boon",
|
||||
"Pact Boon",
|
||||
"Spellcaster",
|
||||
"Spellblade",
|
||||
"Magical Tattoos",
|
||||
"Enchantment",
|
||||
"Enchanted",
|
||||
"Attunement",
|
||||
"Requires Attunement",
|
||||
"Rune",
|
||||
"Runes",
|
||||
"Wand",
|
||||
"Rod",
|
||||
"Scroll",
|
||||
"Potion",
|
||||
"Potions",
|
||||
"Item",
|
||||
"Items",
|
||||
"Bag of Holding",
|
||||
'Magic',
|
||||
'Magic Item',
|
||||
'Magic Items',
|
||||
'Wondrous Item',
|
||||
'Magic Weapon',
|
||||
'Artifact',
|
||||
'Spell',
|
||||
'Spells',
|
||||
'Cantrip',
|
||||
'Cantrips',
|
||||
'Eldritch',
|
||||
'Eldritch Invocation',
|
||||
'Invocation',
|
||||
'Invocations',
|
||||
'Pact boon',
|
||||
'Pact Boon',
|
||||
'Spellcaster',
|
||||
'Spellblade',
|
||||
'Magical Tattoos',
|
||||
'Enchantment',
|
||||
'Enchanted',
|
||||
'Attunement',
|
||||
'Requires Attunement',
|
||||
'Rune',
|
||||
'Runes',
|
||||
'Wand',
|
||||
'Rod',
|
||||
'Scroll',
|
||||
'Potion',
|
||||
'Potions',
|
||||
'Item',
|
||||
'Items',
|
||||
'Bag of Holding',
|
||||
|
||||
// Monsters / Creatures / Enemies
|
||||
"Monster",
|
||||
"Creatures",
|
||||
"Creature",
|
||||
"Beast",
|
||||
"Beasts",
|
||||
"Humanoid",
|
||||
"Undead",
|
||||
"Fiend",
|
||||
"Aberration",
|
||||
"Ooze",
|
||||
"Giant",
|
||||
"Dragon",
|
||||
"Monstrosity",
|
||||
"Demon",
|
||||
"Devil",
|
||||
"Elemental",
|
||||
"Construct",
|
||||
"Constructs",
|
||||
"Boss",
|
||||
"BBEG",
|
||||
'Monster',
|
||||
'Creatures',
|
||||
'Creature',
|
||||
'Beast',
|
||||
'Beasts',
|
||||
'Humanoid',
|
||||
'Undead',
|
||||
'Fiend',
|
||||
'Aberration',
|
||||
'Ooze',
|
||||
'Giant',
|
||||
'Dragon',
|
||||
'Monstrosity',
|
||||
'Demon',
|
||||
'Devil',
|
||||
'Elemental',
|
||||
'Construct',
|
||||
'Constructs',
|
||||
'Boss',
|
||||
'BBEG',
|
||||
|
||||
// ############################# Media / Pop Culture
|
||||
"One Piece",
|
||||
"Dragon Ball",
|
||||
"Dragon Ball Z",
|
||||
"Naruto",
|
||||
"Jujutsu Kaisen",
|
||||
"Fairy Tail",
|
||||
"Final Fantasy",
|
||||
"Kingdom Hearts",
|
||||
"Elder Scrolls",
|
||||
"Skyrim",
|
||||
"WoW",
|
||||
"World of Warcraft",
|
||||
"Marvel Comics",
|
||||
"DC Comics",
|
||||
"Pokemon",
|
||||
"League of Legends",
|
||||
"Runeterra",
|
||||
"Arcane",
|
||||
"Yu-Gi-Oh",
|
||||
"Minecraft",
|
||||
"Don't Starve",
|
||||
"Witcher",
|
||||
"Witcher 3",
|
||||
"Cyberpunk",
|
||||
"Cyberpunk 2077",
|
||||
"Fallout",
|
||||
"Divinity Original Sin 2",
|
||||
"Fullmetal Alchemist",
|
||||
"Fullmetal Alchemist Brotherhood",
|
||||
"Lobotomy Corporation",
|
||||
"Bloodborne",
|
||||
"Dragonlance",
|
||||
"Shackled City Adventure Path",
|
||||
"Baldurs Gate 3",
|
||||
"Library of Ruina",
|
||||
"Radiant Citadel",
|
||||
"Ravenloft",
|
||||
"Forgotten Realms",
|
||||
"Exandria",
|
||||
"Critical Role",
|
||||
"Star Wars",
|
||||
"SW5e",
|
||||
"Star Wars 5e",
|
||||
'One Piece',
|
||||
'Dragon Ball',
|
||||
'Dragon Ball Z',
|
||||
'Naruto',
|
||||
'Jujutsu Kaisen',
|
||||
'Fairy Tail',
|
||||
'Final Fantasy',
|
||||
'Kingdom Hearts',
|
||||
'Elder Scrolls',
|
||||
'Skyrim',
|
||||
'WoW',
|
||||
'World of Warcraft',
|
||||
'Marvel Comics',
|
||||
'DC Comics',
|
||||
'Pokemon',
|
||||
'League of Legends',
|
||||
'Runeterra',
|
||||
'Arcane',
|
||||
'Yu-Gi-Oh',
|
||||
'Minecraft',
|
||||
'Don\'t Starve',
|
||||
'Witcher',
|
||||
'Witcher 3',
|
||||
'Cyberpunk',
|
||||
'Cyberpunk 2077',
|
||||
'Fallout',
|
||||
'Divinity Original Sin 2',
|
||||
'Fullmetal Alchemist',
|
||||
'Fullmetal Alchemist Brotherhood',
|
||||
'Lobotomy Corporation',
|
||||
'Bloodborne',
|
||||
'Dragonlance',
|
||||
'Shackled City Adventure Path',
|
||||
'Baldurs Gate 3',
|
||||
'Library of Ruina',
|
||||
'Radiant Citadel',
|
||||
'Ravenloft',
|
||||
'Forgotten Realms',
|
||||
'Exandria',
|
||||
'Critical Role',
|
||||
'Star Wars',
|
||||
'SW5e',
|
||||
'Star Wars 5e',
|
||||
];
|
||||
|
||||
// substrings to be normalized to the first value on the array
|
||||
export const canonizationList = [
|
||||
['5e 2024', '5.5e', '5e\'24', '5.24', '5e24', '5.5'],
|
||||
['5e', '5th Edition'],
|
||||
['Dungeons & Dragons', 'Dungeons and Dragons', 'Dungeons n dragons'],
|
||||
['D&D', 'DnD', 'dnd', 'Dnd', 'dnD', 'd&d', 'd&D', 'D&d'],
|
||||
['P2e', 'p2e', 'P2E', 'Pathfinder 2e'],
|
||||
];
|
||||
@@ -1,71 +1,62 @@
|
||||
import "./tagInput.less";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Combobox from "../../../components/combobox.jsx";
|
||||
import './tagInput.less';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Combobox from '../../../components/combobox.jsx';
|
||||
|
||||
import tagSuggestionList from "./curatedTagSuggestionList.js";
|
||||
import { tagSuggestionList, canonizationList } 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(
|
||||
values.map((value) => ({
|
||||
values.map((value)=>({
|
||||
value,
|
||||
editing: false,
|
||||
draft: "",
|
||||
editing : false,
|
||||
draft : '',
|
||||
})),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
useEffect(()=>{
|
||||
const incoming = values || [];
|
||||
const current = tagList.map((t) => t.value);
|
||||
const current = tagList.map((t)=>t.value);
|
||||
|
||||
const changed = incoming.length !== current.length || incoming.some((v, i) => v !== current[i]);
|
||||
const changed = incoming.length !== current.length || incoming.some((v, i)=>v !== current[i]);
|
||||
|
||||
if (changed) {
|
||||
if(changed) {
|
||||
setTagList(
|
||||
incoming.map((value) => ({
|
||||
incoming.map((value)=>({
|
||||
value,
|
||||
editing: false,
|
||||
editing : false,
|
||||
})),
|
||||
);
|
||||
}
|
||||
}, [values]);
|
||||
|
||||
useEffect(() => {
|
||||
useEffect(()=>{
|
||||
onChange?.({
|
||||
target: { value: tagList.map((t) => t.value) },
|
||||
target : { value: tagList.map((t)=>t.value) },
|
||||
});
|
||||
}, [tagList]);
|
||||
|
||||
// substrings to be normalized to the first value on the array
|
||||
const duplicateGroups = [
|
||||
["5e 2024", "5.5e", "5e'24", "5.24", "5e24", "5.5"],
|
||||
["5e", "5th Edition"],
|
||||
["Dungeons & Dragons", "Dungeons and Dragons", "Dungeons n dragons"],
|
||||
["D&D", "DnD", "dnd", "Dnd", "dnD", "d&d", "d&D", "D&d"],
|
||||
["P2e", "p2e", "P2E", "Pathfinder 2e"],
|
||||
];
|
||||
|
||||
const normalizeValue = (input) => {
|
||||
const normalizeValue = (input)=>{
|
||||
const lowerInput = input.toLowerCase();
|
||||
let normalizedTag = input;
|
||||
|
||||
for (const group of duplicateGroups) {
|
||||
for (const group of canonizationList) {
|
||||
for (const tag of group) {
|
||||
if (!tag) continue;
|
||||
if(!tag) continue;
|
||||
|
||||
const index = lowerInput.indexOf(tag.toLowerCase());
|
||||
if (index !== -1) {
|
||||
if(index !== -1) {
|
||||
normalizedTag = input.slice(0, index) + group[0] + input.slice(index + tag.length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (normalizedTag.includes(":")) {
|
||||
const [rawType, rawValue = ""] = normalizedTag.split(":");
|
||||
if(normalizedTag.includes(':')) {
|
||||
const [rawType, rawValue = ''] = normalizedTag.split(':');
|
||||
const tagType = rawType.trim().toLowerCase();
|
||||
const tagValue = rawValue.trim();
|
||||
|
||||
if (tagValue.length > 0) {
|
||||
if(tagValue.length > 0) {
|
||||
normalizedTag = `${tagType}:${tagValue[0].toUpperCase()}${tagValue.slice(1)}`;
|
||||
}
|
||||
//trims spaces around colon and capitalizes the first word after the colon
|
||||
@@ -75,56 +66,56 @@ const TagInput = ({tooltip, label, valuePatterns, values = [], unique = true, pl
|
||||
return normalizedTag;
|
||||
};
|
||||
|
||||
const submitTag = (newValue, index = null) => {
|
||||
const submitTag = (newValue, index = null)=>{
|
||||
const trimmed = newValue?.trim();
|
||||
if (!trimmed) return;
|
||||
if (!valuePatterns.test(trimmed)) return;
|
||||
if(!trimmed) return;
|
||||
if(!valuePatterns.test(trimmed)) return;
|
||||
|
||||
const normalizedTag = normalizeValue(trimmed);
|
||||
|
||||
setTagList((prev) => {
|
||||
const existsIndex = prev.findIndex((t) => t.value.toLowerCase() === normalizedTag.toLowerCase());
|
||||
if (unique && existsIndex !== -1) return prev;
|
||||
if (index !== null) {
|
||||
return prev.map((t, i) => (i === index ? { ...t, value: normalizedTag, editing: false } : t));
|
||||
setTagList((prev)=>{
|
||||
const existsIndex = prev.findIndex((t)=>t.value.toLowerCase() === normalizedTag.toLowerCase());
|
||||
if(unique && existsIndex !== -1) return prev;
|
||||
if(index !== null) {
|
||||
return prev.map((t, i)=>(i === index ? { ...t, value: normalizedTag, editing: false } : t));
|
||||
}
|
||||
|
||||
return [...prev, { value: normalizedTag, editing: false }];
|
||||
});
|
||||
};
|
||||
|
||||
const removeTag = (index) => {
|
||||
setTagList((prev) => prev.filter((_, i) => i !== index));
|
||||
const removeTag = (index)=>{
|
||||
setTagList((prev)=>prev.filter((_, i)=>i !== index));
|
||||
};
|
||||
|
||||
const editTag = (index) => {
|
||||
setTagList((prev) => prev.map((t, i) => (i === index ? { ...t, editing: true, draft: t.value } : t)));
|
||||
const editTag = (index)=>{
|
||||
setTagList((prev)=>prev.map((t, i)=>(i === index ? { ...t, editing: true, draft: t.value } : t)));
|
||||
};
|
||||
|
||||
const stopEditing = (index) => {
|
||||
setTagList((prev) => prev.map((t, i) => (i === index ? { ...t, editing: false, draft: "" } : t)));
|
||||
const stopEditing = (index)=>{
|
||||
setTagList((prev)=>prev.map((t, i)=>(i === index ? { ...t, editing: false, draft: '' } : t)));
|
||||
};
|
||||
|
||||
const suggestionOptions = tagSuggestionList.map((tag) => {
|
||||
const tagType = tag.split(":");
|
||||
const suggestionOptions = tagSuggestionList.map((tag)=>{
|
||||
const tagType = tag.split(':');
|
||||
|
||||
let classes = "item";
|
||||
let classes = 'item';
|
||||
switch (tagType[0]) {
|
||||
case "type":
|
||||
classes = "item type";
|
||||
break;
|
||||
case "group":
|
||||
classes = "item group";
|
||||
break;
|
||||
case "meta":
|
||||
classes = "item meta";
|
||||
break;
|
||||
case "system":
|
||||
classes = "item system";
|
||||
break;
|
||||
default:
|
||||
classes = "item";
|
||||
break;
|
||||
case 'type':
|
||||
classes = 'item type';
|
||||
break;
|
||||
case 'group':
|
||||
classes = 'item group';
|
||||
break;
|
||||
case 'meta':
|
||||
classes = 'item meta';
|
||||
break;
|
||||
case 'system':
|
||||
classes = 'item system';
|
||||
break;
|
||||
default:
|
||||
classes = 'item';
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -135,73 +126,69 @@ const TagInput = ({tooltip, label, valuePatterns, values = [], unique = true, pl
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="tagInputWrap">
|
||||
<div className='tagInputWrap'>
|
||||
<Combobox
|
||||
trigger="click"
|
||||
className="tagInput-dropdown"
|
||||
default=""
|
||||
trigger='click'
|
||||
className='tagInput-dropdown'
|
||||
default=''
|
||||
placeholder={placeholder}
|
||||
options={label === "tags" ? suggestionOptions : []}
|
||||
options={label === 'tags' ? suggestionOptions : []}
|
||||
tooltip={tooltip}
|
||||
autoSuggest={
|
||||
label === "tags"
|
||||
label === 'tags'
|
||||
? {
|
||||
suggestMethod: "startsWith",
|
||||
clearAutoSuggestOnClick: true,
|
||||
filterOn: ["value", "title"],
|
||||
}
|
||||
: { suggestMethod: "includes", clearAutoSuggestOnClick: true, filterOn: [] }
|
||||
suggestMethod : 'startsWith',
|
||||
clearAutoSuggestOnClick : true,
|
||||
filterOn : ['value', 'title'],
|
||||
}
|
||||
: { suggestMethod: 'includes', clearAutoSuggestOnClick: true, filterOn: [] }
|
||||
}
|
||||
valuePatterns={valuePatterns.source}
|
||||
onSelect={(value) => submitTag(value)}
|
||||
onEntry={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
onSelect={(value)=>submitTag(value)}
|
||||
onEntry={(e)=>{
|
||||
if(e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
submitTag(e.target.value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ul className="list">
|
||||
{tagList.map((t, i) =>
|
||||
t.editing ? (
|
||||
<input
|
||||
key={i}
|
||||
type="text"
|
||||
value={t.draft} // always use draft
|
||||
pattern={valuePatterns.source}
|
||||
onChange={(e) =>
|
||||
setTagList((prev) =>
|
||||
prev.map((tag, idx) => (idx === i ? { ...tag, draft: e.target.value } : tag)),
|
||||
)
|
||||
<ul className='list'>
|
||||
{tagList.map((t, i)=>t.editing ? (
|
||||
<input
|
||||
key={i}
|
||||
type='text'
|
||||
value={t.draft} // always use draft
|
||||
pattern={valuePatterns.source}
|
||||
onChange={(e)=>setTagList((prev)=>prev.map((tag, idx)=>(idx === i ? { ...tag, draft: e.target.value } : tag)),
|
||||
)
|
||||
}
|
||||
onKeyDown={(e)=>{
|
||||
if(e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
submitTag(t.draft, i); // submit draft
|
||||
setTagList((prev)=>prev.map((tag, idx)=>(idx === i ? { ...tag, draft: '' } : tag)),
|
||||
);
|
||||
}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
submitTag(t.draft, i); // submit draft
|
||||
setTagList((prev) =>
|
||||
prev.map((tag, idx) => (idx === i ? { ...tag, draft: "" } : tag)),
|
||||
);
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
stopEditing(i);
|
||||
e.target.blur();
|
||||
}
|
||||
}}
|
||||
autoFocus
|
||||
/>
|
||||
) : (
|
||||
<li key={i} className="tag" onClick={() => editTag(i)}>
|
||||
{t.value}
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
removeTag(i);
|
||||
}}>
|
||||
<i className="fa fa-times fa-fw" />
|
||||
</button>
|
||||
</li>
|
||||
),
|
||||
if(e.key === 'Escape') {
|
||||
stopEditing(i);
|
||||
e.target.blur();
|
||||
}
|
||||
}}
|
||||
autoFocus
|
||||
/>
|
||||
) : (
|
||||
<li key={i} className='tag' onClick={()=>editTag(i)}>
|
||||
{t.value}
|
||||
<button
|
||||
type='button'
|
||||
onClick={(e)=>{
|
||||
e.stopPropagation();
|
||||
removeTag(i);
|
||||
}}>
|
||||
<i className='fa fa-times fa-fw' />
|
||||
</button>
|
||||
</li>
|
||||
),
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,7 @@
|
||||
|
||||
import 'core-js/es/string/to-well-formed.js'; //Polyfill for older browsers
|
||||
import 'core-js/es/string/to-well-formed.js'; // Polyfill for older browsers
|
||||
import './homebrew.less';
|
||||
import React from 'react';
|
||||
import { StaticRouter as Router, Route, Routes, useParams, useSearchParams } from 'react-router';
|
||||
import { BrowserRouter as Router, Routes, Route, useParams, useSearchParams } from 'react-router';
|
||||
|
||||
import { updateLocalStorage } from './utils/updateLocalStorage/updateLocalStorageKeys.js';
|
||||
|
||||
@@ -41,24 +40,34 @@ const Homebrew = (props)=>{
|
||||
brews
|
||||
} = props;
|
||||
|
||||
global.account = account;
|
||||
global.version = version;
|
||||
global.config = config;
|
||||
|
||||
const backgroundObject = ()=>{
|
||||
if(global.config.deployment || (config.local && config.development)){
|
||||
const bgText = global.config.deployment || 'Local';
|
||||
return {
|
||||
backgroundImage : `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='100px' width='200px'><text x='0' y='15' fill='%23fff7' font-size='20'>${bgText}</text></svg>")`
|
||||
};
|
||||
if(config?.deployment || (config?.local && config?.development)) {
|
||||
const bgText = config?.deployment || 'Local';
|
||||
return {
|
||||
backgroundImage : `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='100px' width='200px'><text x='0' y='15' fill='%23fff7' font-size='20'>${bgText}</text></svg>")`
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
updateLocalStorage();
|
||||
|
||||
if(brew.pureError) {
|
||||
return (
|
||||
<Router>
|
||||
<div className={`homebrew${(config?.deployment || config?.local) ? ' deployment' : ''}`} style={backgroundObject()}>
|
||||
<Routes>
|
||||
<Route path={brew.originalUrl} element={<WithRoute el={ErrorPage} brew={brew} />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Router location={url}>
|
||||
<div className={`homebrew${(config.deployment || config.local) ? ' deployment' : ''}`} style={backgroundObject()}>
|
||||
<Router>
|
||||
<div className={`homebrew${(config?.deployment || config?.local) ? ' deployment' : ''}`} style={backgroundObject()}>
|
||||
<Routes>
|
||||
<Route path='/edit/:id' element={<WithRoute el={EditPage} brew={brew} userThemes={userThemes}/>} />
|
||||
<Route path='/share/:id' element={<WithRoute el={SharePage} brew={brew} share={true} />} />
|
||||
@@ -81,4 +90,4 @@ const Homebrew = (props)=>{
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = Homebrew;
|
||||
export default Homebrew;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import 'naturalcrit/styles/core.less';
|
||||
@import '@sharedStyles/core.less';
|
||||
.homebrew {
|
||||
height : 100%;
|
||||
background-color:@steel;
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import Homebrew from './homebrew.jsx';
|
||||
|
||||
const props = window.__INITIAL_PROPS__ || {};
|
||||
|
||||
createRoot(document.getElementById('reactRoot')).render(<Homebrew {...props} />);
|
||||
@@ -97,7 +97,7 @@ const Account = createReactClass({
|
||||
|
||||
// Logged out
|
||||
// LOCAL ONLY
|
||||
if(global.config.local) {
|
||||
if(global.config?.local) {
|
||||
return <Nav.item color='teal' icon='fas fa-sign-in-alt' onClick={this.localLogin}>
|
||||
login
|
||||
</Nav.item>;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@import '@sharedStyles/core.less';
|
||||
|
||||
.navItem.error {
|
||||
position : relative;
|
||||
background-color : @red;
|
||||
|
||||
@@ -46,11 +46,6 @@ const MetadataNav = createReactClass({
|
||||
</>;
|
||||
},
|
||||
|
||||
getSystems : function(){
|
||||
if(!this.props.brew.systems || this.props.brew.systems.length == 0) return 'No systems';
|
||||
return this.props.brew.systems.join(', ');
|
||||
},
|
||||
|
||||
renderMetaWindow : function(){
|
||||
return <div className={`window ${this.state.showMetaWindow ? 'active' : 'inactive'}`}>
|
||||
<div className='row'>
|
||||
@@ -65,10 +60,6 @@ const MetadataNav = createReactClass({
|
||||
<h4>Tags</h4>
|
||||
<p>{this.getTags()}</p>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<h4>Systems</h4>
|
||||
<p>{this.getSystems()}</p>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<h4>Updated</h4>
|
||||
<p>{Moment(this.props.brew.updatedAt).fromNow()}</p>
|
||||
|
||||
@@ -8,16 +8,10 @@ import PatreonNavItem from './patreon.navitem.jsx';
|
||||
const Navbar = createReactClass({
|
||||
displayName : 'Navbar',
|
||||
getInitialState : function() {
|
||||
return {
|
||||
//showNonChromeWarning : false,
|
||||
ver : '0.0.0'
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState : function() {
|
||||
return {
|
||||
ver : global.version
|
||||
};
|
||||
return {
|
||||
// showNonChromeWarning: false, // uncomment if needed
|
||||
ver : global.version || '0.0.0'
|
||||
};
|
||||
},
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import 'naturalcrit/styles/colors.less';
|
||||
@import '@sharedStyles/core.less';
|
||||
|
||||
@navbarHeight : 28px;
|
||||
@viewerToolsHeight : 32px;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import Nav from './nav.jsx';
|
||||
import { splitTextStyleAndMetadata } from '../../../shared/helpers.js';
|
||||
import { splitTextStyleAndMetadata } from '@shared/helpers.js';
|
||||
|
||||
const BREWKEY = 'HB_newPage_content';
|
||||
const STYLEKEY = 'HB_newPage_style';
|
||||
@@ -24,7 +24,7 @@ const NewBrew = ()=>{
|
||||
localStorage.setItem(BREWKEY, newBrew.text);
|
||||
localStorage.setItem(STYLEKEY, newBrew.style);
|
||||
localStorage.setItem(METAKEY, JSON.stringify(
|
||||
_.pick(newBrew, ['title', 'description', 'tags', 'systems', 'renderer', 'theme', 'lang'])
|
||||
_.pick(newBrew, ['title', 'description', 'tags', 'renderer', 'theme', 'lang'])
|
||||
));
|
||||
window.location.href = '/new';
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import Nav from './nav.jsx';
|
||||
import { printCurrentBrew, scrapeBrewHTML, scrapeBrewZip } from '../../../shared/helpers.js';
|
||||
import { printCurrentBrew, scrapeBrewHTML, scrapeBrewZip } from '@shared/helpers.js';
|
||||
|
||||
export default function(props){
|
||||
return <Nav.dropdown>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@import '@sharedStyles/core.less';
|
||||
|
||||
.brewItem {
|
||||
position : relative;
|
||||
|
||||
@@ -4,34 +4,35 @@ import './editPage.less';
|
||||
// Common imports
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import request from '../../utils/request-middleware.js';
|
||||
import Markdown from '../../../../shared/markdown.js';
|
||||
import Markdown from '@shared/markdown.js';
|
||||
import _ from 'lodash';
|
||||
|
||||
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 Editor from '../../editor/editor.jsx';
|
||||
import BrewRenderer from '../../brewRenderer/brewRenderer.jsx';
|
||||
|
||||
import Nav from '../../navbar/nav.jsx';
|
||||
import Navbar from '../../navbar/navbar.jsx';
|
||||
import NewBrewItem from '../../navbar/newbrew.navitem.jsx';
|
||||
import AccountNavItem from '../../navbar/account.navitem.jsx';
|
||||
import ErrorNavItem from '../../navbar/error-navitem.jsx';
|
||||
import HelpNavItem from '../../navbar/help.navitem.jsx';
|
||||
import VaultNavItem from '../../navbar/vault.navitem.jsx';
|
||||
import PrintNavItem from '../../navbar/print.navitem.jsx';
|
||||
import RecentNavItems from '../../navbar/recent.navitem.jsx';
|
||||
import Nav from '@navbar/nav.jsx';
|
||||
import Navbar from '@navbar/navbar.jsx';
|
||||
import NewBrewItem from '@navbar/newbrew.navitem.jsx';
|
||||
import AccountNavItem from '@navbar/account.navitem.jsx';
|
||||
import ErrorNavItem from '@navbar/error-navitem.jsx';
|
||||
import HelpNavItem from '@navbar/help.navitem.jsx';
|
||||
import VaultNavItem from '@navbar/vault.navitem.jsx';
|
||||
import PrintNavItem from '@navbar/print.navitem.jsx';
|
||||
import RecentNavItems from '@navbar/recent.navitem.jsx';
|
||||
const { both: RecentNavItem } = RecentNavItems;
|
||||
|
||||
// Page specific imports
|
||||
import { Meta } from 'vitreum/headtags';
|
||||
import Headtags from '../../../../vitreum/headtags.js';
|
||||
const Meta = Headtags.Meta;
|
||||
import { md5 } from 'hash-wasm';
|
||||
import { gzipSync, strToU8 } from 'fflate';
|
||||
import { makePatches, stringifyPatches } from '@sanity/diff-match-patch';
|
||||
|
||||
import ShareNavItem from '../../navbar/share.navitem.jsx';
|
||||
import ShareNavItem from '@navbar/share.navitem.jsx';
|
||||
import LockNotification from './lockNotification/lockNotification.jsx';
|
||||
import { updateHistory, versionHistoryGarbageCollection } from '../../utils/versionHistory.js';
|
||||
import googleDriveIcon from '../../googleDrive.svg';
|
||||
@@ -56,28 +57,28 @@ const EditPage = (props)=>{
|
||||
...props
|
||||
};
|
||||
|
||||
const [currentBrew , setCurrentBrew ] = useState(props.brew);
|
||||
const [isSaving , setIsSaving ] = useState(false);
|
||||
const [lastSavedTime , setLastSavedTime ] = useState(new Date());
|
||||
const [saveGoogle , setSaveGoogle ] = useState(!!props.brew.googleId);
|
||||
const [error , setError ] = useState(null);
|
||||
const [HTMLErrors , setHTMLErrors ] = useState(Markdown.validate(props.brew.text));
|
||||
const [currentEditorViewPageNum , setCurrentEditorViewPageNum ] = useState(1);
|
||||
const [currentBrew, setCurrentBrew] = useState(props.brew);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [lastSavedTime, setLastSavedTime] = useState(new Date());
|
||||
const [saveGoogle, setSaveGoogle] = useState(!!props.brew.googleId);
|
||||
const [error, setError] = useState(null);
|
||||
const [HTMLErrors, setHTMLErrors] = useState(Markdown.validate(props.brew.text));
|
||||
const [currentEditorViewPageNum, setCurrentEditorViewPageNum] = useState(1);
|
||||
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
|
||||
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
|
||||
const [themeBundle , setThemeBundle ] = useState({});
|
||||
const [unsavedChanges , setUnsavedChanges ] = useState(false);
|
||||
const [alertTrashedGoogleBrew , setAlertTrashedGoogleBrew ] = useState(props.brew.trashed);
|
||||
const [alertLoginToTransfer , setAlertLoginToTransfer ] = useState(false);
|
||||
const [confirmGoogleTransfer , setConfirmGoogleTransfer ] = useState(false);
|
||||
const [autoSaveEnabled , setAutoSaveEnabled ] = useState(true);
|
||||
const [warnUnsavedChanges , setWarnUnsavedChanges ] = useState(true);
|
||||
const [themeBundle, setThemeBundle] = useState({});
|
||||
const [unsavedChanges, setUnsavedChanges] = useState(false);
|
||||
const [alertTrashedGoogleBrew, setAlertTrashedGoogleBrew] = useState(props.brew.trashed);
|
||||
const [alertLoginToTransfer, setAlertLoginToTransfer] = useState(false);
|
||||
const [confirmGoogleTransfer, setConfirmGoogleTransfer] = useState(false);
|
||||
const [autoSaveEnabled, setAutoSaveEnabled] = useState(true);
|
||||
const [warnUnsavedChanges, setWarnUnsavedChanges] = useState(true);
|
||||
|
||||
const editorRef = useRef(null);
|
||||
const lastSavedBrew = useRef(_.cloneDeep(props.brew));
|
||||
const saveTimeout = useRef(null);
|
||||
const warnUnsavedTimeout = useRef(null);
|
||||
const trySaveRef = useRef(trySave); // CTRL+S listener lives outside React and needs ref to use trySave with latest copy of brew
|
||||
const trySaveRef = useRef(null); // CTRL+S listener lives outside React and needs ref to use trySave with latest copy of brew
|
||||
const unsavedChangesRef = useRef(unsavedChanges); // Similarly, onBeforeUnload lives outside React and needs ref to unsavedChanges
|
||||
|
||||
useEffect(()=>{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import './errorPage.less';
|
||||
import React from 'react';
|
||||
import UIPage from '../basePages/uiPage/uiPage.jsx';
|
||||
import Markdown from '../../../../shared/markdown.js';
|
||||
import Markdown from '@shared/markdown.js';
|
||||
import ErrorIndex from './errors/errorIndex.js';
|
||||
|
||||
const ErrorPage = ({ brew })=>{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
.homebrew {
|
||||
.uiPage.sitePage {
|
||||
.uiPage.sitePage:has(.errorTitle) {
|
||||
.errorTitle {
|
||||
//background-color: @orange;
|
||||
color : #D02727;
|
||||
text-align : center;
|
||||
}
|
||||
|
||||
@@ -1,33 +1,34 @@
|
||||
/* eslint-disable max-lines */
|
||||
|
||||
import './homePage.less';
|
||||
|
||||
// Common imports
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import request from '../../utils/request-middleware.js';
|
||||
import Markdown from '../../../../shared/markdown.js';
|
||||
import Markdown from '@shared/markdown.js';
|
||||
import _ from 'lodash';
|
||||
|
||||
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 Editor from '../../editor/editor.jsx';
|
||||
import BrewRenderer from '../../brewRenderer/brewRenderer.jsx';
|
||||
|
||||
import Nav from '../../navbar/nav.jsx';
|
||||
import Navbar from '../../navbar/navbar.jsx';
|
||||
import NewBrewItem from '../../navbar/newbrew.navitem.jsx';
|
||||
import AccountNavItem from '../../navbar/account.navitem.jsx';
|
||||
import ErrorNavItem from '../../navbar/error-navitem.jsx';
|
||||
import HelpNavItem from '../../navbar/help.navitem.jsx';
|
||||
import VaultNavItem from '../../navbar/vault.navitem.jsx';
|
||||
import PrintNavItem from '../../navbar/print.navitem.jsx';
|
||||
import RecentNavItems from '../../navbar/recent.navitem.jsx';
|
||||
import Nav from '@navbar/nav.jsx';
|
||||
import Navbar from '@navbar/navbar.jsx';
|
||||
import NewBrewItem from '@navbar/newbrew.navitem.jsx';
|
||||
import AccountNavItem from '@navbar/account.navitem.jsx';
|
||||
import ErrorNavItem from '@navbar/error-navitem.jsx';
|
||||
import HelpNavItem from '@navbar/help.navitem.jsx';
|
||||
import VaultNavItem from '@navbar/vault.navitem.jsx';
|
||||
import PrintNavItem from '@navbar/print.navitem.jsx';
|
||||
import RecentNavItems from '@navbar/recent.navitem.jsx';
|
||||
const { both: RecentNavItem } = RecentNavItems;
|
||||
|
||||
|
||||
// Page specific imports
|
||||
import { Meta } from 'vitreum/headtags';
|
||||
import Headtags from '@vitreum/headtags.js';
|
||||
const Meta = Headtags.Meta;
|
||||
|
||||
const BREWKEY = 'homebrewery-new';
|
||||
const STYLEKEY = 'homebrewery-new-style';
|
||||
@@ -44,16 +45,16 @@ const HomePage =(props)=>{
|
||||
...props
|
||||
};
|
||||
|
||||
const [currentBrew , setCurrentBrew] = useState(props.brew);
|
||||
const [error , setError] = useState(undefined);
|
||||
const [HTMLErrors , setHTMLErrors] = useState(Markdown.validate(props.brew.text));
|
||||
const [currentEditorViewPageNum , setCurrentEditorViewPageNum] = useState(1);
|
||||
const [currentBrew, setCurrentBrew] = useState(props.brew);
|
||||
const [error, setError] = useState(undefined);
|
||||
const [HTMLErrors, setHTMLErrors] = useState(Markdown.validate(props.brew.text));
|
||||
const [currentEditorViewPageNum, setCurrentEditorViewPageNum] = useState(1);
|
||||
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
|
||||
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
|
||||
const [themeBundle , setThemeBundle] = useState({});
|
||||
const [unsavedChanges , setUnsavedChanges] = useState(false);
|
||||
const [isSaving , setIsSaving] = useState(false);
|
||||
const [autoSaveEnabled , setAutoSaveEnable] = useState(false);
|
||||
const [themeBundle, setThemeBundle] = useState({});
|
||||
const [unsavedChanges, setUnsavedChanges] = useState(false);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [autoSaveEnabled, setAutoSaveEnable] = useState(false);
|
||||
|
||||
const editorRef = useRef(null);
|
||||
const lastSavedBrew = useRef(_.cloneDeep(props.brew));
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@import '@sharedStyles/core.less';
|
||||
|
||||
.homePage {
|
||||
position : relative;
|
||||
a.floatingNewButton {
|
||||
|
||||
@@ -4,29 +4,28 @@ import './newPage.less';
|
||||
// Common imports
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import request from '../../utils/request-middleware.js';
|
||||
import Markdown from '../../../../shared/markdown.js';
|
||||
import Markdown from '@shared/markdown.js';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
|
||||
import { printCurrentBrew, fetchThemeBundle, splitTextStyleAndMetadata } from '../../../../shared/helpers.js';
|
||||
import { printCurrentBrew, fetchThemeBundle, splitTextStyleAndMetadata } from '@shared/helpers.js';
|
||||
|
||||
import SplitPane from '../../../components/splitPane/splitPane.jsx';
|
||||
import Editor from '../../editor/editor.jsx';
|
||||
import BrewRenderer from '../../brewRenderer/brewRenderer.jsx';
|
||||
|
||||
import Nav from '../../navbar/nav.jsx';
|
||||
import Navbar from '../../navbar/navbar.jsx';
|
||||
import NewBrewItem from '../../navbar/newbrew.navitem.jsx';
|
||||
import AccountNavItem from '../../navbar/account.navitem.jsx';
|
||||
import ErrorNavItem from '../../navbar/error-navitem.jsx';
|
||||
import HelpNavItem from '../../navbar/help.navitem.jsx';
|
||||
import VaultNavItem from '../../navbar/vault.navitem.jsx';
|
||||
import PrintNavItem from '../../navbar/print.navitem.jsx';
|
||||
import RecentNavItems from '../../navbar/recent.navitem.jsx';
|
||||
import Nav from '@navbar/nav.jsx';
|
||||
import Navbar from '@navbar/navbar.jsx';
|
||||
import NewBrewItem from '@navbar/newbrew.navitem.jsx';
|
||||
import AccountNavItem from '@navbar/account.navitem.jsx';
|
||||
import ErrorNavItem from '@navbar/error-navitem.jsx';
|
||||
import HelpNavItem from '@navbar/help.navitem.jsx';
|
||||
import VaultNavItem from '@navbar/vault.navitem.jsx';
|
||||
import PrintNavItem from '@navbar/print.navitem.jsx';
|
||||
import RecentNavItems from '@navbar/recent.navitem.jsx';
|
||||
const { both: RecentNavItem } = RecentNavItems;
|
||||
|
||||
// Page specific imports
|
||||
import { Meta } from 'vitreum/headtags';
|
||||
|
||||
const BREWKEY = 'HB_newPage_content';
|
||||
const STYLEKEY = 'HB_newPage_style';
|
||||
@@ -43,23 +42,23 @@ const NewPage = (props)=>{
|
||||
...props
|
||||
};
|
||||
|
||||
const [currentBrew , setCurrentBrew ] = useState(props.brew);
|
||||
const [isSaving , setIsSaving ] = useState(false);
|
||||
const [saveGoogle , setSaveGoogle ] = useState(global.account?.googleId ? true : false);
|
||||
const [error , setError ] = useState(null);
|
||||
const [HTMLErrors , setHTMLErrors ] = useState(Markdown.validate(props.brew.text));
|
||||
const [currentEditorViewPageNum , setCurrentEditorViewPageNum ] = useState(1);
|
||||
const [currentBrew, setCurrentBrew] = useState(props.brew);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [saveGoogle, setSaveGoogle] = useState(global.account?.googleId ? true : false);
|
||||
const [error, setError] = useState(null);
|
||||
const [HTMLErrors, setHTMLErrors] = useState(Markdown.validate(props.brew.text));
|
||||
const [currentEditorViewPageNum, setCurrentEditorViewPageNum] = useState(1);
|
||||
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
|
||||
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
|
||||
const [themeBundle , setThemeBundle ] = useState({});
|
||||
const [unsavedChanges , setUnsavedChanges ] = useState(false);
|
||||
const [autoSaveEnabled , setAutoSaveEnabled ] = useState(false);
|
||||
const [themeBundle, setThemeBundle] = useState({});
|
||||
const [unsavedChanges, setUnsavedChanges] = useState(false);
|
||||
const [autoSaveEnabled, setAutoSaveEnabled] = useState(false);
|
||||
|
||||
const editorRef = useRef(null);
|
||||
const lastSavedBrew = useRef(_.cloneDeep(props.brew));
|
||||
// const saveTimeout = useRef(null);
|
||||
// const warnUnsavedTimeout = useRef(null);
|
||||
const trySaveRef = useRef(trySave); // CTRL+S listener lives outside React and needs ref to use trySave with latest copy of brew
|
||||
const trySaveRef = useRef(null); // CTRL+S listener lives outside React and needs ref to use trySave with latest copy of brew
|
||||
const unsavedChangesRef = useRef(unsavedChanges); // Similarly, onBeforeUnload lives outside React and needs ref to unsavedChanges
|
||||
|
||||
useEffect(()=>{
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@import '@sharedStyles/colors.less';
|
||||
|
||||
.newPage {
|
||||
.navItem.save {
|
||||
background-color : @orange;
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import './sharePage.less';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { Meta } from 'vitreum/headtags';
|
||||
import Headtags from '../../../../vitreum/headtags.js';
|
||||
const Meta = Headtags.Meta;
|
||||
|
||||
import Nav from '../../navbar/nav.jsx';
|
||||
import Navbar from '../../navbar/navbar.jsx';
|
||||
import MetadataNav from '../../navbar/metadata.navitem.jsx';
|
||||
import PrintNavItem from '../../navbar/print.navitem.jsx';
|
||||
import RecentNavItems from '../../navbar/recent.navitem.jsx';
|
||||
import Nav from '@navbar/nav.jsx';
|
||||
import Navbar from '@navbar/navbar.jsx';
|
||||
import MetadataNav from '@navbar/metadata.navitem.jsx';
|
||||
import PrintNavItem from '@navbar/print.navitem.jsx';
|
||||
import RecentNavItems from '@navbar/recent.navitem.jsx';
|
||||
const { both: RecentNavItem } = RecentNavItems;
|
||||
import Account from '../../navbar/account.navitem.jsx';
|
||||
import Account from '@navbar/account.navitem.jsx';
|
||||
import BrewRenderer from '../../brewRenderer/brewRenderer.jsx';
|
||||
|
||||
import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js';
|
||||
import { printCurrentBrew, fetchThemeBundle } from '../../../../shared/helpers.js';
|
||||
import { printCurrentBrew, fetchThemeBundle } from '@shared/helpers.js';
|
||||
|
||||
const SharePage = (props)=>{
|
||||
const { brew = DEFAULT_BREW_LOAD, disableMeta = false, share = true } = props;
|
||||
|
||||
@@ -3,15 +3,15 @@ import _ from 'lodash';
|
||||
|
||||
import ListPage from '../basePages/listPage/listPage.jsx';
|
||||
|
||||
import Nav from '../../navbar/nav.jsx';
|
||||
import Navbar from '../../navbar/navbar.jsx';
|
||||
import RecentNavItems from '../../navbar/recent.navitem.jsx';
|
||||
import Nav from '@navbar/nav.jsx';
|
||||
import Navbar from '@navbar/navbar.jsx';
|
||||
import RecentNavItems from '@navbar/recent.navitem.jsx';
|
||||
const { both: RecentNavItem } = RecentNavItems;
|
||||
import Account from '../../navbar/account.navitem.jsx';
|
||||
import NewBrew from '../../navbar/newbrew.navitem.jsx';
|
||||
import HelpNavItem from '../../navbar/help.navitem.jsx';
|
||||
import ErrorNavItem from '../../navbar/error-navitem.jsx';
|
||||
import VaultNavitem from '../../navbar/vault.navitem.jsx';
|
||||
import Account from '@navbar/account.navitem.jsx';
|
||||
import NewBrew from '@navbar/newbrew.navitem.jsx';
|
||||
import HelpNavItem from '@navbar/help.navitem.jsx';
|
||||
import ErrorNavItem from '@navbar/error-navitem.jsx';
|
||||
import VaultNavitem from '@navbar/vault.navitem.jsx';
|
||||
|
||||
const UserPage = (props)=>{
|
||||
props = {
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
import './vaultPage.less';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
|
||||
import Nav from '../../navbar/nav.jsx';
|
||||
import Navbar from '../../navbar/navbar.jsx';
|
||||
import RecentNavItems from '../../navbar/recent.navitem.jsx';
|
||||
import Nav from '@navbar/nav.jsx';
|
||||
import Navbar from '@navbar/navbar.jsx';
|
||||
import RecentNavItems from '@navbar/recent.navitem.jsx';
|
||||
const { both: RecentNavItem } = RecentNavItems;
|
||||
import Account from '../../navbar/account.navitem.jsx';
|
||||
import NewBrew from '../../navbar/newbrew.navitem.jsx';
|
||||
import HelpNavItem from '../../navbar/help.navitem.jsx';
|
||||
import Account from '@navbar/account.navitem.jsx';
|
||||
import NewBrew from '@navbar/newbrew.navitem.jsx';
|
||||
import HelpNavItem from '@navbar/help.navitem.jsx';
|
||||
import BrewItem from '../basePages/listPage/brewItem/brewItem.jsx';
|
||||
import SplitPane from '../../../components/splitPane/splitPane.jsx';
|
||||
import ErrorIndex from '../errorPage/errors/errorIndex.js';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@import '@sharedStyles/core.less';
|
||||
|
||||
.vaultPage {
|
||||
height : 100%;
|
||||
overflow-y : hidden;
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
const template = async function(name, title='', props = {}){
|
||||
const ogTags = [];
|
||||
const ogMeta = props.ogMeta ?? {};
|
||||
Object.entries(ogMeta).forEach(([key, value])=>{
|
||||
if(!value) return;
|
||||
const tag = `<meta property="og:${key}" content="${value}">`;
|
||||
ogTags.push(tag);
|
||||
});
|
||||
const ogMetaTags = ogTags.join('\n');
|
||||
|
||||
const ssrModule = await import(`../build/${name}/ssr.cjs`);
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, height=device-height, interactive-widget=resizes-visual" />
|
||||
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
|
||||
<link href=${`/${name}/bundle.css`} type="text/css" rel='stylesheet' />
|
||||
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon" />
|
||||
${ogMetaTags}
|
||||
<meta name="twitter:card" content="summary">
|
||||
<title>${title.length ? `${title} - The Homebrewery`: 'The Homebrewery - NaturalCrit'}</title>
|
||||
</head>
|
||||
<body>
|
||||
<main id="reactRoot">${ssrModule.default(props)}</main>
|
||||
<script src=${`/${name}/bundle.js`}></script>
|
||||
<script>start_app(${JSON.stringify(props)})</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
};
|
||||
|
||||
export default template;
|
||||
Reference in New Issue
Block a user