Compare commits

...
4 Commits
Author SHA1 Message Date
Ben BalterandClaude Opus 5.5 94168d33d6 Cli: Fixes #16894: Fix quadratic conversion time for large tables (#16901)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 12:42:49 +02:00
Laurent Cozic 451200c76c Server v3.7.3 2026-10-23 12:33:59 +02:00
Laurent Cozic d865d1434e Chore: Build assets 2026-10-23 12:33:37 +02:00
Laurent Cozic 23c85cdecf Server: Pin Node version to 24.22.1 2026-10-23 12:28:52 +02:00
8 changed files with 61 additions and 10 deletions

No files matched your search

+2 -2
View File
@@ -2,7 +2,7 @@
# Build stage
# =============================================================================
FROM node:24 AS builder
FROM node:24.22.1 AS builder
RUN apt-get update \
&& apt-get install -y \
@@ -66,7 +66,7 @@ RUN --mount=type=cache,target=/build/.yarn/cache --mount=type=cache,target=/buil
# from a smaller base image.
# =============================================================================
FROM node:24-slim
FROM node:24.22.1-slim
ARG user=joplin
RUN useradd --create-home --shell /bin/bash $user
+1 -1
View File
@@ -1,4 +1,4 @@
FROM node:24-bookworm
FROM node:24.22.1-bookworm
RUN apt-get update \
&& apt-get install -y \
+26
View File
@@ -2,6 +2,7 @@ import shim from '@joplin/lib/shim';
import * as os from 'os';
import { readFile } from 'fs/promises';
import { filename } from '@joplin/lib/path-utils';
import { dirname } from 'path';
import HtmlToMd, { ParseOptions } from '@joplin/lib/HtmlToMd';
import MdToHtml from '@joplin/renderer/MdToHtml';
@@ -130,6 +131,31 @@ describe('HtmlToMd', () => {
expect(collapsedResult).toContain('\n\n');
});
it('should not re-read the rows of a table for every row', async () => {
// Under domino, each access to `table.rows` walks the whole table, so reading it once per row made
// converting large tables quadratic: https://github.com/laurent22/joplin/issues/16894
const resolveFrom = (moduleName: string, fromPath: string) => require.resolve(moduleName, { paths: [dirname(fromPath)] });
const domino: { createDocument: (html: string)=> Document } = require(resolveFrom('@mixmark-io/domino', resolveFrom('@joplin/turndown', require.resolve('@joplin/lib/HtmlToMd'))));
const rowCount = 100;
const rows = Array.from({ length: rowCount }, (_, i) => `<tr><td>${i}</td><td>b</td><td>c</td></tr>`).join('');
const root = domino.createDocument(`<div><table><thead><tr><th>A</th><th>B</th><th>C</th></tr></thead><tbody>${rows}</tbody></table></div>`).querySelector('div');
// Turndown converts a clone of the element it's given, which wouldn't have the counter below
Object.defineProperty(root, 'cloneNode', { value: () => root });
const table = root.querySelector('table');
const getRows = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(table), 'rows').get;
let rowsReadCount = 0;
Object.defineProperty(table, 'rows', { get: () => {
rowsReadCount++;
return getRows.call(table);
} });
const markdown = new HtmlToMd().parse(root);
expect(markdown.split('\n')).toHaveLength(rowCount + 2);
expect(rowsReadCount).toBeLessThan(rowCount);
});
// Covers how newlines are carried between children when joining them, which was rewritten to fix
// https://github.com/laurent22/joplin/issues/16895
it.each([
+2 -2
View File
@@ -1,3 +1,3 @@
/*! abcjs_basic v6.6.3 Copyright © 2009-2024 Paul Rosen and Gregory Dyke (https://abcjs.net) */
/*! abcjs_basic v6.6.4 Copyright © 2009-2024 Paul Rosen and Gregory Dyke (https://abcjs.net) */
/*! For license information please see abcjs_basic.LICENSE */
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.abcjs=t():e.ABCJS=t()}(this,(function(){return e={1045:function(e,t,r){var n=r(1185),a=r(6306),i=r(1592),s=r(1028),o=r(5633),c={};c.signature="abcjs-basic v"+n,Object.keys(a).forEach((function(e){c[e]=a[e]})),Object.keys(i).forEach((function(e){c[e]=i[e]})),c.renderAbc=r(6859),c.tuneMetrics=r(9989),c.TimingCallbacks=r(5681);var l=r(6020);c.setGlyph=l.setSymbol,c.strTranspose=o;var h=r(5594),u=r(8471),d=r(522),f=r(2029),p=r(6313),m=r(5281),g=r(8702),v=r(5049),b=r(4718),y=r(3450),x=r(562),k=r(9991);c.synth={CreateSynth:h,instrumentIndexToName:u,pitchToNoteName:d,SynthController:y,SynthSequence:f,CreateSynthControl:p,registerAudioContext:m,activeAudioContext:g,supportsAudio:v,playEvent:b,getMidiFile:x,sequence:s,midiRenderer:k},c.Editor=r(5294),c.EditArea=r(2945),e.exports=c},6306:function(e,t,r){var n=r(5681),a={};!function(){"use strict";var e,t;a.startAnimation=function(r,a,i){function s(e){for(var t=0;t<e.length;t++){var r=e[t];r.classList.contains("abcjs-bar")||(r.style.display="none")}}var o;function c(e){i.hideCurrentMeasure?function(e){s(r.querySelectorAll(e))}(e):i.hideFinishedMeasures&&function(e){o&&s(r.querySelectorAll(o)),o=e}(e)}e&&(e.stop(),e=void 0),i.showCursor&&((t=r.querySelector(".abcjs-cursor"))||((t=document.createElement("DIV")).className="abcjs-cursor cursor",t.style.position="absolute",r.appendChild(t),r.style.position="relative")),(e=new n(a,{qpm:i.bpm,eventCallback:function(r){if(r){if(r.measureStart){var n=".abcjs-l"+(a=r).line+".abcjs-m"+a.measureNumber;n&&c(n)}t&&(t.style.left=r.left+"px",t.style.top=r.top+"px",t.style.width=r.width+"px",t.style.height=r.height+"px")}else e.stop(),e=void 0;var a}})).start()},a.pauseAnimation=function(t){e&&(t?e.pause():e.start())},a.stopAnimation=function(){e&&(e.stop(),e=void 0)}}(),e.exports=a},5681:function(e){e.exports=function(e,t){var r=this;if(t||(t={}),r.qpm=t.qpm?parseInt(t.qpm,10):null,!r.qpm){var n=e.metaText?e.metaText.tempo:null;r.qpm=e.getBpm(n)}r.extraMeasuresAtBeginning=t.extraMeasuresAtBeginning?parseInt(t.extraMeasuresAtBeginning,10):0,r.beatCallback=t.beatCallback,r.eventCallback=t.eventCallback,r.lineEndCallback=t.lineEndCallback,r.lineEndAnticipation=t.lineEndAnticipation?parseInt(t.lineEndAnticipation,10):0,r.beatSubdivisions=t.beatSubdivisions?parseInt(t.beatSubdivisions,10):1,r.beatSubdivisions||(r.beatSubdivisions=1),r.joggerTimer=null,r.replaceTarget=function(e){if(r.noteTimings=e.setTiming(r.qpm,r.extraMeasuresAtBeginning),0===e.noteTimings.length&&(r.noteTimings=e.setTiming(0,0)),r.lineEndCallback&&(r.lineEndTimings=function(e,t){for(var r=[],n=null,a=0;a<e.length;a++){var i=e[a];"end"!==i.type&&i.top!==n&&(r.push({measureNumber:i.measureNumber,milliseconds:i.milliseconds-t,top:i.top,bottom:i.top+i.height}),n=i.top)}return r}(e.noteTimings,r.lineEndAnticipation)),r.startTime=null,r.currentBeat=0,r.currentEvent=0,r.currentLine=0,r.currentTime=0,r.isPaused=!1,r.isRunning=!1,r.pausedPercent=null,r.justUnpaused=!1,r.newSeekPercent=0,r.lastTimestamp=0,0!==r.noteTimings.length){r.millisecondsPerBeat=1e3/(r.qpm/60)/r.beatSubdivisions,r.lastMoment=r.noteTimings[r.noteTimings.length-1].milliseconds;var t=e.getMeter(),n="";if(t&&"specified"===t.type&&t.value&&t.value.length>0&&t.value[0].num.indexOf("+")>0&&(n=t.value[0].num),r.beatStarts=[],n){for(var a=r.noteTimings[r.noteTimings.length-1].millisecondsPerMeasure,i=r.lastMoment/a,s=n.split("+"),o=0;o<s.length;o++)s[o]=parseInt(s[o],10)/2;for(var c=0,l=0,h=0;h<i;h++)for(var u=h*a,d=0,f=0;f<s.length;f++){var p=s[f];if(1===r.beatSubdivisions)1===r.beatSubdivisions&&c<r.lastMoment&&r.beatStarts.push({b:l,ts:c}),c+=p*r.millisecondsPerBeat;else for(var m=p*r.beatSubdivisions,g=0;g<Math.floor(m);g++){var v=g/m,b=Math.round(u+d*r.millisecondsPerBeat);b<r.lastMoment&&r.beatStarts.push({b:l+v,ts:b}),d++}l++}r.beatStarts.push({b:i*s.length,ts:r.lastMoment}),r.totalBeats=r.beatStarts.length}else{r.totalBeats=Math.round(r.lastMoment/r.millisecondsPerBeat);for(var y=0;y<r.totalBeats+1;y++)r.beatStarts.push({b:y/r.beatSubdivisions,ts:Math.round(y*r.millisecondsPerBeat)})}}},r.replaceTarget(e),r.doTiming=function(e){if(r.lastTimestamp!==e&&(r.lastTimestamp=e,!r.isPaused&&r.isRunning)){for(r.startTime||(r.startTime=e),r.currentTime=e-r.startTime,r.currentTime+=16;r.noteTimings.length>r.currentEvent&&r.noteTimings[r.currentEvent].milliseconds<r.currentTime;){if(r.eventCallback&&"event"===r.noteTimings[r.currentEvent].type){var t=r.startTime;r.eventCallback(r.noteTimings[r.currentEvent]),t!==r.startTime&&(r.currentTime=e-r.startTime)}r.currentEvent++}if(r.lineEndCallback&&r.lineEndTimings.length>r.currentLine&&r.lineEndTimings[r.currentLine].milliseconds<r.currentTime&&r.currentEvent<r.noteTimings.length){var n=r.noteTimings[r.currentEvent].milliseconds===r.currentTime?r.noteTimings[r.currentEvent]:r.noteTimings[r.currentEvent-1];rLine truncated
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.abcjs=t():e.ABCJS=t()}(this,(function(){return e={1045:function(e,t,r){var n=r(1185),a=r(6306),i=r(1592),s=r(1028),o=r(5633),c={};c.signature="abcjs-basic v"+n,Object.keys(a).forEach((function(e){c[e]=a[e]})),Object.keys(i).forEach((function(e){c[e]=i[e]})),c.renderAbc=r(6859),c.tuneMetrics=r(9989),c.TimingCallbacks=r(5681);var l=r(6020);c.setGlyph=l.setSymbol,c.strTranspose=o;var h=r(5594),u=r(8471),d=r(522),f=r(2029),p=r(6313),m=r(5281),g=r(8702),v=r(5049),b=r(4718),y=r(3450),x=r(562),k=r(9991);c.synth={CreateSynth:h,instrumentIndexToName:u,pitchToNoteName:d,SynthController:y,SynthSequence:f,CreateSynthControl:p,registerAudioContext:m,activeAudioContext:g,supportsAudio:v,playEvent:b,getMidiFile:x,sequence:s,midiRenderer:k},c.Editor=r(5294),c.EditArea=r(2945),e.exports=c},6306:function(e,t,r){var n=r(5681),a={};!function(){"use strict";var e,t;a.startAnimation=function(r,a,i){function s(e){for(var t=0;t<e.length;t++){var r=e[t];r.classList.contains("abcjs-bar")||(r.style.display="none")}}var o;function c(e){i.hideCurrentMeasure?function(e){s(r.querySelectorAll(e))}(e):i.hideFinishedMeasures&&function(e){o&&s(r.querySelectorAll(o)),o=e}(e)}e&&(e.stop(),e=void 0),i.showCursor&&((t=r.querySelector(".abcjs-cursor"))||((t=document.createElement("DIV")).className="abcjs-cursor cursor",t.style.position="absolute",r.appendChild(t),r.style.position="relative")),(e=new n(a,{qpm:i.bpm,eventCallback:function(r){if(r){if(r.measureStart){var n=".abcjs-l"+(a=r).line+".abcjs-m"+a.measureNumber;n&&c(n)}t&&(t.style.left=r.left+"px",t.style.top=r.top+"px",t.style.width=r.width+"px",t.style.height=r.height+"px")}else e.stop(),e=void 0;var a}})).start()},a.pauseAnimation=function(t){e&&(t?e.pause():e.start())},a.stopAnimation=function(){e&&(e.stop(),e=void 0)}}(),e.exports=a},5681:function(e){e.exports=function(e,t){var r=this;t||(t={}),r.qpm=t.qpm?parseInt(t.qpm,10):null,r.extraMeasuresAtBeginning=t.extraMeasuresAtBeginning?parseInt(t.extraMeasuresAtBeginning,10):0,r.beatCallback=t.beatCallback,r.eventCallback=t.eventCallback,r.lineEndCallback=t.lineEndCallback,r.lineEndAnticipation=t.lineEndAnticipation?parseInt(t.lineEndAnticipation,10):0,r.beatSubdivisions=t.beatSubdivisions?parseInt(t.beatSubdivisions,10):1,r.beatSubdivisions||(r.beatSubdivisions=1),r.joggerTimer=null,r.replaceTarget=function(e){if(!t.qpm){var n=e.metaText?e.metaText.tempo:null;r.qpm=e.getBpm(n)}if(r.noteTimings=e.setTiming(r.qpm,r.extraMeasuresAtBeginning),0===e.noteTimings.length&&(r.noteTimings=e.setTiming(0,0)),r.lineEndCallback&&(r.lineEndTimings=function(e,t){for(var r=[],n=null,a=0;a<e.length;a++){var i=e[a];"end"!==i.type&&i.top!==n&&(r.push({measureNumber:i.measureNumber,milliseconds:i.milliseconds-t,top:i.top,bottom:i.top+i.height}),n=i.top)}return r}(e.noteTimings,r.lineEndAnticipation)),r.startTime=null,r.currentBeat=0,r.currentEvent=0,r.currentLine=0,r.currentTime=0,r.isPaused=!1,r.isRunning=!1,r.pausedPercent=null,r.justUnpaused=!1,r.newSeekPercent=0,r.lastTimestamp=0,0!==r.noteTimings.length){r.millisecondsPerBeat=1e3/(r.qpm/60)/r.beatSubdivisions,r.lastMoment=r.noteTimings[r.noteTimings.length-1].milliseconds;var a=e.getMeter(),i="";if(a&&"specified"===a.type&&a.value&&a.value.length>0&&a.value[0].num.indexOf("+")>0&&(i=a.value[0].num),r.beatStarts=[],i){for(var s=r.noteTimings[r.noteTimings.length-1].millisecondsPerMeasure,o=r.lastMoment/s,c=i.split("+"),l=0;l<c.length;l++)c[l]=parseInt(c[l],10)/2;for(var h=0,u=0,d=0;d<o;d++)for(var f=d*s,p=0,m=0;m<c.length;m++){var g=c[m];if(1===r.beatSubdivisions)1===r.beatSubdivisions&&h<r.lastMoment&&r.beatStarts.push({b:u,ts:h}),h+=g*r.millisecondsPerBeat;else for(var v=g*r.beatSubdivisions,b=0;b<Math.floor(v);b++){var y=b/v,x=Math.round(f+p*r.millisecondsPerBeat);x<r.lastMoment&&r.beatStarts.push({b:u+y,ts:x}),p++}u++}r.beatStarts.push({b:o*c.length,ts:r.lastMoment}),r.totalBeats=r.beatStarts.length}else{r.totalBeats=Math.round(r.lastMoment/r.millisecondsPerBeat);for(var k=0;k<r.totalBeats+1;k++)r.beatStarts.push({b:k/r.beatSubdivisions,ts:Math.round(k*r.millisecondsPerBeat)})}}},r.replaceTarget(e),r.doTiming=function(e){if(r.lastTimestamp!==e&&(r.lastTimestamp=e,!r.isPaused&&r.isRunning)){for(r.startTime||(r.startTime=e),r.currentTime=e-r.startTime,r.currentTime+=16;r.noteTimings.length>r.currentEvent&&r.noteTimings[r.currentEvent].milliseconds<r.currentTime;){if(r.eventCallback&&"event"===r.noteTimings[r.currentEvent].type){var t=r.startTime;r.eventCallback(r.noteTimings[r.currentEvent]),t!==r.startTime&&(r.currentTime=e-r.startTime)}r.currentEvent++}if(r.lineEndCallback&&r.lineEndTimings.length>r.currentLine&&r.lineEndTimings[r.currentLine].milliseconds<r.currentTime&&r.currentEvent<r.noteTimings.length){var n=r.noteTimings[r.currentEvent].milliseconds===r.currentTime?r.noteTimings[r.currentEvent]:r.noteTimings[r.currentEvent-1];rLine truncated
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@joplin/server",
"version": "3.7.2",
"version": "3.7.3",
"private": true,
"scripts": {
"start-dev": "yarn build && JOPLIN_IS_TESTING=1 nodemon --config nodemon.json --ext ts,js,mustache,css,tsx dist/index.js --env dev",
+1
View File
@@ -375,4 +375,5 @@ twodo
Gemma
untruncated
instr
mixmark
Hrvatski
+8 -4
View File
@@ -29,8 +29,11 @@ function getColumnAlignment(table, columnIndex) {
var align = '';
for (var i = 0; i < table.rows.length; ++i) {
var row = table.rows[i];
// Read `rows` once: under domino each access re-walks the whole table, which made converting large tables
// quadratic: https://github.com/laurent22/joplin/issues/16894
var rows = table.rows;
for (var i = 0; i < rows.length; ++i) {
var row = rows[i];
if (columnIndex < row.childNodes.length) {
var cellAlignment = getAlignment(row.childNodes[columnIndex]);
++votes[cellAlignment];
@@ -371,8 +374,9 @@ function handleColSpan(content, node, emptyChar) {
function tableColCount(node) {
let maxColCount = 0;
for (let i = 0; i < node.rows.length; i++) {
const row = node.rows[i]
const rows = node.rows;
for (let i = 0; i < rows.length; i++) {
const row = rows[i]
const colCount = row.childNodes.length
if (colCount > maxColCount) maxColCount = colCount
}
+20
View File
@@ -1,5 +1,25 @@
# Joplin Server Changelog
## [server-v3.7.3](https://github.com/laurent22/joplin/releases/tag/server-v3.7.3) - 2026-10-23T10:33:53Z
- New: Add rate limiting to the signup and password reset endpoints (#16467)
- Improved: Accessibility: Associate labels with controls on the "Your Profile" page (#16588 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator))
- Improved: Allow syncing with a web app instance hosted on a custom domain (#16760 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator))
- Improved: Do not log failed image downloads as warnings (#16493)
- Improved: Do not log invalid requests as errors (#16492)
- Improved: Handle locked notes inside shared notebooks (#16579) (#16336 by [@keshav0479](https://github.com/keshav0479))
- Improved: Only record changes for resource blobs under the .resource/ directory (4724e3c)
- Improved: Pin Node version to 24.22.1 (23c85cd)
- Improved: Published notebook notes cannot be scrolled down on Android browsers (#16702) (#16669 by [@Rygaa](https://github.com/Rygaa))
- Improved: Published notebook sidebar has no overlay on small screens (#16798) (#16795 by [@Rygaa](https://github.com/Rygaa))
- Improved: Restore footer (#16763) (#16757 by [@Rygaa](https://github.com/Rygaa))
- Improved: Updated packages adm-zip (v0.5.18), fs-extra (v11.3.6), tar (v7.5.19)
- Fixed: Applications tab: Fix web app is marked as "unknown" (#16478 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator))
- Fixed: Fix Stripe subscription item being located by position instead of price (ef70b72)
- Fixed: Fix account not being created when the Stripe customer has no name (#16557)
- Fixed: Fix notification read-state being updatable by non-owner (#16466)
- Fixed: Fixes sync errors when an upload is retried (#16491)
## [server-v3.7.2](https://github.com/laurent22/joplin/releases/tag/server-v3.7.2) - 2026-09-07T16:29:45Z
- New: Add OrphanTrace logging to investigate orphaned items bug (#15709)