diff --git a/.circleci/config.yml b/.circleci/config.yml index 5effc0bb2..cc6bd8d82 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -82,6 +82,9 @@ jobs: - run: name: Test - HTML sanitization command: npm run test:safehtml + - run: + name: Test - Helpers + command: npm run test:helpers - run: name: Test - Coverage command: npm run test:coverage diff --git a/changelog.md b/changelog.md index 136f6346a..1c5a9b714 100644 --- a/changelog.md +++ b/changelog.md @@ -93,30 +93,83 @@ pre { ## changelog For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery). -### Saturday 4/20/2026 - v3.22.0 - +### Monday 9/07/2026 - v3.23.0 {{taskList +##### firstmatekidd (new contributor!) +* [x] Add nav buttons to tab order for accesibility +* [x] Add title to brewRenderer iframe for accessibility +* [x] Add aria labels to toolbars + +Partially fixes issue [#3032](https://github.com/naturalcrit/homebrewery/issues/3032) + +##### ReinaSweet (new contributor!) +* [x] Added security to prevent scripts running in brews + +##### Gazook89 +* [x] Lazy load brew images for faster first load +* [x] Fix snippet menus getting cut off behind divider bar + +Fixes issue [#3840](https://github.com/naturalcrit/homebrewery/issues/3840) + +##### Abquintic +* [x] Fix variables not hoisting in later pages + ##### 5e-Cleric -* [x] Major update to editor framework (Codemirror 6) -Fixes issues [#3511](https://github.com/naturalcrit/homebrewery/issues/3511), [#4590](https://github.com/naturalcrit/homebrewery/issues/4590), [#4563](https://github.com/naturalcrit/homebrewery/issues/4653), [#4655](https://github.com/naturalcrit/homebrewery/issues/4655) -* [x] Fix to Admin page tab names +* [x] Add image preview when hovering over image syntax + +Fixes issue [#2037](https://github.com/naturalcrit/homebrewery/issues/2037) + +* [x] Remember editor theme across tabs + +Fixes issue [#4858](https://github.com/naturalcrit/homebrewery/issues/4858) + +* [x] Add ability to remove co-authors if you are the owner + +Fixes part of issue [#4101](https://github.com/naturalcrit/homebrewery/issues/4101) ##### G-Ambatte -* [x] Fix white page crash on certain browsers +* [x] Fix editor panel shrinking when opening dev tools + +Fixes issue [#4866](https://github.com/naturalcrit/homebrewery/issues/4866) + +* [x] Added security to prevent scripts running in brews + +Fixes issue [#4904](https://github.com/naturalcrit/homebrewery/issues/4904) + +##### Abquintic, G-Ambatte, Gazook89, 5e-Cleric +* [x] Multiple background fixes and code cleanups }} +### Wednesday 5/13/2026 - v3.22.1 +{{taskList +##### Frederlk (new Contributor!) +* [x] Add "share current page" to {{openSans **SHARE :fas_share_nodes:**}} menu + +##### Calculuschild, 5e-Cleric +* [x] Fix brews triggering unneeded save when loaded + +##### G-Ambatte +* [x] Fix brews with `$` in text getting out of sync on save + +##### 5e-Cleric, Gazook89 +* [x] Fix various issues with Codemirror 6 + +Fixes issues [#4771](https://github.com/naturalcrit/homebrewery/issues/4771), [#4783](https://github.com/naturalcrit/homebrewery/issues/4783) +}} + +\page + ### Saturday 4/04/2026 - v3.21.0 {{taskList ##### Gazook89 -* [x] Allow custom {{openSans **:fas_table_list: SNIPPETS**}} to be inserted mid-line +* [x] Allow custom {{openSans **:fas_table_list: SNIPPETS**}} dropdown to be inserted mid-line ##### abquintic * [x] Move example snippet images out of imgur (for folks without imgur access) ##### 5e-Cleric * [x] Add auto-suggest to tag entry input box -* [x] Replace all example artwork with * [x] Added tooltips to the {{openSans :fas_circle_info: **Properties**}} menu * [x] Removed {{openSans **SYSTEMS**}} checkboxes from {{openSans :fas_circle_info: **Properties**}} menu; instead {{openSans **TAGS**}} should be used for this purpose * [x] Replace all AI-generated art with public domain art @@ -131,6 +184,7 @@ Fixes issues [#543](https://github.com/naturalcrit/homebrewery/issues/543), [#24 * [x] Multiple other backend fixes and refactors }} + ### Friday 1/11/2026 - v3.20.1 {{taskList @@ -167,7 +221,7 @@ Fixes issue [#4559](https://github.com/naturalcrit/homebrewery/issues/4559) ##### G-Ambatte * [x] Fix default save location failing on new documents -Fixes issue [#4437](https://github.com/naturalcrit/homebrewery/issues/3175) +Fixes issue [#4437](https://github.com/naturalcrit/homebrewery/issues/4437) * [x] Fix usernames with special symbols unable to open userpage Fixes issue [#807](https://github.com/naturalcrit/homebrewery/issues/807) @@ -326,7 +380,7 @@ Fixes issue [#1729](https://github.com/naturalcrit/homebrewery/issues/1729) Fixes issue [#4079](https://github.com/naturalcrit/homebrewery/issues/4079) ##### Calculuschild -* [x] `꞉꞉꞉꞉` now produces `
` instead of a `
` +* [x] `꞉꞉꞉꞉` now produces `
` instead of a `<div>` * [x] Fix typos in tables freezing the editor Fixes issue [#4059](https://github.com/naturalcrit/homebrewery/issues/4059) @@ -2396,4 +2450,4 @@ Massive changelog incoming: * Added `phb.standalone.css` plus a build system for creating it * Added page numbers and footer text -* Page accent now flips each page \ No newline at end of file +* Page accent now flips each page diff --git a/client/components/codeEditor/extensions/customKeyMaps.js b/client/components/codeEditor/extensions/customKeyMaps.js index 974b0b1e2..6fe479348 100644 --- a/client/components/codeEditor/extensions/customKeyMaps.js +++ b/client/components/codeEditor/extensions/customKeyMaps.js @@ -83,7 +83,7 @@ const insertTab = (view)=>{ changes, selection : EditorSelection.create( view.state.selection.ranges.map((range)=>EditorSelection.cursor( - mappedChanges.changes.mapPos(range.from, 1) + 2 + mappedChanges.changes.mapPos(range.from, -1) + 2 ) ) ) diff --git a/client/homebrew/brewRenderer/brewRenderer.jsx b/client/homebrew/brewRenderer/brewRenderer.jsx index 05ca73bea..618a64ecd 100644 --- a/client/homebrew/brewRenderer/brewRenderer.jsx +++ b/client/homebrew/brewRenderer/brewRenderer.jsx @@ -6,7 +6,7 @@ import React, { useState, useRef, useMemo, useEffect } from 'react'; import _ from 'lodash'; import MarkdownLegacy from '@shared/markdownLegacy.js'; -import Markdown from '@shared/markdown.js'; +import { hbfm } from 'marked-hbfm'; import ErrorBar from './errorBar/errorBar.jsx'; import ToolBar from './toolBar/toolBar.jsx'; @@ -23,7 +23,6 @@ import safeHTML from './safeHTML.js'; const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m; const PAGEBREAK_REGEX_LEGACY = /\\page(?:break)?/m; const COLUMNBREAK_REGEX_LEGACY = /\\column(:?break)?/m; -const PAGE_HEIGHT = 1056; const TOOLBAR_STATE_KEY = 'HB_renderer_toolbarState'; @@ -47,31 +46,25 @@ const BrewPage = (props)=>{ }; const pageRef = useRef(null); const cleanText = safeHTML(props.contents); + const pageNum = props.index + 1; useEffect(()=>{ if(!pageRef.current) return; - // Observer for tracking pages within the `.pages` div + // Observer for tracking which pages are at least 30% visible in the iframe const visibleObserver = new IntersectionObserver( - (entries)=>{ - entries.forEach((entry)=>{ - if(entry.isIntersecting) - props.onVisibilityChange(props.index + 1, true, false); // add page to array of visible pages. - else - props.onVisibilityChange(props.index + 1, false, false); - }); - }, + (entries)=>entries.forEach((entry)=>{ + props.onVisibilityChange(pageNum, entry.isIntersecting, false); // add/remove page from array of visible pages. + }), { threshold: .3, rootMargin: '0px 0px 0px 0px' } // detect when >30% of page is within bounds. ); // Observer for tracking the page at the center of the iframe. const centerObserver = new IntersectionObserver( - (entries)=>{ - entries.forEach((entry)=>{ - if(entry.isIntersecting) - props.onVisibilityChange(props.index + 1, true, true); // Set this page as the center page - }); - }, + (entries)=>entries.forEach((entry)=>{ + if(entry.isIntersecting) + props.onVisibilityChange(pageNum, true, true); // Set this page as the center page + }), { threshold: 0, rootMargin: '-50% 0px -50% 0px' } // Detect when the page is at the center ); @@ -92,7 +85,7 @@ const BrewPage = (props)=>{ //v=====--------------------< Brew Renderer Component >-------------------=====v// let renderedPages = []; -let pageTemplates = []; +const pageTemplates = []; let rawPages = []; const BrewRenderer = (props)=>{ @@ -100,22 +93,24 @@ const BrewRenderer = (props)=>{ text : '', style : '', renderer : 'legacy', - theme : '5ePHB', lang : '', errors : [], currentEditorCursorPageNum : 1, - currentEditorViewPageNum : 1, currentBrewRendererPageNum : 1, themeBundle : {}, onPageChange : ()=>{}, ...props }; + const pagesRef = useRef(null); + + const [visiblePages, setVisiblePages] = useState([]); + const [centerPage , setCenterPage ] = useState(1); + const [headerState , setHeaderState ] = useState(false); + const [state, setState] = useState({ - isMounted : false, - visibility : 'hidden', - visiblePages : [], - centerPage : 1 + isMounted : false, + visibility : 'hidden' }); const [displayOptions, setDisplayOptions] = useState({ @@ -133,12 +128,6 @@ const BrewRenderer = (props)=>{ toolbarState && setDisplayOptions(toolbarState); }, []); - const [headerState, setHeaderState] = useState(false); - - const mainRef = useRef(null); - const pagesRef = useRef(null); - const urlRef = useRef(''); - if(props.renderer == 'legacy') { rawPages = props.text.split(PAGEBREAK_REGEX_LEGACY); } else { @@ -146,20 +135,16 @@ const BrewRenderer = (props)=>{ } const handlePageVisibilityChange = (pageNum, isVisible, isCenter)=>{ - setState((prevState)=>{ - const updatedVisiblePages = new Set(prevState.visiblePages); - if(!isCenter) - isVisible ? updatedVisiblePages.add(pageNum) : updatedVisiblePages.delete(pageNum); - - return { - ...prevState, - visiblePages : [...updatedVisiblePages].sort((a, b)=>a - b), - centerPage : isCenter ? pageNum : prevState.centerPage - }; + setVisiblePages((prev)=>{ + const updatedVisiblePages = new Set(prev); + isVisible ? updatedVisiblePages.add(pageNum) : updatedVisiblePages.delete(pageNum); + return [...updatedVisiblePages].sort((a, b)=>a - b); }); - if(isCenter) + if(isCenter) { + setCenterPage(pageNum); props.onPageChange(pageNum); + } }; const isInView = (index)=>{ @@ -203,7 +188,7 @@ const BrewRenderer = (props)=>{ return ; } else { if(pageText.startsWith('\\page')) { - const firstLineTokens = Markdown.marked.lexer(pageText.split('\n', 1)[0])[0].tokens; + const firstLineTokens = hbfm.marked.lexer(pageText.split('\n', 1)[0])[0].tokens; const injectedTags = firstLineTokens?.find((obj)=>obj.injectedTags !== undefined)?.injectedTags; if(injectedTags) { styles = { ...styles, ...injectedTags.styles }; @@ -231,7 +216,7 @@ const BrewRenderer = (props)=>{ // DO NOT REMOVE!!! REQUIRED FOR BACKWARDS COMPATIBILITY WITH NON-UPGRADABLE VERSIONS OF CHROME. pageText += `\n\n \n\\column\n `; //Artificial column break at page end to emulate column-fill:auto (until `wide` is used, when column-fill:balance will reappear) - const html = Markdown.render(pageText, index); + const html = hbfm.render(pageText, index); return ; } @@ -327,7 +312,7 @@ const BrewRenderer = (props)=>{ }; const renderedStyle = useMemo(()=>renderStyle(), [props.style, props.themeBundle]); - renderedPages = useMemo(()=>renderPages(), [props.text, displayOptions]); + renderedPages = useMemo(()=>renderPages(), [props.text, centerPage, displayOptions]); return ( <> @@ -341,19 +326,19 @@ const BrewRenderer = (props)=>{ : null} -
+
- 0 ? state.visiblePages : [state.centerPage]} totalPages={rawPages.length} headerState={headerState} setHeaderState={setHeaderState}/> + 0 ? visiblePages : [centerPage]} totalPages={rawPages.length} headerState={headerState} setHeaderState={setHeaderState}/> {/*render in iFrame so broken code doesn't crash the site.*/} - {emitClick();}} - sandbox="allow-same-origin allow-modals allow-top-navigation" + sandbox='allow-same-origin allow-modals allow-top-navigation' >
{ return notifications.map((notification)=>(
  • {notification.title}
    -

    +

  • )); }; diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx index 5e4bd50f6..a42d13eda 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx +++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx @@ -157,11 +157,11 @@ const MetadataEditor = createReactClass({ renderPublish : function(){ if(this.props.metadata.published){ return