Merge branch 'master' into name-drop-cap-style-5024

This commit is contained in:
Víctor Losada Hernández
2026-09-23 15:00:58 +02:00
committed by GitHub
22 changed files with 1051 additions and 2778 deletions
+3
View File
@@ -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
@@ -6,7 +6,7 @@ import React, { useState, useRef, useMemo, useEffect } from 'react';
import _ from 'lodash';
import MarkdownLegacy from '@shared/markdownLegacy.js';
import { hbfm } from 'hbmarkedwrapper';
import { hbfm } from 'marked-hbfm';
import ErrorBar from './errorBar/errorBar.jsx';
import ToolBar from './toolBar/toolBar.jsx';
@@ -54,7 +54,7 @@ const BrewPage = (props)=>{
// Observer for tracking which pages are at least 30% visible in the iframe
const visibleObserver = new IntersectionObserver(
(entries)=>entries.forEach((entry)=>{
props.onVisibilityChange(pageNum, entry.isIntersecting, false); // add page to array of visible pages.
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.
);
@@ -102,8 +102,11 @@ const BrewRenderer = (props)=>{
...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,
@@ -125,10 +128,6 @@ const BrewRenderer = (props)=>{
toolbarState && setDisplayOptions(toolbarState);
}, []);
const [headerState, setHeaderState] = useState(false);
const pagesRef = useRef(null);
if(props.renderer == 'legacy') {
rawPages = props.text.split(PAGEBREAK_REGEX_LEGACY);
} else {
@@ -1,7 +1,7 @@
import './notificationPopup.less';
import React, { useEffect, useState } from 'react';
import request from '../../utils/request-middleware.js';
import { hbfm } from 'hbmarkedwrapper';
import { hbfm } from 'marked-hbfm';
import Dialog from '@components/dialog.jsx';
+1 -1
View File
@@ -4,7 +4,7 @@ import './editPage.less';
// Common imports
import React, { useState, useEffect, useRef, useEffectEvent } from 'react';
import request from '../../utils/request-middleware.js';
import { hbfm } from 'hbmarkedwrapper';
import { hbfm } from 'marked-hbfm';
import _ from 'lodash';
import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js';
@@ -1,7 +1,7 @@
import './errorPage.less';
import React from 'react';
import UIPage from '../basePages/uiPage/uiPage.jsx';
import { hbfm } from 'hbmarkedwrapper';
import { hbfm } from 'marked-hbfm';
import ErrorIndex from './errors/errorIndex.js';
const ErrorPage = ({ brew })=>{
+1 -1
View File
@@ -4,7 +4,7 @@ import './homePage.less';
// Common imports
import React, { useState, useEffect, useRef, useEffectEvent } from 'react';
import request from '../../utils/request-middleware.js';
import { hbfm } from 'hbmarkedwrapper';
import { hbfm } from 'marked-hbfm';
import _ from 'lodash';
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
+1 -1
View File
@@ -4,7 +4,7 @@ import './newPage.less';
// Common imports
import React, { useState, useEffect, useRef, useEffectEvent } from 'react';
import request from '../../utils/request-middleware.js';
import { hbfm } from 'hbmarkedwrapper';
import { hbfm } from 'marked-hbfm';
import _ from 'lodash';
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
+18 -16
View File
@@ -16,8 +16,9 @@ import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js';
import { printCurrentBrew, fetchThemeBundle } from '@shared/helpers.js';
const SharePage = (props)=>{
const { brew = DEFAULT_BREW_LOAD, disableMeta = false } = props;
const { disableMeta = false } = props;
const [currentBrew, setCurrentBrew] = useState(props.brew || DEFAULT_BREW_LOAD);
const [themeBundle, setThemeBundle] = useState({});
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
@@ -37,7 +38,7 @@ const SharePage = (props)=>{
useEffect(()=>{
document.addEventListener('keydown', handleControlKeys);
fetchThemeBundle(undefined, setThemeBundle, brew.renderer, brew.theme);
fetchThemeBundle(undefined, setThemeBundle, currentBrew.renderer, currentBrew.theme);
// listen for changes in the brew version
const eventSource = new EventSource('/stream');
@@ -45,8 +46,9 @@ const SharePage = (props)=>{
const messageData = JSON.parse(evt.data);
if(messageData.eventType == 'brewUpdated'){
if(messageData.shareId == brew.shareId && messageData.version != brew.version) {
console.log(`brew has been updated, viewing ${brew.version}, new version is ${messageData.version}`);
if(messageData.shareId == currentBrew.shareId && messageData.version != currentBrew.version) {
console.log(`brew has been updated, viewing ${currentBrew.version}, new version is ${messageData.version}`);
console.log('should fetch brew');
}
}
});
@@ -57,13 +59,13 @@ const SharePage = (props)=>{
}, []);
const processShareId = ()=>{
return brew.googleId && !brew.stubbed ? brew.googleId + brew.shareId : brew.shareId;
return currentBrew.googleId && !currentBrew.stubbed ? currentBrew.googleId + currentBrew.shareId : currentBrew.shareId;
};
const renderEditLink = ()=>{
if(!brew.editId) return null;
if(!currentBrew.editId) return null;
const editLink = brew.googleId && ! brew.stubbed ? brew.googleId + brew.editId : brew.editId;
const editLink = currentBrew.googleId && ! currentBrew.stubbed ? currentBrew.googleId + currentBrew.editId : currentBrew.editId;
return (
<Nav.item color='orange' icon='fas fa-pencil-alt' href={`/edit/${editLink}`}>
@@ -74,7 +76,7 @@ const SharePage = (props)=>{
const titleEl = (
<Nav.item className='brewTitle' style={disableMeta ? { cursor: 'default' } : {}}>
{brew.title}
{currentBrew.title}
</Nav.item>
);
@@ -83,11 +85,11 @@ const SharePage = (props)=>{
<Meta name='robots' content='noindex, nofollow' />
<Navbar>
<Nav.section className='titleSection'>
{disableMeta ? titleEl : <MetadataNav brew={brew}>{titleEl}</MetadataNav>}
{disableMeta ? titleEl : <MetadataNav brew={currentBrew}>{titleEl}</MetadataNav>}
</Nav.section>
<Nav.section>
{brew.shareId && (
{currentBrew.shareId && (
<>
<PrintNavItem />
<Nav.dropdown>
@@ -120,18 +122,18 @@ const SharePage = (props)=>{
</Nav.dropdown>
</>
)}
<RecentNavItem brew={brew} storageKey='view' />
<RecentNavItem brew={currentBrew} storageKey='view' />
<Account />
</Nav.section>
</Navbar>
<div className='content'>
<BrewRenderer
text={brew.text}
style={brew.style}
lang={brew.lang}
renderer={brew.renderer}
theme={brew.theme}
text={currentBrew.text}
style={currentBrew.style}
lang={currentBrew.lang}
renderer={currentBrew.renderer}
theme={currentBrew.theme}
themeBundle={themeBundle}
onPageChange={handleBrewRendererPageChange}
currentBrewRendererPageNum={currentBrewRendererPageNum}
+868 -2715
View File
File diff suppressed because it is too large Load Diff
+26 -25
View File
@@ -39,6 +39,7 @@
"test:emojis": "jest tests/markdown/emojis.test.js --verbose --noStackTrace",
"test:route": "jest tests/routes/static-pages.test.js --verbose",
"test:safehtml": "jest tests/html/safeHTML.test.js --verbose",
"test:helpers": "jest tests/html/helpers.test.js --verbose",
"phb": "node --experimental-require-module scripts/phb.js",
"prod": "set NODE_ENV=production && npm run build",
"postinstall": "npm run build",
@@ -86,13 +87,13 @@
]
},
"dependencies": {
"@babel/core": "^8.0.1",
"@babel/plugin-transform-runtime": "^8.0.1",
"@babel/preset-env": "^8.0.2",
"@babel/core": "^8.0.6",
"@babel/plugin-transform-runtime": "^8.0.6",
"@babel/preset-env": "^8.0.6",
"@babel/preset-react": "^8.0.1",
"@babel/runtime": "^8.0.0",
"@babel/runtime": "^8.0.5",
"@codemirror/autocomplete": "^6.20.3",
"@codemirror/commands": "^6.11.0",
"@codemirror/commands": "^6.11.1",
"@codemirror/highlight": "^0.19.8",
"@codemirror/lang-css": "^6.3.1",
"@codemirror/lang-javascript": "^6.2.5",
@@ -100,17 +101,17 @@
"@codemirror/language": "^6.12.2",
"@codemirror/language-data": "^6.5.2",
"@codemirror/search": "^6.6.0",
"@codemirror/state": "^6.6.0",
"@codemirror/view": "^6.43.9",
"@codemirror/state": "^6.7.5",
"@codemirror/view": "^6.43.12",
"@dmsnell/diff-match-patch": "^1.1.0",
"@googleapis/drive": "^21.0.0",
"@googleapis/drive": "^26.0.0",
"@lezer/highlight": "^1.2.3",
"@oddbird/css-anchor-positioning": "^0.10.2",
"@sanity/diff-match-patch": "^3.2.0",
"@vitejs/plugin-react": "^6.0.5",
"body-parser": "^2.3.0",
"classnames": "^2.5.1",
"codemirror-5-themes": "^1.5.1",
"codemirror-5-themes": "^1.5.3",
"cookie-parser": "^1.4.7",
"core-js": "^3.50.0",
"cors": "^2.8.5",
@@ -118,15 +119,14 @@
"dedent": "^1.7.2",
"express": "^5.1.0",
"express-async-handler": "^1.2.0",
"express-static-gzip": "3.0.1",
"express-static-gzip": "3.0.2",
"fflate": "^0.8.3",
"fs-extra": "^11.3.5",
"hash-wasm": "^4.12.0",
"hbmarkedwrapper": "^1.0.0",
"idb-keyval": "^6.2.5",
"js-yaml": "^5.3.0",
"js-yaml": "^5.4.2",
"jwt-simple": "^0.5.6",
"less": "^4.8.1",
"less": "^4.9.1",
"lodash": "^4.18.1",
"marked": "15.0.12",
"marked-alignment-paragraphs": "^1.0.0",
@@ -135,41 +135,42 @@
"marked-emoji": "^2.0.3",
"marked-extended-tables": "^2.0.1",
"marked-gfm-heading-id": "^4.1.4",
"marked-hbfm": "^1.0.1",
"marked-nonbreaking-spaces": "^1.0.1",
"marked-smartypants-lite": "^1.0.3",
"marked-subsuper-text": "^1.0.4",
"marked-variables": "^1.0.5",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.30.1",
"mongoose": "^9.9.3",
"moment": "^2.31.0",
"mongoose": "^9.10.1",
"nanoid": "6.0.1",
"nconf": "^0.13.0",
"node": "^26.7.0",
"prettier": "^3.8.1",
"react": "^19.2.7",
"react-dom": "^19.2.7",
"node": "^26.9.0",
"prettier": "^3.9.8",
"react": "^19.3.0",
"react-dom": "^19.3.0",
"react-frame-component": "^5.3.2",
"react-router": "^8.3.0",
"react-router": "^8.4.0",
"sanitize-filename": "1.6.4",
"superagent": "^10.2.1"
},
"devDependencies": {
"@stylistic/stylelint-plugin": "^5.3.0",
"babel-jest": "^30.4.1",
"babel-jest": "^30.5.2",
"babel-plugin-transform-import-meta": "^3.0.0",
"eslint": "9.7",
"eslint-plugin-jest": "^29.15.1",
"eslint-plugin-react": "^7.37.5",
"globals": "^16.4.0",
"jest": "^30.4.2",
"jest": "^30.5.2",
"jest-expect-message": "^1.1.3",
"jsdom": "^30.0.1",
"jsdom": "^30.1.0",
"jsdom-global": "^3.0.2",
"postcss-less": "^6.0.0",
"stylelint": "^17.11.1",
"stylelint": "^17.15.0",
"stylelint-config-recess-order": "^7.7.0",
"stylelint-config-recommended": "^18.0.0",
"supertest": "^7.1.4",
"vite": "^8.2.1"
"vite": "^8.3.0"
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ import { model as HomebrewModel } from './homebrew.model.js';
import express from 'express';
import zlib from 'zlib';
import GoogleActions from './googleActions.js';
import { hbfm } from 'hbmarkedwrapper';
import { hbfm } from 'marked-hbfm';
import * as yaml from 'js-yaml';
import asyncHandler from 'express-async-handler';
import { nanoid } from 'nanoid';
+2 -1
View File
@@ -229,5 +229,6 @@ export {
printCurrentBrew,
fetchThemeBundle,
brewSnippetsToJSON,
debugTextMismatch
debugTextMismatch,
yamlSnippetsToText
};
+114
View File
@@ -0,0 +1,114 @@
import {
fetchThemeBundle,
brewSnippetsToJSON,
debugTextMismatch,
yamlSnippetsToText,
} from '../../shared/helpers.js';
import dedent from 'dedent';
// Marked.js adds line returns after closing tags on some default tokens.
// This removes those line returns for comparison sake.
String.prototype.trimReturns = function(){
return this.replace(/\r?\n|\r/g, '');
};
const emoji = 'df_d12_2';
const brewSnippetsThemeTest = [
{
name : 'Test Theme',
snippets : dedent `
\snippet First Theme Snippet
I am the first theme snippet!
\snippet Second Theme Snippet
I am the second theme Snippet!`,
}
];
const brewSnippetsBrewTest = dedent`
\snippet First Brew Snippet
I am the first brew snippet!
\snippet Second Brew Snippet
I am the second brew Snippet!`;
describe(`brewSnippetsToJSON`, ()=>{
it('converts raw brew snippets without theme snippets to JSON', function() {
const testMenuObject = {
groupName : 'Brew Snippets',
icon : 'fas fa-th-list',
view : 'text',
snippets : [{
name : 'Test Snippets JSON without theme snippets',
subsnippets : [
{
gen : 'I am the first brew snippet!\n',
name : 'First Brew Snippet'
}, {
gen : 'I am the second brew Snippet!',
name: 'Second Brew Snippet'
}
]}]
};
const rendered = brewSnippetsToJSON(`Test Snippets JSON without theme snippets`, brewSnippetsBrewTest, null, true);
expect(rendered).toStrictEqual(testMenuObject);
});
it('converts raw brew snippets with theme snippets to JSON', function() {
const testMenuObject = {
groupName : 'Brew Snippets',
icon : 'fas fa-th-list',
view : 'text',
snippets : [{
gen : '',
icon : '',
name : 'Test Theme',
subsnippets : [
{
gen : 'I am the first theme snippet!\n',
icon : '',
name : 'First Theme Snippet',
},
{
gen : 'I am the second theme Snippet!',
icon : '',
name : 'Second Theme Snippet',
},
]},
{
name : 'Test Snippets JSON with theme snippets',
subsnippets : [
{
gen : 'I am the first brew snippet!\n',
name : 'First Brew Snippet'
},
{
gen : 'I am the second brew Snippet!',
name: 'Second Brew Snippet'
}
]
}]};
const rendered = brewSnippetsToJSON(`Test Snippets JSON with theme snippets`, brewSnippetsBrewTest, brewSnippetsThemeTest, true);
expect(rendered).toStrictEqual(testMenuObject);
});
});
describe(`YAMLSnippetsToText`, ()=>{
it('converts brew snippet YAML to a string ', function() {
const brewSnippetsYAML = [{
subsnippets : [
{
gen : 'I am the first brew snippet!\n',
name : 'First Brew Snippet'
}, {
gen : 'I am the second brew Snippet!',
name: 'Second Brew Snippet'
}
]
}];
const rendered = yamlSnippetsToText(brewSnippetsYAML);
expect(rendered).toBe(`${brewSnippetsBrewTest}\n`);
});
});
+1 -1
View File
@@ -1,6 +1,6 @@
import { hbfm } from 'hbmarkedwrapper';
import { hbfm } from 'marked-hbfm';
test('Processes the markdown within an HTML block if its just a class wrapper', function() {
const source = '<div>*Bold text*</div>';
+1 -1
View File
@@ -1,6 +1,6 @@
import { hbfm } from 'hbmarkedwrapper';
import { hbfm } from 'marked-hbfm';
describe('Inline Definition Lists', ()=>{
test('No Term 1 Definition', function() {
+1 -1
View File
@@ -1,4 +1,4 @@
import { hbfm } from 'hbmarkedwrapper';
import { hbfm } from 'marked-hbfm';
import dedent from 'dedent';
// Marked.js adds line returns after closing tags on some default tokens.
+1 -1
View File
@@ -1,6 +1,6 @@
import { hbfm } from 'hbmarkedwrapper';
import { hbfm } from 'marked-hbfm';
describe('Hard Breaks', ()=>{
test('Single Break', function() {
+1 -1
View File
@@ -1,7 +1,7 @@
/* eslint-disable max-lines */
import dedent from 'dedent';
import { hbfm } from 'hbmarkedwrapper';
import { hbfm } from 'marked-hbfm';
// Marked.js adds line returns after closing tags on some default tokens.
// This removes those line returns for comparison sake.
+1 -1
View File
@@ -1,6 +1,6 @@
import {hbfm} from 'hbmarkedwrapper';
import {hbfm} from 'marked-hbfm';
describe('Non-Breaking Spaces Interactions', ()=>{
test('I am actually a single-line definition list!', function() {
@@ -1,6 +1,6 @@
import { hbfm } from 'hbmarkedwrapper';
import { hbfm } from 'marked-hbfm';
describe('Justification', ()=>{
test('Left Justify', function() {
+1 -1
View File
@@ -1,7 +1,7 @@
/* eslint-disable max-lines */
import dedent from 'dedent';
import { hbfm } from 'hbmarkedwrapper';
import { hbfm } from 'marked-hbfm';
// Marked.js adds line returns after closing tags on some default tokens.
// This removes those line returns for comparison sake.
+1 -1
View File
@@ -1,4 +1,4 @@
import hbfm from 'hbmarkedwrapper';
import { hbfm } from 'marked-hbfm';
export default {
createFooterFunc : function(headerSize=1){