online.git: browser/src

1 view
Skip to first unread message

"Banobe Pascal (via github)"

unread,
Feb 4, 2026, 4:14:52 AMFeb 4
to collaboraon...@googlegroups.com
browser/src/control/Control.NotebookbarWriter.js | 18 +++++-------------
browser/src/map/handler/Map.WOPI.js | 2 +-
2 files changed, 6 insertions(+), 14 deletions(-)

New commits:
commit 455a6e934259f9e1f0ba239bb92ce0cd7338f03f
Author: Banobe Pascal <banobe...@collabora.com>
AuthorDate: Wed Feb 4 04:58:24 2026 +0300
Commit: Szymon Kłos <eszk...@gmail.com>
CommitDate: Wed Feb 4 10:13:55 2026 +0100

Remove Compare overflowgroup and reorder menu entries

- Remove unnecessary overflowgroup from 'review-compare' button
- Move 'Compare Document...' to first position in the entries

Signed-off-by: Banobe Pascal <banobe...@collabora.com>
Change-Id: I9ac61b7d15a815667c2e74e3b5532e55aa67b025

diff --git a/browser/src/control/Control.NotebookbarWriter.js b/browser/src/control/Control.NotebookbarWriter.js
index 6d14caf52a..1c589a5906 100644
--- a/browser/src/control/Control.NotebookbarWriter.js
+++ b/browser/src/control/Control.NotebookbarWriter.js
@@ -2403,19 +2403,11 @@ window.L.Control.NotebookbarWriter = window.L.Control.Notebookbar.extend({
},
{ type: 'separator', id: 'review-accepttrackedchanges-break', orientation: 'vertical' },
hideChangeTrackingControls ? {} : {
- 'type': 'overflowgroup',
- 'id': 'review-compare',
- 'name':_('Compare'),
- 'accessibility': { focusBack: false, combination: 'RC', de: null },
- 'children' : [
- {
- 'id': 'review-compare:CompareDocumentsMenu',
- 'type': 'menubutton',
- 'text': _('Compare Documents'),
- 'command': '.uno:CompareDocuments',
- 'accessibility': { focusBack: true, combination: 'CD', de: null }
- },
- ]
+ 'id': 'review-compare:CompareDocumentsMenu',
+ 'type': 'menubutton',
+ 'text': _UNO('.uno:CompareDocuments', 'text'),
+ 'command': '.uno:CompareDocuments',
+ 'accessibility': { focusBack: true, combination: 'RO', de: null }
},
hideChangeTrackingControls ? {} : { type: 'separator', id: 'review-compare-break', orientation: 'vertical' },
{
diff --git a/browser/src/map/handler/Map.WOPI.js b/browser/src/map/handler/Map.WOPI.js
index db36d7e371..15250a6c90 100644
--- a/browser/src/map/handler/Map.WOPI.js
+++ b/browser/src/map/handler/Map.WOPI.js
@@ -221,7 +221,7 @@ window.L.Map.WOPI = window.L.Handler.extend({
/* Separate, because needs explicit integration support */
menuEntriesMultimedia.push({action: 'remotemultimedia', text: _UNO('.uno:InsertAVMedia', '', true)});

- menuEntriesCompare.push({action: 'remotecomparedocuments', text: _('Compare Document...')});
+ menuEntriesCompare.unshift({action: 'remotecomparedocuments', text: _('Compare Document...')});
}

this._insertImageMenuSetupDone = true;

"Szymon Kłos (via github)"

unread,
Feb 4, 2026, 5:00:01 AMFeb 4
to collaboraon...@googlegroups.com
browser/src/unocommands.js | 1 +
1 file changed, 1 insertion(+)

New commits:
commit 4c105ae5fb26253ea281c9164b6258a91f1a9a7a
Author: Szymon Kłos <szymo...@collabora.com>
AuthorDate: Wed Feb 4 09:25:11 2026 +0000
Commit: Szymon Kłos <eszk...@gmail.com>
CommitDate: Wed Feb 4 10:59:51 2026 +0100

unocommands: update for CompareDocuments

Signed-off-by: Szymon Kłos <szymo...@collabora.com>
Change-Id: I2538da06972c382997493d8a9c75643bd9750506

diff --git a/browser/src/unocommands.js b/browser/src/unocommands.js
index 24d163576b..0641aeee21 100644
--- a/browser/src/unocommands.js
+++ b/browser/src/unocommands.js
@@ -121,6 +121,7 @@ var unoCommandsArray = {
'CommonAlignRight':{global:{menu:_('Right'),},},
'CommonAlignTop':{global:{menu:_('Top'),},},
'CommonAlignVerticalCenter':{global:{menu:_('Center'),},},
+ 'CompareDocuments':{global:{context:_('Compare Non-Track Changed Document'),menu:_('Compare'),},},
'CompressGraphic':{global:{menu:_('Co~mpress...'),},presentation:{menu:_('Co~mpress...'),},},
'CondDateFormatDialog':{spreadsheet:{menu:_('Date...'),},},
'ConditionalFormatManagerDialog':{spreadsheet:{menu:_('Manage...'),},},

"Hubert Figuière (via github)"

unread,
Feb 5, 2026, 7:59:31 AMFeb 5
to collaboraon...@googlegroups.com
browser/src/control/Control.NotebookbarBase.ts | 4 ++--
browser/src/control/Control.UIManager.ts | 16 ++++++++++++----
2 files changed, 14 insertions(+), 6 deletions(-)

New commits:
commit 4694c329b0b4db040e5524dce6003b9834266f2e
Author: Hubert Figuière <h...@collabora.com>
AuthorDate: Wed Feb 4 15:28:13 2026 -0500
Commit: Szymon Kłos <eszk...@gmail.com>
CommitDate: Thu Feb 5 13:58:50 2026 +0100

uimanager: Properly return the item was found

We were getting an error that the command wasn't found when it was:
showNotebookbarCommand() should return a boolean like the impl

Also:
When converting to typescript. 'found |= something()' was converted to
'found ||= something()' because TypeScript complained which in turn
transpile to 'found || (found = something())' which make something not being
called if found is already true. This would made this.showCommandInMenubar() i
not being called.

Signed-off-by: Hubert Figuière <h...@collabora.com>
Change-Id: I03016f86df847b310609282706c2610d7a72d4d3

diff --git a/browser/src/control/Control.NotebookbarBase.ts b/browser/src/control/Control.NotebookbarBase.ts
index c55443a5ff..33f47dffa9 100644
--- a/browser/src/control/Control.NotebookbarBase.ts
+++ b/browser/src/control/Control.NotebookbarBase.ts
@@ -116,8 +116,8 @@ class NotebookbarBase extends JSDialogComponent {
this.impl?.reloadShortcutsBar();
}

- public showNotebookbarCommand(commandId: string, show: boolean) {
- this.impl?.showNotebookbarCommand(commandId, show);
+ public showNotebookbarCommand(commandId: string, show: boolean): boolean {
+ return this.impl?.showNotebookbarCommand(commandId, show);
}

// tabs
diff --git a/browser/src/control/Control.UIManager.ts b/browser/src/control/Control.UIManager.ts
index 7c65c7b69e..9b9bf2aee6 100644
--- a/browser/src/control/Control.UIManager.ts
+++ b/browser/src/control/Control.UIManager.ts
@@ -1129,13 +1129,21 @@ class UIManager extends window.L.Control {

var found = false;
if (this.getCurrentMode() === 'classic') {
- found ||= this.showCommandInClassicToolbar(command, show);
- found ||= this.showCommandInMenubar(command, show);
+ if (this.showCommandInClassicToolbar(command, show)) {
+ found = true;
+ }
+ if (this.showCommandInMenubar(command, show)) {
+ found = true;
+ }
}

if (this.notebookbar) {
- if (this.getCurrentMode() === 'notebookbar') this.notebookbar.reloadShortcutsBar();
- found ||= this.notebookbar.showNotebookbarCommand(command, show);
+ if (this.getCurrentMode() === 'notebookbar') {
+ this.notebookbar.reloadShortcutsBar();
+ }
+ if (this.notebookbar.showNotebookbarCommand(command, show)) {
+ found = true;
+ }
}

if (!found)

"Shardul Vikram Singh (via github)"

unread,
Feb 5, 2026, 8:32:12 AMFeb 5
to collaboraon...@googlegroups.com
browser/src/canvas/CanvasSectionProps.js | 3 +
browser/src/canvas/sections/ShapeHandlesSection.ts | 58 +++++++++++++++++++--
browser/src/control/Control.Header.ts | 8 ++
3 files changed, 65 insertions(+), 4 deletions(-)

New commits:
commit 5a4cbcfb5d44ebd6d4cc00de4ab1a3dedb2bac01
Author: Shardul Vikram Singh <shardu...@collabora.com>
AuthorDate: Sat Jan 17 22:11:32 2026 +0530
Commit: pedropintosilva <65948705+ped...@users.noreply.github.com>
CommitDate: Thu Feb 5 14:31:22 2026 +0100

Prevent shape from being dragged out of grid boundaries

Signed-off-by: Shardul Vikram Singh <shardu...@collabora.com>
Change-Id: Ide6e9fbd8bad8dcd0c5500735030cde270e5436d

diff --git a/browser/src/canvas/CanvasSectionProps.js b/browser/src/canvas/CanvasSectionProps.js
index 13747e8b46..cc058faf47 100644
--- a/browser/src/canvas/CanvasSectionProps.js
+++ b/browser/src/canvas/CanvasSectionProps.js
@@ -47,6 +47,7 @@ app.CSections.ColumnGroup = { name: 'column group' , zIndex: 5 };
app.CSections.RowGroup = { name: 'row group' , zIndex: 5 };
app.CSections.CornerGroup = { name: 'corner group' , zIndex: 5 };
app.CSections.SelectionRectangle = { name: 'selection-rectangle', zIndex: 5 };
+app.CSections.ShapeHandlesSection = { name: 'shapeHandlesSection', zIndex: 5 };

app.CSections.Comment = { name: 'comment' , zIndex: 7 }; // This class is for comment markers. It is a document object. One should change instance's name after initializing (there may be many instances of this class).

@@ -95,6 +96,7 @@ app.CSections.Debug.Splits.processingOrder = 71; // Calc. This is bound to ti
app.CSections.MouseControl.processingOrder = 72; // Writer & Impress & Calc. Bound to tiles.
app.CSections.SelectionRectangle.processingOrder = 73; // Impress.
app.CSections.TextSelection.processingOrder = 74; // All.
+app.CSections.ShapeHandlesSection.processingOrder = 75;
app.CSections.Splitter.processingOrder = 80; // Calc.

app.CSections.CalcGrid.drawingOrder = 40; // Calc.
@@ -116,6 +118,7 @@ app.CSections.CellFillMarker.drawingOrder = 95; // Calc.
app.CSections.RowGroup.drawingOrder = 100; // Calc.
app.CSections.ColumnGroup.drawingOrder = 110; // Calc.
app.CSections.CornerGroup.drawingOrder = 120; // Calc.
+app.CSections.ShapeHandlesSection.drawingOrder = 125; // Calc.
app.CSections.CornerHeader.drawingOrder = 130; // Calc.
app.CSections.RowHeader.drawingOrder = 140; // Calc.
app.CSections.ColumnHeader.drawingOrder = 150; // Calc.
diff --git a/browser/src/canvas/sections/ShapeHandlesSection.ts b/browser/src/canvas/sections/ShapeHandlesSection.ts
index b4117a9ee2..9ecdf3f6a0 100644
--- a/browser/src/canvas/sections/ShapeHandlesSection.ts
+++ b/browser/src/canvas/sections/ShapeHandlesSection.ts
@@ -57,14 +57,14 @@ class HelperLineStyles {
}

class ShapeHandlesSection extends CanvasSectionObject {
- processingOrder: number = app.CSections.DefaultForDocumentObjects.processingOrder;
- drawingOrder: number = app.CSections.DefaultForDocumentObjects.drawingOrder;
- zIndex: number = app.CSections.DefaultForDocumentObjects.zIndex;
+ processingOrder: number = app.CSections.ShapeHandlesSection.processingOrder;
+ drawingOrder: number = app.CSections.ShapeHandlesSection.drawingOrder;
+ zIndex: number = app.CSections.ShapeHandlesSection.zIndex;
documentObject: boolean = true;
showSection: boolean = false;

constructor (info: any) {
- super("shapeHandlesSection");
+ super(app.CSections.ShapeHandlesSection.name);

this.sectionProperties.info = null;
this.sectionProperties.handles = [];
@@ -997,6 +997,54 @@ class ShapeHandlesSection extends CanvasSectionObject {
this.containerObject.requestReDraw();
}

+ private constrainDragToSheetArea(dragDistance: number[]) {
+ if (app.map.getDocType() === 'spreadsheet') {
+ // Cap the drag distance to the document boundaries
+ const calcGridSection = app.sectionContainer.getSectionWithName(
+ app.CSections.CalcGrid.name,
+ );
+ const rowHeaderSection = app.sectionContainer.getSectionWithName(
+ app.CSections.RowHeader.name,
+ ) as cool.RowHeader;
+ const columnHeaderSection = app.sectionContainer.getSectionWithName(
+ app.CSections.ColumnHeader.name,
+ ) as cool.ColumnHeader;
+
+ if (calcGridSection && rowHeaderSection && columnHeaderSection) {
+ dragDistance[0] = Math.max(
+ calcGridSection.myTopLeft[0] -
+ this.myTopLeft[0] -
+ columnHeaderSection.getHeaderInfo().getDocVisStart(),
+ dragDistance[0],
+ );
+ dragDistance[1] = Math.max(
+ calcGridSection.myTopLeft[1] -
+ this.myTopLeft[1] -
+ rowHeaderSection.getHeaderInfo().getDocVisStart(),
+ dragDistance[1],
+ );
+ // Clip svg at the header edges
+ if (this.sectionProperties.svg) {
+ const cropLeft =
+ Math.max(
+ calcGridSection.myTopLeft[0] -
+ this.myTopLeft[0] -
+ dragDistance[0],
+ 0,
+ ) / app.dpiScale;
+ const cropTop =
+ Math.max(
+ calcGridSection.myTopLeft[1] -
+ this.myTopLeft[1] -
+ dragDistance[1],
+ 0,
+ ) / app.dpiScale;
+ this.sectionProperties.svg.style.clipPath = `inset(${cropTop}px 0px 0px ${cropLeft}px)`;
+ }
+ }
+ }
+ }
+
onMouseMove(position: cool.SimplePoint, dragDistance: number[]) {
let canDrag = !app.file.textCursor.visible;

@@ -1008,6 +1056,8 @@ class ShapeHandlesSection extends CanvasSectionObject {
}

if (this.containerObject.isDraggingSomething() && canDrag) {
+ this.constrainDragToSheetArea(dragDistance);
+
if (!app.activeDocument.activeLayout.viewedRectangle.equals(this.sectionProperties.viewedRectangleOnMouseDown.toArray())) {
const diff = new cool.SimplePoint(
app.activeDocument.activeLayout.viewedRectangle.x1 - this.sectionProperties.viewedRectangleOnMouseDown.x1,
diff --git a/browser/src/control/Control.Header.ts b/browser/src/control/Control.Header.ts
index f6d37f413e..4be1a2728b 100644
--- a/browser/src/control/Control.Header.ts
+++ b/browser/src/control/Control.Header.ts
@@ -53,6 +53,10 @@ export class Header extends CanvasSectionObject {

getFont: () => string;

+ getHeaderInfo(): HeaderInfo {
+ return this._headerInfo;
+ }
+
constructor (name: string) {
super(name);
}
@@ -919,6 +923,10 @@ export class HeaderInfo {
return this._elements[index];
}

+ getDocVisStart(): number {
+ return this._docVisStart;
+ }
+
getRowData (index: number): HeaderEntryData {
window.app.console.assert(!this._isColumn, 'this is a column header instance!');
return this.getElementData(index);

"Mohit Marathe (via github)"

unread,
Feb 5, 2026, 9:55:30 AMFeb 5
to collaboraon...@googlegroups.com
browser/src/control/Control.UIManager.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)

New commits:
commit 8f58ab7f8b51627db4a6a93fa4c763b1d3cad6bc
Author: Mohit Marathe <mohit....@collabora.com>
AuthorDate: Thu Feb 5 13:26:43 2026 +0530
Commit: pedropintosilva <65948705+ped...@users.noreply.github.com>
CommitDate: Thu Feb 5 15:55:17 2026 +0100

followme: auto start followme presentation for new joiners

without checking for startFollowMePresentation=true url parameter

Signed-off-by: Mohit Marathe <mohit....@collabora.com>
Change-Id: I19e1eed546e58b2df0010a5a385ddfaa81bfe650

diff --git a/browser/src/control/Control.UIManager.ts b/browser/src/control/Control.UIManager.ts
index 9b9bf2aee6..783e3db56b 100644
--- a/browser/src/control/Control.UIManager.ts
+++ b/browser/src/control/Control.UIManager.ts
@@ -563,7 +563,7 @@ class UIManager extends window.L.Control {

this.refreshTheme();

- var startFolloMePresntationGet = this.map.isPresentationOrDrawing() && window.coolParams.get('startFollowMePresentation');
+ var startFolloMePresntationGet = this.map.isPresentationOrDrawing();
var presentationLeaderIdGet = this.map.isPresentationOrDrawing() && window.coolParams.get('presentationLeaderId');
var startPresentationGet = this.map.isPresentationOrDrawing() && window.coolParams.get('startPresentation');
if (this.map.wopi.PresentationLeader)
@@ -573,7 +573,7 @@ class UIManager extends window.L.Control {
// check for "presentation" dispatch event only after document gets fully loaded
// in case if the leader is defined we have to wait a little longer to get the viewer info
const startPresentation = () => {
- if (startFolloMePresntationGet === 'true' || startFolloMePresntationGet === '1') {
+ if (startFolloMePresntationGet) {
const dispatchFollowPresentation = () => {
app.dispatcher.dispatch('followpresentation');
this.map.off('slideshowfollowon', dispatchFollowPresentation);

"Rashesh Padia (via github)"

unread,
Feb 6, 2026, 1:08:59 AMFeb 6
to collaboraon...@googlegroups.com
browser/src/canvas/sections/CommentSection.ts | 74 +++++++++++++++++++++++---
1 file changed, 66 insertions(+), 8 deletions(-)

New commits:
commit 203ded97bb0100d074fec5315caec13b13d35837
Author: Rashesh Padia <rashes...@collabora.com>
AuthorDate: Wed Feb 4 14:47:49 2026 +0530
Commit: Rashesh Padia <rashesh...@gmail.com>
CommitDate: Fri Feb 6 11:38:29 2026 +0530

fix: mention doesn't work when inserted at the start of the comment

In Webkit, sometimes due to some sort of race condition the range is
null for selection or range points to treeview popup element.

Only set range variable when range.startContainer contains the editor element.
If range is null, we get the active editor and find the TEXT_NODE manually.

Steps to reproduce in WebKit browser:
1. Open writer document
2. Insert comment
3. Start typing mention and insert mention
4. Notice mention is not inserted and cursor moves to start position

I tested with epiphany. It is not always reproducible, on about 10 tries
I was able to reproduce it once or twice.

Signed-off-by: Rashesh Padia <rashes...@collabora.com>
Change-Id: I83965c56c3f8420a673be254dcbf12f4c90c5503

diff --git a/browser/src/canvas/sections/CommentSection.ts b/browser/src/canvas/sections/CommentSection.ts
index db8490e4fa..db5fb2b58d 100644
--- a/browser/src/canvas/sections/CommentSection.ts
+++ b/browser/src/canvas/sections/CommentSection.ts
@@ -1741,14 +1741,41 @@ export class Comment extends CanvasSectionObject {

public autoCompleteMention(username: string, profileLink: string, replacement: string): void {
const selection = window.getSelection();
- if (!selection.rangeCount) return;
-
- const range = selection.getRangeAt(0);
+ if (!selection)
+ return;

- const cursorPosition = range.endOffset;
- const container = range.startContainer;
+ const editorElement = this.getActiveEditorElement();
+ let range: Range | null = null;
+ if (selection.rangeCount) {
+ const candidateRange = selection.getRangeAt(0);
+ if (
+ !editorElement ||
+ editorElement.contains(candidateRange.startContainer)
+ )
+ range = candidateRange;
+ }
+ const container = range ? range.startContainer : null;
+ let textNode: Text | null = null;
+ let cursorPosition = range ? range.endOffset : 0;
+
+ if (container && container.nodeType === Node.TEXT_NODE) {
+ textNode = container as Text;
+ } else if (!range) {
+ if (!editorElement) return;
+ const walker = document.createTreeWalker(
+ editorElement,
+ NodeFilter.SHOW_TEXT,
+ null,
+ );
+ textNode = walker.nextNode() as Text | null;
+ if (!textNode) {
+ textNode = document.createTextNode('');
+ editorElement.appendChild(textNode);
+ }
+ cursorPosition = textNode.textContent?.length ?? 0;
+ }

- const containerText = container.textContent || '';
+ const containerText = textNode?.textContent || container?.textContent || editorElement?.textContent || '';
const mentionStart = containerText.lastIndexOf(replacement, cursorPosition);

if (mentionStart !== -1) {
@@ -1761,8 +1788,12 @@ export class Comment extends CanvasSectionObject {
hyperlink.href = profileLink;
hyperlink.textContent = `@${username}`;

- container.textContent = beforeMention;
- container.parentNode?.insertBefore(hyperlink, container.nextSibling);
+ if (!textNode || !textNode.parentNode) {
+ return;
+ }
+
+ textNode.textContent = beforeMention;
+ textNode.parentNode?.insertBefore(hyperlink, textNode.nextSibling);

const afterTextNode = document.createTextNode(afterMention);
const extraSpaceNode = document.createTextNode('\u00A0');
@@ -1777,6 +1808,33 @@ export class Comment extends CanvasSectionObject {
selection.addRange(newRange);
}
}
+
+ private getActiveEditorElement(): HTMLElement | null {
+ if ((<any>window).mode.isMobile()) {
+ const commentSection = app.sectionContainer.getSectionWithName(
+ app.CSections.CommentList.name,
+ );
+ const isMobileCommentActive = commentSection?.isMobileCommentActive();
+ if (!isMobileCommentActive) return null;
+ const mobileCommentModalId = commentSection?.getMobileCommentModalId();
+ return document.getElementById(mobileCommentModalId);
+ }
+ if (
+ this.sectionProperties.nodeModify &&
+ this.sectionProperties.nodeModify.style.display !== 'none'
+ )
+ return this.sectionProperties.nodeModifyText;
+ if (
+ this.sectionProperties.nodeReply &&
+ this.sectionProperties.nodeReply.style.display !== 'none'
+ )
+ return this.sectionProperties.nodeReplyText;
+ return (
+ this.sectionProperties.nodeModifyText ||
+ this.sectionProperties.nodeReplyText ||
+ null
+ );
+ }
}

}

"Caolán McNamara (via github)"

unread,
Feb 6, 2026, 6:53:34 AMFeb 6
to collaboraon...@googlegroups.com
browser/src/map/Map.js | 21 ++++++++-------------
1 file changed, 8 insertions(+), 13 deletions(-)

New commits:
commit 68150e1d72c6d75199e2cd8a4afd550f8f2b2bc9
Author: Caolán McNamara <caolan....@collabora.com>
AuthorDate: Wed Feb 4 10:31:31 2026 +0000
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Fri Feb 6 11:53:07 2026 +0000

_resetView is never called with overridden preserveMapOffset or afterZoomAnim args

so bake in what always happens

Signed-off-by: Caolán McNamara <caolan....@collabora.com>
Change-Id: I343fd691a61569be9736ea582f1ae1160f485bbd

diff --git a/browser/src/map/Map.js b/browser/src/map/Map.js
index fd68565a19..b0570e264a 100644
--- a/browser/src/map/Map.js
+++ b/browser/src/map/Map.js
@@ -1313,30 +1313,25 @@ window.L.Map = window.L.Evented.extend({

// private methods that modify map state

- _resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) {
-
+ _resetView: function (center, zoom) {
var zoomChanged = (this._zoom !== zoom);

- if (!afterZoomAnim) {
- this.fire('movestart');
+ this.fire('movestart');

- if (zoomChanged) {
- this.fire('zoomstart');
- }
+ if (zoomChanged) {
+ this.fire('zoomstart');
}

this._zoom = zoom;

- if (!preserveMapOffset) {
- window.L.DomUtil.setPosition(this._mapPane, new cool.Point(0, 0));
- }
+ window.L.DomUtil.setPosition(this._mapPane, new cool.Point(0, 0));

this._pixelOrigin = this._getNewPixelOrigin(center);

var loading = !this._loaded;
this._loaded = true;

- this.fire('viewreset', {hard: !preserveMapOffset});
+ this.fire('viewreset', {hard: true});

if (loading) {
this.fire('load');
@@ -1344,7 +1339,7 @@ window.L.Map = window.L.Evented.extend({

this.fire('move');

- if (zoomChanged || afterZoomAnim) {
+ if (zoomChanged) {
this.fire('zoomend');
this.fire('zoomlevelschange');
}
@@ -1352,7 +1347,7 @@ window.L.Map = window.L.Evented.extend({
// don't allow to turn off the following when moving to other sheet
var backupFollowed = app.getFollowedViewId();

- this.fire('moveend', {hard: !preserveMapOffset});
+ this.fire('moveend', {hard: true});

app.setFollowingUser(backupFollowed);
},

"Pedro Pinto Silva (via github)"

unread,
Feb 6, 2026, 8:55:46 AMFeb 6
to collaboraon...@googlegroups.com
browser/src/control/Control.TopToolbar.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)

New commits:
commit 9044fbb7ef8309a74ff3bcfb811c391475a93a6e
Author: Pedro Pinto Silva <pedro...@collabora.com>
AuthorDate: Fri Feb 6 14:00:53 2026 +0100
Commit: Darshan-Upadhyay <61383886+Darsh...@users.noreply.github.com>
CommitDate: Fri Feb 6 19:25:27 2026 +0530

Compact view: Set again save and print compact icons

It seems someone rebased my PR:
https://github.com/CollaboraOnline/online/pull/14141
and solved the conflicts my removing the icon: compact_save.svg and
icon: compact_print.svg that was added previously in
https://github.com/CollaboraOnline/online/pull/14277

Add those changes back.

Signed-off-by: Pedro Pinto Silva <pedro...@collabora.com>
Change-Id: Iba629bc3acd96f9fd16a6d3d8bef8bc08f758f8e

diff --git a/browser/src/control/Control.TopToolbar.js b/browser/src/control/Control.TopToolbar.js
index 4c674ea8a5..2579e9cc6b 100644
--- a/browser/src/control/Control.TopToolbar.js
+++ b/browser/src/control/Control.TopToolbar.js
@@ -102,11 +102,11 @@ class TopToolbar extends JSDialog.Toolbar {
getToolItems() {
var saveGroup = [
{type: 'customtoolitem', id: 'closemobile', desktop: false, mobile: false, tablet: true, visible: false},
- {type: 'customtoolitem', id: 'save', command: 'save', text: _UNO('.uno:Save'), lockUno: '.uno:Save'},
+ {type: 'customtoolitem', id: 'save', command: 'save', text: _UNO('.uno:Save'), lockUno: '.uno:Save', icon: 'compact_save.svg'},
];
var printGroup = [
- {type: 'customtoolitem', id: 'print', command: 'print', text: _UNO('.uno:Print'), mobile: false, tablet: false, lockUno: '.uno:Print'},
- {type: 'menubutton', id: 'printoptions', command: 'printoptions', noLabel: true, text: _UNO('.uno:Print', 'text'), mobile: false, tablet: false, lockUno: '.uno:Print',
+ {type: 'customtoolitem', id: 'print', command: 'print', text: _UNO('.uno:Print'), mobile: false, tablet: false, lockUno: '.uno:Print', icon: 'compact_print.svg'},
+ {type: 'menubutton', id: 'printoptions', command: 'printoptions', noLabel: true, text: _UNO('.uno:Print', 'text'), mobile: false, tablet: false, lockUno: '.uno:Print', icon: 'compact_print.svg',
menu: [
{id: 'print-active-sheet', action: 'print-active-sheet', text: _('Active Sheet')},
{id: 'print-all-sheets', action: 'print-all-sheets', text: _('All Sheets')},

"Banobe Pascal (via github)"

unread,
Feb 6, 2026, 9:08:58 AMFeb 6
to collaboraon...@googlegroups.com
browser/src/app/iface/Map.Interface.ts | 1
browser/src/control/Control.NotebookbarBase.ts | 15 +++++
browser/src/control/jsdialog/Definitions.Menu.ts | 60 ++++++++++++++++++++++
browser/src/control/jsdialog/Definitions.Types.ts | 1
browser/src/control/jsdialog/Util.Dropdown.ts | 15 +++++
5 files changed, 92 insertions(+)

New commits:
commit 808f77dd9eb881ac2193da9beb04d22736cb8492
Author: Banobe Pascal <banobe...@collabora.com>
AuthorDate: Fri Feb 6 14:02:56 2026 +0300
Commit: pedropintosilva <65948705+ped...@users.noreply.github.com>
CommitDate: Fri Feb 6 15:08:10 2026 +0100

Table Design: update menu button entries to reflect the selected table style

- Synchronizes table style change events with the menu button selection state.
- The menu entries are updated based on the style applied to the currently selected table,
avoiding the default selection of the “None” entry and ensuring the menu state accurately
reflects the active table style.

Signed-off-by: Banobe Pascal <banobe...@collabora.com>
Change-Id: Ib31518c34932ad1fed7fc7c070fa30b6e53bf1e9

diff --git a/browser/src/app/iface/Map.Interface.ts b/browser/src/app/iface/Map.Interface.ts
index 7edac81bbd..708ecb7d3d 100644
--- a/browser/src/app/iface/Map.Interface.ts
+++ b/browser/src/app/iface/Map.Interface.ts
@@ -32,6 +32,7 @@ interface MapInterface extends Evented {

stateChangeHandler: {
getItemValue(unoCmd: string): string;
+ setItemValue(unoCmd: string, value: any): void;
};

sendUnoCommand(unoCmd: string): void;
diff --git a/browser/src/control/Control.NotebookbarBase.ts b/browser/src/control/Control.NotebookbarBase.ts
index 33f47dffa9..82df5286dd 100644
--- a/browser/src/control/Control.NotebookbarBase.ts
+++ b/browser/src/control/Control.NotebookbarBase.ts
@@ -83,11 +83,26 @@ class NotebookbarBase extends JSDialogComponent {
protected onJSUpdate(e: any) {
if (super.onJSUpdate(e)) {
this.impl?.setInitialized(true);
+ this._updateTableStyleStatus(e.data);
return true;
}
return false;
}

+ private _updateTableStyleStatus(data: any) {
+ const control = data.control;
+ if (
+ control.id === 'tablestyles_cb2' &&
+ control.selectedEntries.length > 0
+ ) {
+ const index = parseInt(control.selectedEntries[0]);
+ this.map['stateChangeHandler'].setItemValue(
+ '.uno:TableStyleStatus',
+ index,
+ );
+ }
+ }
+
protected onJSAction(e: any) {
if (super.onJSAction(e)) {
this.impl?.setInitialized(true);
diff --git a/browser/src/control/jsdialog/Definitions.Menu.ts b/browser/src/control/jsdialog/Definitions.Menu.ts
index cd88136354..7ffbf640f1 100644
--- a/browser/src/control/jsdialog/Definitions.Menu.ts
+++ b/browser/src/control/jsdialog/Definitions.Menu.ts
@@ -1714,301 +1714,361 @@ menuDefinitions.set('TableStylesMenu', [
{
text: _('Table Style Light 1'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_light_one',
},
{
text: _('Table Style Light 2'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_light_two',
},
{
text: _('Table Style Light 3'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_light_three',
},
{
text: _('Table Style Light 4'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_light_four',
},
{
text: _('Table Style Light 5'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_light_five',
},
{
text: _('Table Style Light 6'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_light_six',
},
{
text: _('Table Style Light 7'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_light_seven',
},
{
text: _('Table Style Light 8'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_light_eight',
},
{
text: _('Table Style Light 9'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_light_nine',
},
{
text: _('Table Style Light 10'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_light_ten',
},
{
text: _('Table Style Light 11'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_light_eleven',
},
{
text: _('Table Style Light 12'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_light_twelve',
},
{
text: _('Table Style Light 13'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_light_thirteen',
},
{
text: _('Table Style Light 14'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_light_fourteen',
},
{
text: _('Table Style Light 15'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_light_fifteen',
},
{
text: _('Table Style Light 16'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_light_sixteen',
},
{
text: _('Table Style Light 17'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_light_seventeen',
},
{
text: _('Table Style Light 18'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_light_eighteen',
},
{
text: _('Table Style Light 19'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_light_nineteen',
},
{
text: _('Table Style Light 20'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_light_twenty',
},
{
text: _('Table Style Light 21'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_light_twentyone',
},
{
text: _('Table Style Medium 1'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_one',
},
{
text: _('Table Style Medium 2'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_two',
},
{
text: _('Table Style Medium 3'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_three',
},
{
text: _('Table Style Medium 4'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_four',
},
{
text: _('Table Style Medium 5'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_five',
},
{
text: _('Table Style Medium 6'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_six',
},
{
text: _('Table Style Medium 7'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_seven',
},
{
text: _('Table Style Medium 8'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_eight',
},
{
text: _('Table Style Medium 9'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_nine',
},
{
text: _('Table Style Medium 10'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_ten',
},
{
text: _('Table Style Medium 11'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_eleven',
},
{
text: _('Table Style Medium 12'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_twelve',
},
{
text: _('Table Style Medium 13'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_thirteen',
},
{
text: _('Table Style Medium 14'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_fourteen',
},
{
text: _('Table Style Medium 15'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_fifteen',
},
{
text: _('Table Style Medium 16'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_sixteen',
},
{
text: _('Table Style Medium 17'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_seventeen',
},
{
text: _('Table Style Medium 18'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_eighteen',
},
{
text: _('Table Style Medium 19'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_nineteen',
},
{
text: _('Table Style Medium 20'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_twenty',
},
{
text: _('Table Style Medium 21'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_twentyone',
},
{
text: _('Table Style Medium 22'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_twentytwo',
},
{
text: _('Table Style Medium 23'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_twentythree',
},
{
text: _('Table Style Medium 24'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_twentyfour',
},
{
text: _('Table Style Medium 25'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_twentyfive',
},
{
text: _('Table Style Medium 26'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_twentysix',
},
{
text: _('Table Style Medium 27'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_twentyseven',
},
{
text: _('Table Style Medium 28'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_medium_twentyeight',
},
{
text: _('Table Style Dark 1'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_dark_one',
},
{
text: _('Table Style Dark 2'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_dark_two',
},
{
text: _('Table Style Dark 3'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_dark_three',
},
{
text: _('Table Style Dark 4'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_dark_four',
},
{
text: _('Table Style Dark 5'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_dark_five',
},
{
text: _('Table Style Dark 6'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_dark_six',
},
{
text: _('Table Style Dark 7'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_dark_seven',
},
{
text: _('Table Style Dark 8'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_dark_eight',
},
{
text: _('Table Style Dark 9'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_dark_nine',
},
{
text: _('Table Style Dark 10'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_dark_ten',
},
{
text: _('Table Style Dark 11'),
action: 'apply-table-style',
+ statusCommand: '.uno:TableStyleStatus',
img: 'table_dark_eleven',
},
] as Array<MenuDefinition>);
diff --git a/browser/src/control/jsdialog/Definitions.Types.ts b/browser/src/control/jsdialog/Definitions.Types.ts
index 153055eecb..5be5bb7a04 100644
--- a/browser/src/control/jsdialog/Definitions.Types.ts
+++ b/browser/src/control/jsdialog/Definitions.Types.ts
@@ -203,6 +203,7 @@ interface MenuDefinition extends WidgetJSON {
checked?: boolean; // state of check mark
items?: Array<any>;
selected?: boolean; // selected state for entry
+ statusCommand?: string; // UNO command used to retrieve the status/value of the entry
pos?: number | string; // identifier of an entry
}

diff --git a/browser/src/control/jsdialog/Util.Dropdown.ts b/browser/src/control/jsdialog/Util.Dropdown.ts
index 647db9de87..27b6d71f84 100644
--- a/browser/src/control/jsdialog/Util.Dropdown.ts
+++ b/browser/src/control/jsdialog/Util.Dropdown.ts
@@ -70,6 +70,21 @@ JSDialog.OpenDropdown = function (
else return false;
};

+ for (let i = 0; i < entries.length; i++) {
+ if (entries[i].statusCommand) {
+ const items = window.L.Map.THIS['stateChangeHandler'];
+ const val = items.getItemValue(entries[i].statusCommand);
+ if (val) {
+ const index = parseInt(val);
+ if (index === i) {
+ entries[i].selected = true;
+ } else {
+ entries[i].selected = false;
+ }
+ }
+ }
+ }
+
const shouldSelectFirstEntry =
entries.length > 0
? !entries.some((entry) => entry.selected === true)

"Pranam Lashkari (via github)"

unread,
Feb 7, 2026, 3:08:24 PMFeb 7
to collaboraon...@googlegroups.com
browser/src/slideshow/engine/SlideShowNavigator.ts | 1 +
1 file changed, 1 insertion(+)

New commits:
commit b9595f99036dce485f9db091b59205b35eb3bc50
Author: Pranam Lashkari <lpr...@collabora.com>
AuthorDate: Mon Feb 2 17:14:23 2026 +0100
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Sat Feb 7 20:07:48 2026 +0000

present-to all: mark mark as not following on button press

when user navigates slide with keyboard
during slideshow mark them as not following if
they are follower

problem:
when follower navigated the slides with keyboard
they were not marked as unfollowed so that's why
"follow presenter" button would not work
Signed-off-by: Pranam Lashkari <lpr...@collabora.com>
Change-Id: I9ac443ab21244a4f42fd36cc71c7c41568bfd3de

diff --git a/browser/src/slideshow/engine/SlideShowNavigator.ts b/browser/src/slideshow/engine/SlideShowNavigator.ts
index dda507fd7f..b6703e5a38 100644
--- a/browser/src/slideshow/engine/SlideShowNavigator.ts
+++ b/browser/src/slideshow/engine/SlideShowNavigator.ts
@@ -522,6 +522,7 @@ class SlideShowNavigator {
if (handler) {
aEvent.preventDefault();
aEvent.stopPropagation();
+ if (this.presenter.isFollower()) this.presenter.setFollowing(false);
handler();
}
}

"Szymon Kłos (via github)"

unread,
Feb 9, 2026, 10:22:05 AMFeb 9
to collaboraon...@googlegroups.com
browser/src/control/Control.Menubar.ts | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)

New commits:
commit ae26584e57cb8ee77d2dc8437dcccf1bc566685a
Author: Szymon Kłos <szymo...@collabora.com>
AuthorDate: Fri Feb 6 06:41:54 2026 +0000
Commit: Szymon Kłos <eszk...@gmail.com>
CommitDate: Mon Feb 9 16:21:16 2026 +0100

Check languages count before use

- I saw logs where we got empty language
- Be more defensive and log

Signed-off-by: Szymon Kłos <szymo...@collabora.com>
Change-Id: Ia753d78ce3b679e4d72cf23faed13c033988064f

diff --git a/browser/src/control/Control.Menubar.ts b/browser/src/control/Control.Menubar.ts
index 9d5c1a0a17..9d337c0c47 100644
--- a/browser/src/control/Control.Menubar.ts
+++ b/browser/src/control/Control.Menubar.ts
@@ -1916,7 +1916,13 @@ class Menubar extends window.L.Control {
if (unoCommand.startsWith(constLanguage)) {
unoCommand = constLanguage;
languageAndCode = this._map[constState].getItemValue(unoCommand);
- lang = languageAndCode.split(';')[0];
+ lang = languageAndCode.split(';');
+ if (lang.length) {
+ lang = lang[0];
+ } else {
+ lang = '';
+ app.console.error('Menubar _beforeShow: missing language');
+ }
data = decodeURIComponent($(aItem).data(constUno));
if (data.indexOf(lang) !== -1) {
$(aItem).addClass(constChecked);

"Miklos Vajna (via github)"

unread,
Feb 10, 2026, 3:31:59 AMFeb 10
to collaboraon...@googlegroups.com
browser/src/control/Control.UIManager.ts | 16 ++++++++++++++++
browser/src/layer/tile/CanvasTileLayer.js | 6 ++++++
2 files changed, 22 insertions(+)

New commits:
commit 289bc381b468fa08ce5129e36f45ef41a6d76d68
Author: Miklos Vajna <vmi...@collabora.com>
AuthorDate: Mon Feb 9 14:03:44 2026 +0100
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Tue Feb 10 08:30:57 2026 +0000

cool#13988 redline render mode: add a HTTP parameter to activate this mode on load

An integrator may want to load a text document which already has
redlines as the result if comparing two documents. In this case asking
the user to click on Review -> View Changes to enter the side-by-side
diff mode is not great.

When that button is pressed, we enter doc compare mode by dispatching
the comparechanges event.

Fix the problem by adding a new 'comparechanges' HTTP GET parameter,
similar to how 'startPresentation' can auto-start Impress files already.

Note that UIManager.initializeSpecializedUI() would run before
CanvasTileLayer._fitWidthZoom(), resulting in a view that has 2 pages
side by side, but the zoom would set to fit the width of one page. Fix
this by not zooming in when we're in doc compare mode, similar to how we
return early already in Calc and Draw.

Signed-off-by: Miklos Vajna <vmi...@collabora.com>
Change-Id: I18ef73063b43524f424c4c32a1c4b3007d12e7af

diff --git a/browser/src/control/Control.UIManager.ts b/browser/src/control/Control.UIManager.ts
index 783e3db56b..28e637b877 100644
--- a/browser/src/control/Control.UIManager.ts
+++ b/browser/src/control/Control.UIManager.ts
@@ -549,6 +549,17 @@ class UIManager extends window.L.Control {
this.map.quickFindPanel = JSDialog.QuickFindPanel(this.map);
this.map.addControl(this.map.quickFindPanel);
}
+
+ if (this.getStartCompareChanges()) {
+ // Don't switch to the comparechanges view yet, first wait for the
+ // document to be loaded to avoid accessing not yet initialized
+ // state.
+ const enterCompareChanges = () => {
+ app.dispatcher.dispatch('comparechanges');
+ this.map.off('docloaded', enterCompareChanges);
+ };
+ this.map.on('docloaded', enterCompareChanges);
+ }
}

if (this.map.isPresentationOrDrawing() && (isDesktop || window.mode.isTablet())) {
@@ -2351,4 +2362,9 @@ class UIManager extends window.L.Control {
const docType = this.map.getDocType();
return window.prefs.getBoolean(`${docType}.${name}`, defaultValue);
}
+
+ getStartCompareChanges(): boolean {
+ const compareChangesOption = window.coolParams.get('comparechanges');
+ return compareChangesOption === 'true' || compareChangesOption === '1';
+ }
}
diff --git a/browser/src/layer/tile/CanvasTileLayer.js b/browser/src/layer/tile/CanvasTileLayer.js
index 40a7b33b85..e1ae1f72d9 100644
--- a/browser/src/layer/tile/CanvasTileLayer.js
+++ b/browser/src/layer/tile/CanvasTileLayer.js
@@ -3149,6 +3149,12 @@ window.L.CanvasTileLayer = window.L.Layer.extend({
if (this.isCalc() || this.isDraw())
return;

+ if (this._map.uiManager.getStartCompareChanges()) {
+ // comparechanges view, don't zoom in, to have space for two pages side by
+ // side.
+ return;
+ }
+
if (this.isImpress() && !maxZoom)
maxZoom = 10;


"Sahil Gautam (via github)"

unread,
Feb 10, 2026, 7:25:30 AMFeb 10
to collaboraon...@googlegroups.com
browser/src/app/ViewLayoutWriter.ts | 35 ++++++++++++++++++++++++++++-------
browser/src/map/Map.js | 3 +++
2 files changed, 31 insertions(+), 7 deletions(-)

New commits:
commit db0011166aa49caa1196e0087a68e94f5f635dba
Author: Sahil Gautam <sahil....@collabora.com>
AuthorDate: Fri Feb 6 18:19:24 2026 +0530
Commit: Szymon Kłos <eszk...@gmail.com>
CommitDate: Tue Feb 10 13:24:43 2026 +0100

enhancement: make document movements smooth when making space for comments

Previously `documentZoomOrResizeCallback` was added in
`ViewLayoutWriter` and was used as a callback to zoom and resize
events where we scrolled off the document to the left to make some
space for the comments on the right. Before this, there was canvas
code which would recenter the document on zoom/resize.

When the user resized the window, these two approaches clashed with
each other and the document would be centered & then moved left & then
centered & then moved left... It would dance in the center as the user
changed the window size.

It was also hard to coordinate between events like if the user zooms
in/out, ideally we would want to calculate the document scroll
offset after the zoom calculations are done, but with the old
scroll mechanism that wasn't possible since everything was done via
"layouting tasks".

Now we add the `documentScrollOffset` to the code which recentered the
document. So all the calculations happen in one place at once and we
don't move the document around to make it flicker.

Signed-off-by: Sahil Gautam <sahil....@collabora.com>
Change-Id: Ic0753cec4685c92e10dfacd95a05ba7f7c3b5b32

diff --git a/browser/src/app/ViewLayoutWriter.ts b/browser/src/app/ViewLayoutWriter.ts
index 4853a0d91e..2f58ff3f5a 100644
--- a/browser/src/app/ViewLayoutWriter.ts
+++ b/browser/src/app/ViewLayoutWriter.ts
@@ -20,8 +20,7 @@ class ViewLayoutWriter extends ViewLayoutBase {

constructor() {
super();
- app.map.on('zoomlevelschange', this.documentZoomOrResizeCallback, this);
- app.map.on('resize', this.documentZoomOrResizeCallback, this);
+ app.map.on('zoomlevelschange', this.documentZoomCallback, this);
app.map.on('deleteannotation', this.annotationOperationsCallback, this);
app.map.on('insertannotation', this.annotationOperationsCallback, this);
app.map.on('importannotations', this.annotationOperationsCallback, this);
@@ -78,6 +77,19 @@ class ViewLayoutWriter extends ViewLayoutBase {
);
}

+ public getDocumentScrollOffset() {
+ if (this.commentsHiddenOrNotPresent()) return 0;
+ if (!this.viewHasEnoughSpaceToShowFullWidthComments()) return 0;
+
+ if (this.documentCanMoveLeft(true)) {
+ this.documentScrollOffset = 0;
+ this.documentScrollOffset = this.documentMoveLeftByOffset();
+ return this.documentScrollOffset;
+ }
+
+ return 0;
+ }
+
private recenterDocument() {
if (this.documentScrollOffset == 0) return;

@@ -111,19 +123,28 @@ class ViewLayoutWriter extends ViewLayoutBase {
}
}

- private adjustDocumentMarginsForComments(onZoomOrResize: boolean) {
+ private adjustDocumentMarginsForComments(onZoom: boolean) {
this.unselectSelectedCommentIfAny();

if (this.commentsHiddenOrNotPresent()) return;

- if (this.documentCanMoveLeft(onZoomOrResize)) {
- if (onZoomOrResize) this.documentScrollOffset = 0;
+ if (this.documentCanMoveLeft(onZoom)) {
+ if (onZoom) this.documentScrollOffset = 0;
this.documentScrollOffset = this.documentMoveLeftByOffset();
- this.scrollHorizontal(this.documentScrollOffset, true);
+ /*
+ * we scrollHorizontal by 1 to trigger the layouting tasks in
+ * the `ScrollSection.doMove` function, which calls `map.panBy`
+ * to adjust the document center, and there we add the offset
+ * to the x component to move the document to the left.
+ * we only do it for the zoom events because for resize,
+ * the layouting tasks are scheduled automatically by other
+ * code, like that in CommentListSection.ts (updateDOM)
+ */
+ this.scrollHorizontal(1, true);
}
}

- private documentZoomOrResizeCallback() {
+ private documentZoomCallback() {
this.adjustDocumentMarginsForComments(true);
}

diff --git a/browser/src/map/Map.js b/browser/src/map/Map.js
index b0570e264a..d2868bb80e 100644
--- a/browser/src/map/Map.js
+++ b/browser/src/map/Map.js
@@ -417,6 +417,9 @@ window.L.Map = window.L.Evented.extend({
if (!offset.x && !offset.y)
return this;

+ if (this._docLayer && this._docLayer._docType === 'text' && offset.x != 0)
+ offset.x += (app.activeDocument.activeLayout).getDocumentScrollOffset();
+
//If we pan too far then chrome gets issues with tiles
// and makes them disappear or appear in the wrong place (slightly offset) #2602
if (!this.getSize().contains(offset)) {

"Gökay Şatır (via github)"

unread,
Feb 11, 2026, 3:58:24 AMFeb 11
to collaboraon...@googlegroups.com
browser/src/app/TilesMiddleware.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)

New commits:
commit 7bd013d9b71c4eca98c7e614575d373d1d1283cc
Author: Gökay Şatır <gokay...@collabora.com>
AuthorDate: Tue Feb 10 16:38:06 2026 +0300
Commit: Gökay Şatır <gokay...@gmail.com>
CommitDate: Wed Feb 11 11:57:22 2026 +0300

Rename a variable for better readlability.

Signed-off-by: Gökay Şatır <gokay...@collabora.com>
Change-Id: I48fd170a19b91bd17be6aea36c8f993f86c9e177

diff --git a/browser/src/app/TilesMiddleware.ts b/browser/src/app/TilesMiddleware.ts
index 2bf140ab43..93f94742f3 100644
--- a/browser/src/app/TilesMiddleware.ts
+++ b/browser/src/app/TilesMiddleware.ts
@@ -1389,16 +1389,16 @@ class TileManager {
)
) {
this.beginTransaction();
- const queue_ = this.checkRequestTiles(
+ const queue = this.checkRequestTiles(
app.activeDocument.activeLayout.getCurrentCoordList(),
false,
);
this.endTransaction(null);
- return queue_;
+ return queue;
}

var tileRanges = this.pxBoundsToTileRanges(pixelBounds);
- var queue = [];
+ const queue = [];

// If we're looking for tiles for the current (visible) area, update tile distance.
if (isCurrent) {

"Hubert Figuière (via github)"

unread,
Feb 11, 2026, 3:20:01 PMFeb 11
to collaboraon...@googlegroups.com
browser/src/control/Control.Menubar.ts | 192 +++++++++++++++++----------------
1 file changed, 103 insertions(+), 89 deletions(-)

New commits:
commit da4848fa1b12541c8811fe2694946512622855f5
Author: Hubert Figuière <h...@collabora.com>
AuthorDate: Wed Feb 4 16:29:13 2026 -0500
Commit: Hubert Figuière <h...@figuiere.net>
CommitDate: Wed Feb 11 15:19:08 2026 -0500

menubar: When searching for commands always check for unoid

Add 'unoid' to most menuitm that have UNO as label.
This allow finding these with _getItemsForCommand()
so we can hide them by UNO commands even if
the menu is of type of "action".

Signed-off-by: Hubert Figuière <h...@collabora.com>
Change-Id: I037c61d663f92c394035e777c6d55d266174529d

diff --git a/browser/src/control/Control.Menubar.ts b/browser/src/control/Control.Menubar.ts
index 9d337c0c47..f93b711c0d 100644
--- a/browser/src/control/Control.Menubar.ts
+++ b/browser/src/control/Control.Menubar.ts
@@ -14,6 +14,9 @@ interface MenuItem {
id?: string;
name?: string;
uno?: string;
+ // Tag a menu item as being for the uno command, but don't do anything. If `uno` is set
+ // this shouldn't be. It is used by `_getItemsForCommand()`.
+ unoid?: string;
type?: string;
menu?: MenuItem[];
disabled?: boolean;
@@ -92,7 +95,7 @@ class Menubar extends window.L.Control {
],
text: [
{name: _UNO('.uno:PickList', 'text'), id: 'file', type: 'menu', menu: [
- {name: _UNO('.uno:Save', 'text'), id: 'save', type: 'action'},
+ {name: _UNO('.uno:Save', 'text'), unoid: '.uno:Save', id: 'save', type: 'action'},
{name: _UNO('.uno:SaveAs', 'text'), id: 'saveas', type: window.prefs.get('saveAsMode') === 'group' ? 'menu' : 'action', menu: [
{name: _('ODF text document (.odt)'), id: 'saveas-odt', type: 'action'},
{name: _('Word 2003 Document (.doc)'), id: 'saveas-doc', type: 'action'},
@@ -119,7 +122,7 @@ class Menubar extends window.L.Control {
{name: _UNO('.uno:Signature', 'text'), uno: '.uno:Signature', id: 'signature'},
{name: _('Options'), id: 'settings-dialog', type: 'action', mobileapp: false},
{type: 'separator'},
- {name: _UNO('.uno:Print', 'text'), id: 'print', type: 'action'},
+ {name: _UNO('.uno:Print', 'text'), unoid: '.uno:Print', id: 'print', type: 'action'},
{name: _('Close document'), id: 'closedocument', type: 'action'}
]},
{name: _UNO('.uno:EditMenu', 'text'), id: 'editmenu', type: 'menu', menu: [
@@ -158,10 +161,10 @@ class Menubar extends window.L.Control {
menu: (window.mode.isTablet() ? [
{name: _('Reset zoom'), id: 'zoomreset', type: 'action'},
] as MenuItem[] : [
- {name: _UNO('.uno:FullScreen', 'text'), id: 'fullscreen', type: 'action'},
+ {name: _UNO('.uno:FullScreen', 'text'), unoid: '.uno:FullScreen', id: 'fullscreen', type: 'action'},
{type: 'separator'},
- {name: _UNO('.uno:ZoomPlus', 'text'), id: 'zoomin', type: 'action'},
- {name: _UNO('.uno:ZoomMinus', 'text'), id: 'zoomout', type: 'action',},
+ {name: _UNO('.uno:ZoomPlus', 'text'), unoid: '.uno:ZoomPlus', id: 'zoomin', type: 'action'},
+ {name: _UNO('.uno:ZoomMinus', 'text'), unoid: '.uno:ZoomMinus', id: 'zoomout', type: 'action',},
{name: _('Fit to Screen'), id: 'fitwidthzoom', type: 'action'},
] as MenuItem[]).concat([
{type: 'separator'},
@@ -175,14 +178,14 @@ class Menubar extends window.L.Control {
{uno: '.uno:SidebarDeck.StyleListDeck', name: _('Style list')},
{uno: '.uno:Navigator', id: 'navigator'},
{type: 'separator'},
- {name: _UNO('.uno:ShowAnnotations', 'text'), id: 'showannotations', type: 'action'},
- {name: _UNO('.uno:ShowResolvedAnnotations', 'text'), id: 'showresolved', type: 'action'},
+ {name: _UNO('.uno:ShowAnnotations', 'text'), unoid: '.uno:ShowAnnotations', id: 'showannotations', type: 'action'},
+ {name: _UNO('.uno:ShowResolvedAnnotations', 'text'), unoid: '.uno:ShowResolvedAnnotations', id: 'showresolved', type: 'action'},
{uno: '.uno:ControlCodes'},
])},
{name: _UNO('.uno:InsertMenu', 'text'), id: 'insert', type: 'menu', menu: [
{name: _('Local Image...'), id: 'insertgraphic', type: 'action'},
- {name: _UNO('.uno:InsertGraphic', 'text'), id: 'insertgraphicremote', type: 'action'},
- {name: _UNO('.uno:InsertAnnotation', 'text'), id: 'insertcomment', type: 'action'},
+ {name: _UNO('.uno:InsertGraphic', 'text'), unoid: '.uno:InsertGraphic', id: 'insertgraphicremote', type: 'action'},
+ {name: _UNO('.uno:InsertAnnotation', 'text'), unoid: '.uno:InsertAnnotation', id: 'insertcomment', type: 'action'},
{uno: '.uno:InsertObjectChart'},
{name: _UNO('.uno:FontworkGalleryFloater'), uno: '.uno:FontworkGalleryFloater', id: 'fontworkgalleryfloater'},
{name: _UNO('.uno:DrawText'), uno: '.uno:DrawText'},
@@ -215,7 +218,7 @@ class Menubar extends window.L.Control {
{uno: '.uno:InsertBreak'},
{name: _UNO('.uno:InsertColumnBreak', 'spreadsheet'), uno: '.uno:InsertColumnBreak'},
{type: 'separator'},
- {name: _UNO('.uno:HyperlinkDialog'), id: 'inserthyperlink', type: 'action'},
+ {name: _UNO('.uno:HyperlinkDialog'), unoid: '.uno:HyperlinkDialog', id: 'inserthyperlink', type: 'action'},
{name: _('Smart Picker'), id: 'remotelink', type: 'action'},
{name: _('AI Assistant'), id: 'remoteaicontent', type: 'action'},
{type: 'separator'},
@@ -426,7 +429,7 @@ class Menubar extends window.L.Control {

presentation: [
{name: _UNO('.uno:PickList', 'presentation'), id: 'file', type: 'menu', menu: [
- {name: _UNO('.uno:Save', 'presentation'), id: 'save', type: 'action'},
+ {name: _UNO('.uno:Save', 'presentation'), unoid: '.uno:Save', id: 'save', type: 'action'},
{name: _UNO('.uno:SaveAs', 'presentation'), id: 'saveas', type: window.prefs.get('saveAsMode') === 'group' ? 'menu' : 'action', menu: [
{name: _('ODF presentation (.odp)'), id: 'saveas-odp', type: 'action'},
{name: _('PowerPoint 2003 Presentation (.ppt)'), id: 'saveas-ppt', type: 'action'},
@@ -479,10 +482,10 @@ class Menubar extends window.L.Control {
menu: (window.mode.isTablet() ? [
{name: _('Reset zoom'), id: 'zoomreset', type: 'action'},
] as MenuItem[] : [
- {name: _UNO('.uno:FullScreen', 'presentation'), id: 'fullscreen', type: 'action'},
+ {name: _UNO('.uno:FullScreen', 'presentation'), unoid: '.uno:FullScreen', id: 'fullscreen', type: 'action'},
{type: 'separator'},
- {name: _UNO('.uno:ZoomPlus', 'presentation'), id: 'zoomin', type: 'action'},
- {name: _UNO('.uno:ZoomMinus', 'presentation'), id: 'zoomout', type: 'action'},
+ {name: _UNO('.uno:ZoomPlus', 'presentation'), unoid: '.uno:ZoomPlus', id: 'zoomin', type: 'action'},
+ {name: _UNO('.uno:ZoomMinus', 'presentation'), unoid: '.uno:ZoomMinus', id: 'zoomout', type: 'action'},
{name: _('Reset zoom'), id: 'zoomreset', type: 'action'},
] as MenuItem[]).concat([
{type: 'separator'},
@@ -501,23 +504,23 @@ class Menubar extends window.L.Control {
{uno: '.uno:Navigator', id: 'navigator'},
{type: 'separator'},
{uno: '.uno:ModifyPage'},
- {name: _UNO('.uno:SlideChangeWindow', 'presentation', true), id: 'transitiondeck', type: 'action'},
+ {name: _UNO('.uno:SlideChangeWindow', 'presentation', true), unoid: '.uno:SlideChangeWindow', id: 'transitiondeck', type: 'action'},
{uno: '.uno:MasterSlidesPanel'},
{uno: '.uno:CustomAnimation'}, // core version
//{name: _UNO('.uno:CustomAnimation', 'presentation', true), id: 'animationdeck', type: 'action'}, // online version
])},
{name: _UNO('.uno:InsertMenu', 'presentation'), id: 'insert', type: 'menu', menu: [
{name: _('Local Image...'), id: 'insertgraphic', type: 'action'},
- {name: _UNO('.uno:InsertGraphic', 'presentation'), id: 'insertgraphicremote', type: 'action'},
+ {name: _UNO('.uno:InsertGraphic', 'presentation'), unoid: '.uno:InsertGraphic', id: 'insertgraphicremote', type: 'action'},
{name: _('Local Multimedia...'), id: 'insertmultimedia', type: 'action'},
- {name: _UNO('.uno:SelectBackground', 'presentation'), id: 'selectbackground', type: 'action'},
- {name: _UNO('.uno:InsertAnnotation', 'presentation'), id: 'insertcomment', type: 'action'},
+ {name: _UNO('.uno:SelectBackground', 'presentation'), unoid: '.uno:SelectBackground', id: 'selectbackground', type: 'action'},
+ {name: _UNO('.uno:InsertAnnotation', 'presentation'), unoid: '.uno:InsertAnnotation', id: 'insertcomment', type: 'action'},
{uno: '.uno:InsertObjectChart'},
{name: _UNO('.uno:FontworkGalleryFloater'), uno: '.uno:FontworkGalleryFloater', id: 'fontworkgalleryfloater'},
- {name: _UNO('.uno:Text', 'presentation'), id: 'inserttextbox', type: 'action'},
+ {name: _UNO('.uno:Text', 'presentation'), unoid: '.uno:Text', id: 'inserttextbox', type: 'action'},
{name: _UNO('.uno:VerticalText'), uno: '.uno:VerticalText'},
{type: 'separator'},
- {name: _UNO('.uno:HyperlinkDialog'), id: 'inserthyperlink', type: 'action'},
+ {name: _UNO('.uno:HyperlinkDialog'), unoid: '.uno:HyperlinkDialog', id: 'inserthyperlink', type: 'action'},
{name: _('Smart Picker'), id: 'remotelink', type: 'action'},
{name: _('AI Assistant'), id: 'remoteaicontent', type: 'action'},
{type: 'separator'},
@@ -570,11 +573,11 @@ class Menubar extends window.L.Control {
{uno: '.uno:TableDialog'}]
},
{name: _UNO('.uno:SlideMenu', 'presentation'), id: 'slide', type: 'menu', menu: [
- {name: _UNO('.uno:InsertSlide', 'presentation'), id: 'insertpage', type: 'action'},
- {name: _UNO('.uno:DuplicateSlide', 'presentation'), id: 'duplicatepage', type: 'action'},
- {name: _UNO('.uno:DeleteSlide', 'presentation'), id: 'deletepage', type: 'action'},
- {name: _UNO('.uno:ShowSlide', 'presentation'), id: 'showslide', type: 'action'},
- {name: _UNO('.uno:HideSlide', 'presentation'), id: 'hideslide', type: 'action'},
+ {name: _UNO('.uno:InsertSlide', 'presentation'), unoid: '.uno:InsertSlide', id: 'insertpage', type: 'action'},
+ {name: _UNO('.uno:DuplicateSlide', 'presentation'), unoid: '.uno:DuplicateSlide', id: 'duplicatepage', type: 'action'},
+ {name: _UNO('.uno:DeleteSlide', 'presentation'), unoid: '.uno:DeleteSlide', id: 'deletepage', type: 'action'},
+ {name: _UNO('.uno:ShowSlide', 'presentation'), unoid: '.uno:ShowSlide', id: 'showslide', type: 'action'},
+ {name: _UNO('.uno:HideSlide', 'presentation'), unoid: '.uno:HideSlide', id: 'hideslide', type: 'action'},
{type: 'separator'},
{name: _UNO('.uno:GotoSlide', 'presentation'), uno: '.uno:GotoPage'},
{type: 'separator', id: 'fullscreen-presentation-separator'},
@@ -609,14 +612,14 @@ class Menubar extends window.L.Control {

drawing: [
{name: _UNO('.uno:PickList', 'presentation'), id: 'file', type: 'menu', menu: [
- {name: _UNO('.uno:Save', 'presentation'), id: 'save', type: 'action'},
- {name: _UNO('.uno:SaveAs', 'presentation'), id: 'saveas', type: 'action'},
+ {name: _UNO('.uno:Save', 'presentation'), unoid: '.uno:Save', id: 'save', type: 'action'},
+ {name: _UNO('.uno:SaveAs', 'presentation'), unoid: '.uno:SaveAs', id: 'saveas', type: 'action'},
{name: _('Export as'), id: 'exportas', type: 'menu', menu: [
{name: _('PDF Document (.pdf)'), id: 'exportas-pdf', type: 'action'}
]},
{name: _('Save Comments'), id: 'savecomments', type: 'action'},
{name: _('Share...'), id:'shareas', type: 'action'},
- {name: _UNO('.uno:Print', 'presentation'), id: 'print', type: 'action'},
+ {name: _UNO('.uno:Print', 'presentation'), unoid: '.uno:Print', id: 'print', type: 'action'},
{name: _('See revision history'), id: 'rev-history', type: 'action'},
{name: !window.ThisIsAMobileApp ? _('Download as') : _('Export as'), id: 'downloadas', type: 'menu', menu: [
{name: _('PDF Document (.pdf)'), id: !window.ThisIsAMobileApp ? 'exportdirectpdf' : 'downloadas-pdf', type: 'action'},
@@ -646,10 +649,10 @@ class Menubar extends window.L.Control {
menu: (window.mode.isTablet() ? [
{name: _('Reset zoom'), id: 'zoomreset', type: 'action'},
] as MenuItem[] : [
- {name: _UNO('.uno:FullScreen', 'presentation'), id: 'fullscreen', type: 'action'},
+ {name: _UNO('.uno:FullScreen', 'presentation'), unoid: '.uno:FullScreen', id: 'fullscreen', type: 'action'},
{type: 'separator'},
- {name: _UNO('.uno:ZoomPlus', 'presentation'), id: 'zoomin', type: 'action'},
- {name: _UNO('.uno:ZoomMinus', 'presentation'), id: 'zoomout', type: 'action'},
+ {name: _UNO('.uno:ZoomPlus', 'presentation'), unoid: '.uno:ZoomPlus', id: 'zoomin', type: 'action'},
+ {name: _UNO('.uno:ZoomMinus', 'presentation'), unoid: '.uno:ZoomMinus', id: 'zoomout', type: 'action'},
{name: _('Reset zoom'), id: 'zoomreset', type: 'action'},
] as MenuItem[]).concat([
{type: 'separator'},
@@ -666,12 +669,12 @@ class Menubar extends window.L.Control {
])},
{name: _UNO('.uno:InsertMenu', 'presentation'), id: 'insert', type: 'menu', menu: [
{name: _('Local Image...'), id: 'insertgraphic', type: 'action'},
- {name: _UNO('.uno:InsertGraphic', 'presentation'), id: 'insertgraphicremote', type: 'action'},
- {name: _UNO('.uno:SelectBackground', 'presentation'), id: 'selectbackground', type: 'action'},
- {name: _UNO('.uno:InsertAnnotation', 'presentation'), id: 'insertcomment', type: 'action'},
+ {name: _UNO('.uno:InsertGraphic', 'presentation'), unoid: '.uno:InsertGraphic', id: 'insertgraphicremote', type: 'action'},
+ {name: _UNO('.uno:SelectBackground', 'presentation'), unoid: '.uno:SelectBackground', id: 'selectbackground', type: 'action'},
+ {name: _UNO('.uno:InsertAnnotation', 'presentation'), unoid: '.uno:InsertAnnotation', id: 'insertcomment', type: 'action'},
{uno: '.uno:InsertObjectChart'},
{type: 'separator'},
- {name: _UNO('.uno:HyperlinkDialog'), id: 'inserthyperlink', type: 'action'},
+ {name: _UNO('.uno:HyperlinkDialog'), unoid: '.uno:HyperlinkDialog', id: 'inserthyperlink', type: 'action'},
{name: _('Smart Picker'), id: 'remotelink', type: 'action'},
{name: _('AI Assistant'), id: 'remoteaicontent', type: 'action'},
{type: 'separator'},
@@ -687,7 +690,7 @@ class Menubar extends window.L.Control {
{name: _UNO('.uno:InsertPageTitleField', 'presentation'), uno: '.uno:InsertPageTitleField'},
{name: _UNO('.uno:InsertPagesField', 'presentation'), uno: '.uno:InsertPagesField'},
]},
- {name: _UNO('.uno:InsertSignatureLine'), id: 'insert-signatureline', type: 'action'},
+ {name: _UNO('.uno:InsertSignatureLine'), unoid: '.uno:InsertSignatureLine', id: 'insert-signatureline', type: 'action'},
]},
{name: _UNO('.uno:FormatMenu', 'presentation'), id: 'format', type: 'menu', menu: [
{uno: '.uno:FontDialog'},
@@ -723,9 +726,9 @@ class Menubar extends window.L.Control {
{name: _UNO('.uno:TableDialog', 'presentation'), uno: '.uno:TableDialog'}]
},
{name: _UNO('.uno:PageMenu', 'presentation'), type: 'menu', menu: [
- {name: _UNO('.uno:InsertPage', 'presentation'), id: 'insertpage', type: 'action'},
- {name: _UNO('.uno:DuplicatePage', 'presentation'), id: 'duplicatepage', type: 'action'},
- {name: _UNO('.uno:DeletePage', 'presentation'), id: 'deletepage', type: 'action'},
+ {name: _UNO('.uno:InsertPage', 'presentation'), unoid: '.uno:InsertPage', id: 'insertpage', type: 'action'},
+ {name: _UNO('.uno:DuplicatePage', 'presentation'), unoid: '.uno:DuplicatePage', id: 'duplicatepage', type: 'action'},
+ {name: _UNO('.uno:DeletePage', 'presentation'), unoid: '.uno:DeletePage', id: 'deletepage', type: 'action'},
{name: _UNO('.uno:GotoPage', 'presentation'), uno: '.uno:GotoPage'}]
},
{name: _UNO('.uno:ToolsMenu', 'presentation'), id: 'tools', type: 'menu', menu: [
@@ -748,7 +751,7 @@ class Menubar extends window.L.Control {

spreadsheet: [
{name: _UNO('.uno:PickList', 'spreadsheet'), id: 'file', type: 'menu', menu: [
- {name: _UNO('.uno:Save', 'spreadsheet'), id: 'save', type: 'action'},
+ {name: _UNO('.uno:Save', 'spreadsheet'), unoid: '.uno:Save', id: 'save', type: 'action'},
{name: _UNO('.uno:SaveAs', 'spreadsheet'), id: 'saveas', type: window.prefs.get('saveAsMode') === 'group' ? 'menu' : 'action', menu: [
{name: _('ODF spreadsheet (.ods)'), id: 'saveas-ods', type: 'action'},
{name: _('Excel 2003 Spreadsheet (.xls)'), id: 'saveas-xls', type: 'action'},
@@ -794,10 +797,10 @@ class Menubar extends window.L.Control {
menu: (window.mode.isTablet() ? [
{name: _('Reset zoom'), id: 'zoomreset', type: 'action'},
] as MenuItem[] : [
- {name: _UNO('.uno:FullScreen', 'spreadsheet'), id: 'fullscreen', type: 'action'},
+ {name: _UNO('.uno:FullScreen', 'spreadsheet'), unoid: '.uno:FullScreen', id: 'fullscreen', type: 'action'},
{type: 'separator'},
- {name: _UNO('.uno:ZoomPlus', 'text'), id: 'zoomin', type: 'action'},
- {name: _UNO('.uno:ZoomMinus', 'text'), id: 'zoomout', type: 'action',},
+ {name: _UNO('.uno:ZoomPlus', 'text'), unoid: '.uno:ZoomPlus', id: 'zoomin', type: 'action'},
+ {name: _UNO('.uno:ZoomMinus', 'text'), unoid: '.uno:ZoomMinus', id: 'zoomout', type: 'action',},
{name: _('Reset zoom'), id: 'zoomreset', type: 'action'},
] as MenuItem[]).concat([
{type: 'separator'},
@@ -820,11 +823,11 @@ class Menubar extends window.L.Control {
])},
{name: _UNO('.uno:InsertMenu', 'spreadsheet'), id: 'insert', type: 'menu', menu: [
{name: _('Local Image...'), id: 'insertgraphic', type: 'action'},
- {name: _UNO('.uno:InsertGraphic', 'spreadsheet'), id: 'insertgraphicremote', type: 'action'},
+ {name: _UNO('.uno:InsertGraphic', 'spreadsheet'), unoid: '.uno:InsertGraphic', id: 'insertgraphicremote', type: 'action'},
{name: _UNO('.uno:DataDataPilotRun', 'spreadsheet'), uno: '.uno:DataDataPilotRun'},
{name: _UNO('.uno:InsertCalcTable', 'spreadsheet'), uno: '.uno:InsertCalcTable'},
{name: _UNO('.uno:InsertSparkline', 'spreadsheet'), uno: '.uno:InsertSparkline'},
- {name: _UNO('.uno:InsertAnnotation', 'spreadsheet'), id: 'insertcomment', type: 'action'},
+ {name: _UNO('.uno:InsertAnnotation', 'spreadsheet'), unoid: '.uno:InsertAnnotation', id: 'insertcomment', type: 'action'},
{uno: '.uno:InsertObjectChart'},
{name: _UNO('.uno:FontworkGalleryFloater'), uno: '.uno:FontworkGalleryFloater', id: 'fontworkgalleryfloater'},
{name: _UNO('.uno:DrawText'), uno: '.uno:DrawText'},
@@ -1047,8 +1050,8 @@ class Menubar extends window.L.Control {
mobiletext: [
{name: _('Search'), id: 'recsearch', type: 'action'},
{name: _UNO('.uno:PickList', 'text'), id: 'file', type: 'menu', menu: [
- {name: _UNO('.uno:Save', 'text'), id: 'save', type: 'action'},
- {name: _UNO('.uno:SaveAs', 'text'), id: 'saveas', type: 'action'},
+ {name: _UNO('.uno:Save', 'text'), unoid: '.uno:Save', id: 'save', type: 'action'},
+ {name: _UNO('.uno:SaveAs', 'text'), unoid: '.uno:SaveAs', id: 'saveas', type: 'action'},
{name: _('Export as'), id: 'exportas', type: 'menu', menu: [
{name: _('PDF Document (.pdf)'), id: 'exportas-pdf', type: 'action'},
{name: _('EPUB (.epub)'), id: 'exportas-epub', type: 'action'}
@@ -1056,7 +1059,7 @@ class Menubar extends window.L.Control {
{name: _('Share...'), id:'shareas', type: 'action'},
{name: _('See revision history'), id: 'rev-history', type: 'action'},
{type: 'separator'},
- {name: _UNO('.uno:Print', 'text'), id: 'print', type: 'action'},
+ {name: _UNO('.uno:Print', 'text'), unoid: '.uno:Print', id: 'print', type: 'action'},
]},
{name: !window.ThisIsAMobileApp ? _('Download as') : _('Export as'), id: 'downloadas', type: 'menu', menu: [
{name: _('PDF Document (.pdf)'), id: !window.ThisIsAMobileApp ? 'exportdirectpdf' : 'downloadas-pdf', type: 'action'},
@@ -1087,7 +1090,7 @@ class Menubar extends window.L.Control {
{uno: '.uno:NextTrackedChange'}
]},
{name: _UNO('.uno:ViewMenu', 'text'), id: 'view', type: 'menu', menu: [
- {name: _UNO('.uno:FullScreen', 'text'), id: 'fullscreen', type: 'action', mobileapp: false},
+ {name: _UNO('.uno:FullScreen', 'text'), unoid: '.uno:FullScreen', id: 'fullscreen', type: 'action', mobileapp: false},
{uno: '.uno:ControlCodes', id: 'formattingmarks'},
{uno: '.uno:SpellOnline'},
{name: _UNO('.uno:ShowResolvedAnnotations', 'text'), id: 'showresolved', uno: '.uno:ShowResolvedAnnotations'},
@@ -1113,15 +1116,15 @@ class Menubar extends window.L.Control {
mobilepresentation: [
{name: _('Search'), id: 'recsearch', type: 'action'},
{name: _UNO('.uno:PickList', 'presentation'), id: 'file', type: 'menu', menu: [
- {name: _UNO('.uno:Save', 'presentation'), id: 'save', type: 'action'},
- {name: _UNO('.uno:SaveAs', 'presentation'), id: 'saveas', type: 'action'},
+ {name: _UNO('.uno:Save', 'presentation'), unoid: '.uno:Save', id: 'save', type: 'action'},
+ {name: _UNO('.uno:SaveAs', 'presentation'), unoid: '.uno:SaveAs', id: 'saveas', type: 'action'},
{name: _('Export as'), id: 'exportas', type: 'menu', menu: [
{name: _('PDF Document (.pdf)'), id: 'exportas-pdf', type: 'action'}
]},
{name: _('Share...'), id:'shareas', type: 'action'},
{name: _('See revision history'), id: 'rev-history', type: 'action'},
{type: 'separator'},
- {name: _UNO('.uno:Print', 'presentation'), id: 'print', type: 'action'},
+ {name: _UNO('.uno:Print', 'presentation'), uno: '.uno:Print', id: 'print', type: 'action'},
]},
{name: !window.ThisIsAMobileApp ? _('Download as') : _('Export as'), id:'downloadas', type: 'menu', menu: [
{name: _('PDF Document (.pdf)'), id: !window.ThisIsAMobileApp ? 'exportdirectpdf' : 'downloadas-pdf', type: 'action'},
@@ -1143,7 +1146,7 @@ class Menubar extends window.L.Control {
]},
{name: _UNO('.uno:ViewMenu', 'presentation'), id: 'view', type: 'menu', menu: [
{uno: '.uno:SpellOnline'},
- {name: _UNO('.uno:FullScreen', 'presentation'), id: 'fullscreen', type: 'action', mobileapp: false},
+ {name: _UNO('.uno:FullScreen', 'presentation'), unoid: '.uno:FullScreen', id: 'fullscreen', type: 'action', mobileapp: false},
{name: _('Dark Mode'), id: 'toggledarktheme', type: 'action'},
{name: _('Invert Background'), id: 'invertbackground', type: 'action'},
]
@@ -1164,9 +1167,9 @@ class Menubar extends window.L.Control {
]
},
{name: _UNO('.uno:SlideMenu', 'presentation'), id: 'slidemenu', type: 'menu', menu: [
- {name: _UNO('.uno:InsertSlide', 'presentation'), id: 'insertpage', type: 'action'},
- {name: _UNO('.uno:DuplicateSlide', 'presentation'), id: 'duplicatepage', type: 'action'},
- {name: _UNO('.uno:DeleteSlide', 'presentation'), id: 'deletepage', type: 'action'}]
+ {name: _UNO('.uno:InsertSlide', 'presentation'), unoid: '.uno:InsertSlide', id: 'insertpage', type: 'action'},
+ {name: _UNO('.uno:DuplicateSlide', 'presentation'), unoid: '.uno:DuplicateSlide', id: 'duplicatepage', type: 'action'},
+ {name: _UNO('.uno:DeleteSlide', 'presentation'), unoid: '.uno:DeleteSlide', id: 'deletepage', type: 'action'}]
},
{name: _UNO('.uno:RunMacro'), id: 'runmacro', uno: '.uno:RunMacro'},
{name: _('Fullscreen presentation'), id: 'fullscreen-presentation', type: 'action'},
@@ -1178,13 +1181,13 @@ class Menubar extends window.L.Control {
mobiledrawing: [
{name: _('Search'), id: 'recsearch', type: 'action'},
{name: _UNO('.uno:PickList', 'presentation'), id: 'file', type: 'menu', menu: [
- {name: _UNO('.uno:Save', 'presentation'), id: 'save', type: 'action'},
- {name: _UNO('.uno:SaveAs', 'presentation'), id: 'saveas', type: 'action'},
+ {name: _UNO('.uno:Save', 'presentation'), unoid: '.uno:Save', id: 'save', type: 'action'},
+ {name: _UNO('.uno:SaveAs', 'presentation'), unoid: '.uno:SaveAs', id: 'saveas', type: 'action'},
{name: _('Export as'), id: 'exportas', type: 'menu', menu: [
{name: _('PDF Document (.pdf)'), id: 'exportas-pdf', type: 'action'}
]},
{name: _('Share...'), id:'shareas', type: 'action'},
- {name: _UNO('.uno:Print', 'presentation'), id: 'print', type: 'action'},
+ {name: _UNO('.uno:Print', 'presentation'), unoid: '.uno:Print', id: 'print', type: 'action'},
{name: _('See revision history'), id: 'rev-history', type: 'action'},
]},
{name: !window.ThisIsAMobileApp ? _('Download as') : _('Export as'), id:'downloadas', type: 'menu', menu: [
@@ -1204,7 +1207,7 @@ class Menubar extends window.L.Control {
]},
{name: _UNO('.uno:ViewMenu', 'presentation'), id: 'view', type: 'menu', menu: [
{uno: '.uno:SpellOnline'},
- {name: _UNO('.uno:FullScreen', 'presentation'), id: 'fullscreen', type: 'action', mobileapp: false},
+ {name: _UNO('.uno:FullScreen', 'presentation'), unoid: '.uno:FullScreen', id: 'fullscreen', type: 'action', mobileapp: false},
{name: _('Dark Mode'), id: 'toggledarktheme', type: 'action'},
{name: _('Invert Background'), id: 'invertbackground', type: 'action'},
]
@@ -1220,9 +1223,9 @@ class Menubar extends window.L.Control {
{name: _UNO('.uno:MergeCells', 'presentation'), uno: '.uno:MergeCells'}]
},
{name: _UNO('.uno:PageMenu', 'presentation'), id: 'pagemenu', type: 'menu', menu: [
- {name: _UNO('.uno:InsertPage', 'presentation'), id: 'insertpage', type: 'action'},
- {name: _UNO('.uno:DuplicatePage', 'presentation'), id: 'duplicatepage', type: 'action'},
- {name: _UNO('.uno:DeletePage', 'presentation'), id: 'deletepage', type: 'action'}]
+ {name: _UNO('.uno:InsertPage', 'presentation'), unoid: '.uno:InsertPage', id: 'insertpage', type: 'action'},
+ {name: _UNO('.uno:DuplicatePage', 'presentation'), unoid: '.uno:DuplicatePage', id: 'duplicatepage', type: 'action'},
+ {name: _UNO('.uno:DeletePage', 'presentation'), unoid: '.uno:DeletePage', id: 'deletepage', type: 'action'}]
},
{name: _UNO('.uno:RunMacro'), id: 'runmacro', uno: '.uno:RunMacro'},
{name: _('Latest Updates'), id: 'latestupdates', type: 'action', iosapp: false},
@@ -1233,15 +1236,15 @@ class Menubar extends window.L.Control {
mobilespreadsheet: [
{name: _('Search'), id: 'recsearch', type: 'action'},
{name: _UNO('.uno:PickList', 'spreadsheet'), id: 'file', type: 'menu', menu: [
- {name: _UNO('.uno:Save', 'spreadsheet'), id: 'save', type: 'action'},
- {name: _UNO('.uno:SaveAs', 'spreadsheet'), id: 'saveas', type: 'action'},
+ {name: _UNO('.uno:Save', 'spreadsheet'), unoid: '.uno:Save', id: 'save', type: 'action'},
+ {name: _UNO('.uno:SaveAs', 'spreadsheet'), unoid: '.uno:SaveAs', id: 'saveas', type: 'action'},
{name: _('Export as'), id: 'exportas', type: 'menu', menu: [
{name: _('PDF Document (.pdf)'), id: 'exportas-pdf', type: 'action'}
]},
{name: _('Share...'), id:'shareas', type: 'action'},
{name: _('See revision history'), id: 'rev-history', type: 'action'},
{type: 'separator'},
- {name: _UNO('.uno:Print', 'spreadsheet'), id: 'print', type: 'action'},
+ {name: _UNO('.uno:Print', 'spreadsheet'), unoid: '.uno:Print', id: 'print', type: 'action'},
{name: _('Define print area'), uno: '.uno:DefinePrintArea' },
{name: _('Remove print area'), uno: '.uno:DeletePrintArea' },
]},
@@ -1264,7 +1267,7 @@ class Menubar extends window.L.Control {
]},
{name: _UNO('.uno:ViewMenu', 'spreadsheet'), id: 'view', type: 'menu', menu: [
{uno: '.uno:SpellOnline'},
- {name: _UNO('.uno:FullScreen', 'presentation'), id: 'fullscreen', type: 'action', mobileapp: false},
+ {name: _UNO('.uno:FullScreen', 'presentation'), unoid: '.uno:FullScreen', id: 'fullscreen', type: 'action', mobileapp: false},
{name: _('Dark Mode'), id: 'toggledarktheme', type: 'action'},
{name: _('Invert Background'), id: 'invertbackground', type: 'action'},
]
@@ -1309,10 +1312,10 @@ class Menubar extends window.L.Control {
'text' : {
name: _UNO('.uno:InsertMenu', 'text'), id: 'insert', type: 'menu', menu: [
{name: _('Local Image...'), id: 'insertgraphic', type: 'action'},
- {name: _UNO('.uno:InsertGraphic', 'text'), id: 'insertgraphicremote', type: 'action'},
+ {name: _UNO('.uno:InsertGraphic', 'text'), unoid: '.uno:InsertGraphic', id: 'insertgraphicremote', type: 'action'},
{uno: '.uno:InsertObjectChart'},
- {name: _UNO('.uno:InsertAnnotation', 'text'), id: 'insertcomment', type: 'action'},
- {name: _UNO('.uno:TableMenu'), id: 'inserttable', type: 'action'},
+ {name: _UNO('.uno:InsertAnnotation', 'text'), unoid: '.uno:InsertAnnotation', id: 'insertcomment', type: 'action'},
+ {name: _UNO('.uno:TableMenu'), unoid: '.uno:TableMenu', id: 'inserttable', type: 'action'},
{type: 'separator'},
{name: _UNO('.uno:InsertField', 'text'), id: 'insertfield', type: 'menu', menu: [
{uno: '.uno:InsertPageNumberField'},
@@ -1335,8 +1338,8 @@ class Menubar extends window.L.Control {
{uno: '.uno:InsertPagebreak'},
{name: _UNO('.uno:InsertColumnBreak', 'spreadsheet'), uno: '.uno:InsertColumnBreak'},
{type: 'separator'},
- {name: _UNO('.uno:HyperlinkDialog'), id: 'inserthyperlink', type: 'action'},
- {name: _UNO('.uno:ShapesMenu'), id: 'insertshape', type: 'action'},
+ {name: _UNO('.uno:HyperlinkDialog'), unoid: '.uno:HyperlinkDialog', id: 'inserthyperlink', type: 'action'},
+ {name: _UNO('.uno:ShapesMenu'), unoid: '.uno:ShapesMenu', id: 'insertshape', type: 'action'},
{name: _UNO('.uno:FontworkGalleryFloater'), uno: '.uno:FontworkGalleryFloater', id: 'fontworkgalleryfloater'},
{name: _UNO('.uno:FormattingMarkMenu', 'text'), id: 'formattingmark', type: 'menu', menu: [
{uno: '.uno:InsertNonBreakingSpace'},
@@ -1359,12 +1362,12 @@ class Menubar extends window.L.Control {
'spreadsheet' : {
name: _UNO('.uno:InsertMenu', 'spreadsheet'), id: 'insert', type: 'menu', menu: [
{name: _('Local Image...'), id: 'insertgraphic', type: 'action'},
- {name: _UNO('.uno:InsertGraphic', 'spreadsheet'), id: 'insertgraphicremote', type: 'action'},
+ {name: _UNO('.uno:InsertGraphic', 'spreadsheet'), unoid: '.uno:InsertGraphic', id: 'insertgraphicremote', type: 'action'},
{uno: '.uno:InsertObjectChart'},
- {name: _UNO('.uno:InsertAnnotation', 'spreadsheet'), id: 'insertcomment', type: 'action'},
+ {name: _UNO('.uno:InsertAnnotation', 'spreadsheet'), unoid: '.uno:InsertAnnotation', id: 'insertcomment', type: 'action'},
{type: 'separator'},
- {name: _UNO('.uno:HyperlinkDialog'), id: 'inserthyperlink', type: 'action'},
- {name: _UNO('.uno:ShapesMenu'), id: 'insertshape', type: 'action'},
+ {name: _UNO('.uno:HyperlinkDialog'), unoid: '.uno:HyperlinkDialog', id: 'inserthyperlink', type: 'action'},
+ {name: _UNO('.uno:ShapesMenu'), unoid: '.uno:ShapesMenu', id: 'insertshape', type: 'action'},
{uno: '.uno:InsertCurrentDate'},
{uno: '.uno:InsertCurrentTime'},
// other fields need EditEngine context & can't be disabled in the menu.
@@ -1373,14 +1376,14 @@ class Menubar extends window.L.Control {
'presentation' : {
name: _UNO('.uno:InsertMenu', 'presentation'), id: 'insert', type: 'menu', menu: [
{name: _('Local Image...'), id: 'insertgraphic', type: 'action'},
- {name: _UNO('.uno:InsertGraphic', 'presentation'), id: 'insertgraphicremote', type: 'action'},
+ {name: _UNO('.uno:InsertGraphic', 'presentation'), unoid: '.uno:InsertGraphic', id: 'insertgraphicremote', type: 'action'},
{uno: '.uno:InsertObjectChart'},
- {name: _UNO('.uno:InsertAnnotation', 'presentation'), id: 'insertcomment', type: 'action'},
- {name: _UNO('.uno:TableMenu'), id: 'inserttable', type: 'action'},
- {name: _UNO('.uno:HyperlinkDialog'), id: 'inserthyperlink', type: 'action'},
- {name: _UNO('.uno:ShapesMenu'), id: 'insertshape', type: 'action'},
+ {name: _UNO('.uno:InsertAnnotation', 'presentation'), unoid: '.uno:InsertAnnotation', id: 'insertcomment', type: 'action'},
+ {name: _UNO('.uno:TableMenu'), unoid: '.uno:TableMenu', id: 'inserttable', type: 'action'},
+ {name: _UNO('.uno:HyperlinkDialog'), unoid: '.uno:HyperlinkDialog', id: 'inserthyperlink', type: 'action'},
+ {name: _UNO('.uno:ShapesMenu'), unoid: '.uno:ShapesMenu', id: 'insertshape', type: 'action'},
{name: _UNO('.uno:FontworkGalleryFloater'), uno: '.uno:FontworkGalleryFloater', id: 'fontworkgalleryfloater'},
- {name: _UNO('.uno:Text', 'presentation'), id: 'inserttextbox', type: 'action'},
+ {name: _UNO('.uno:Text', 'presentation'), unoid: '.uno:Text', id: 'inserttextbox', type: 'action'},
{name: _UNO('.uno:InsertField', 'text'), id: 'insertfield', type: 'menu', menu: [
{uno: '.uno:InsertDateFieldFix'},
{uno: '.uno:InsertDateFieldVar'},
@@ -1396,14 +1399,14 @@ class Menubar extends window.L.Control {
drawing : {
name: _UNO('.uno:InsertMenu', 'presentation'), id: 'insert', type: 'menu', menu: [
{name: _('Local Image...'), id: 'insertgraphic', type: 'action'},
- {name: _UNO('.uno:InsertGraphic', 'presentation'), id: 'insertgraphicremote', type: 'action'},
+ {name: _UNO('.uno:InsertGraphic', 'presentation'), unoid: '.uno:InsertGraphic', id: 'insertgraphicremote', type: 'action'},
{uno: '.uno:InsertObjectChart'},
- {name: _UNO('.uno:InsertAnnotation', 'presentation'), id: 'insertcomment', type: 'action'},
- {name: _UNO('.uno:TableMenu'), id: 'inserttable', type: 'action'},
- {name: _UNO('.uno:HyperlinkDialog'), id: 'inserthyperlink', type: 'action'},
- {name: _UNO('.uno:ShapesMenu'), id: 'insertshape', type: 'action'},
+ {name: _UNO('.uno:InsertAnnotation', 'presentation'), unoid: '.uno:InsertAnnotation', id: 'insertcomment', type: 'action'},
+ {name: _UNO('.uno:TableMenu'), unoid: '.uno:TableMenu', id: 'inserttable', type: 'action'},
+ {name: _UNO('.uno:HyperlinkDialog'), unoid: '.uno:HyperlinkDialog', id: 'inserthyperlink', type: 'action'},
+ {name: _UNO('.uno:ShapesMenu'), unoid: '.uno:ShapesMenu', id: 'insertshape', type: 'action'},
{name: _UNO('.uno:FontworkGalleryFloater'), uno: '.uno:FontworkGalleryFloater', id: 'fontworkgalleryfloater'},
- {name: _UNO('.uno:Text', 'presentation'), id: 'inserttextbox', type: 'action'},
+ {name: _UNO('.uno:Text', 'presentation'), unoid: '.uno:Text', id: 'inserttextbox', type: 'action'},
{name: _UNO('.uno:InsertField', 'text'), id: 'insertfield', type: 'menu', menu: [
{name: _UNO('.uno:InsertDateFieldFix', 'presentation'), uno: '.uno:InsertDateFieldFix'},
{name: _UNO('.uno:InsertDateFieldVar', 'presentation'), uno: '.uno:InsertDateFieldVar'},
@@ -2675,6 +2678,8 @@ class Menubar extends window.L.Control {
$(aItem).data('id', menu[i].id);
if (menu[i].uno !== undefined)
$(aItem).data('uno', menu[i].uno);
+ if (menu[i].unoid !== undefined)
+ $(aItem).data('unoid', menu[i].unoid);
aItem.tabIndex = 0;
}

@@ -2777,6 +2782,7 @@ class Menubar extends window.L.Control {
var items = this._getItems();
if (items == null)
return null;
+ const isUno = commandId.startsWith('.uno:');
var found = $(items).filter(function() {
var item = $(this.children[0]);
var type = item.data('type');
@@ -2784,7 +2790,15 @@ class Menubar extends window.L.Control {
if (type == 'unocommand') {
id = $(item).data('uno');
} else if (type == 'action') {
- id = $(item).data('id');
+ if (isUno) {
+ id = $(item).data('unoid');
+ if (!id) {
+ id = $(item).data('uno');
+ }
+ }
+ if (!id) {
+ id = $(item).data('id');
+ }
}
if (id && id == commandId) {
return true;

"Gökay Şatır (via github)"

unread,
Feb 12, 2026, 4:19:42 AMFeb 12
to collaboraon...@googlegroups.com
browser/src/app/ViewLayoutCompareChanges.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)

New commits:
commit 0308859a06b1dc4b9e7a120ee06769312ff6ed13
Author: Gökay Şatır <gokay...@collabora.com>
AuthorDate: Wed Feb 11 16:21:44 2026 +0300
Commit: Miklos Vajna <vmi...@collabora.com>
CommitDate: Thu Feb 12 10:18:34 2026 +0100

CompareChanges view: Update view data inside a layouting task.

It sets CSS properties. Without this, cursor is misplaced at start.

Signed-off-by: Gökay Şatır <gokay...@collabora.com>
Change-Id: Ia889bc033fb3e763b46ece72d6c10ef0b60ed466

diff --git a/browser/src/app/ViewLayoutCompareChanges.ts b/browser/src/app/ViewLayoutCompareChanges.ts
index cbdd6e3405..3316ccd648 100644
--- a/browser/src/app/ViewLayoutCompareChanges.ts
+++ b/browser/src/app/ViewLayoutCompareChanges.ts
@@ -24,7 +24,8 @@ class ViewLayoutCompareChanges extends ViewLayoutNewBase {
super();

this.adjustViewZoomLevel();
- this.updateViewData();
+
+ app.layoutingService.appendLayoutingTask(() => this.updateViewData());
}

public adjustViewZoomLevel() {

"Parth Raiyani (via github)"

unread,
Feb 12, 2026, 6:30:11 AMFeb 12
to collaboraon...@googlegroups.com
browser/src/control/Control.JSDialogBuilder.js | 10 +++++++---
browser/src/control/jsdialog/Widget.SidebarContainers.ts | 2 +-
2 files changed, 8 insertions(+), 4 deletions(-)

New commits:
commit 0862bab397752ad90d6313bb25f65e957a11149e
Author: Parth Raiyani <parth....@collabora.com>
AuthorDate: Thu Feb 12 11:34:43 2026 +0530
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Thu Feb 12 11:29:25 2026 +0000

fix: add aria-haspopup role for improved accessibility in dialog components

Changes:
1. enable 'popup' role for more options button
2. do not add 'aria-pressed', if element already have 'aria-haspopup' role

Change-Id: I8780b01f8156668bea5218d1b7cd2527c2231d93
Signed-off-by: Parth Raiyani <parth....@collabora.com>

diff --git a/browser/src/control/Control.JSDialogBuilder.js b/browser/src/control/Control.JSDialogBuilder.js
index c56396f54d..aa8ebfaac4 100644
--- a/browser/src/control/Control.JSDialogBuilder.js
+++ b/browser/src/control/Control.JSDialogBuilder.js
@@ -1732,6 +1732,8 @@ window.L.Control.JSDialogBuilder = window.L.Control.extend({
const isSplitButton = !!data.applyCallback;
const isDropdownButton = !!data.dropdown;

+ const hasPopupRole = data.aria && data.aria.role === 'popup';
+
/**
* Determines if the dropdown arrow should be interactive (focusable button) vs decorative (div).
* This affects ARIA attribute placement and arrowbackground element type creation.
@@ -1811,7 +1813,7 @@ window.L.Control.JSDialogBuilder = window.L.Control.extend({
button.accessKey = data.accessKey;

// if dropdown arrow does not exist or is not interactive then only button can have aria-haspopup
- if (hasPopUp && !isArrowInteractive)
+ if (hasPopUp && !isArrowInteractive || hasPopupRole)
button.setAttribute('aria-haspopup', true);

if (data.w2icon) {
@@ -1877,13 +1879,15 @@ window.L.Control.JSDialogBuilder = window.L.Control.extend({
const selectFn = () => {
window.L.DomUtil.addClass(button, 'selected');
window.L.DomUtil.addClass(div, 'selected');
- button.setAttribute('aria-pressed', true);
+ if(!button.hasAttribute('aria-haspopup'))
+ button.setAttribute('aria-pressed', true);
};

const unSelectFn = () => {
window.L.DomUtil.removeClass(button, 'selected');
window.L.DomUtil.removeClass(div, 'selected');
- button.setAttribute('aria-pressed', false);
+ if(!button.hasAttribute('aria-haspopup'))
+ button.setAttribute('aria-pressed', false);
};

if (data.command) {
diff --git a/browser/src/control/jsdialog/Widget.SidebarContainers.ts b/browser/src/control/jsdialog/Widget.SidebarContainers.ts
index 70eca6b282..e4118033d3 100644
--- a/browser/src/control/jsdialog/Widget.SidebarContainers.ts
+++ b/browser/src/control/jsdialog/Widget.SidebarContainers.ts
@@ -76,7 +76,7 @@ JSDialog.panel = function (
{
type: 'toolitem',
command: expanderData.command,
- aria: { label: moreOptionsText },
+ aria: { label: moreOptionsText, role: 'popup' },
icon: app.LOUtil.getIconNameOfCommand('morebutton'),
tooltip: moreOptionsText,
} as any as WidgetJSON, // FIXME: use toolitem JSON type

"Gökay Şatır (via github)"

unread,
Feb 13, 2026, 8:18:33 AMFeb 13
to collaboraon...@googlegroups.com
browser/src/app/TilesMiddleware.ts | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)

New commits:
commit c3188966abbd3d9821e65c5f1650969a35e3a85d
Author: Gökay Şatır <gokay...@collabora.com>
AuthorDate: Fri Feb 13 12:48:21 2026 +0300
Commit: Miklos Vajna <vmi...@collabora.com>
CommitDate: Fri Feb 13 14:17:54 2026 +0100

Tiles Compare-Changes view: Check both modes before filtering tiles out.

Issue: Sometimes there are missing tiles on the screen.
This change seems to improve the situation. Also, logically correct.

Signed-off-by: Gökay Şatır <gokay...@collabora.com>
Change-Id: I51672f58feb9fbbd91c63d1426c6ee421c889019

diff --git a/browser/src/app/TilesMiddleware.ts b/browser/src/app/TilesMiddleware.ts
index 7f5420fb5b..c12147d6fd 100644
--- a/browser/src/app/TilesMiddleware.ts
+++ b/browser/src/app/TilesMiddleware.ts
@@ -1293,7 +1293,7 @@ class TileManager {
return boundList.map((x: any) => this.pxBoundsToTileRange(x));
}

- private static updateTileDistance(tile: Tile, zoom: number) {
+ private static getModeArray() {
let modes = [app.map._docLayer._selectedMode];
if (
app.activeDocument &&
@@ -1302,6 +1302,11 @@ class TileManager {
// 2 modes are active at the same time in compare changes view mode.
modes = [TileMode.LeftSide, TileMode.RightSide];
}
+ return modes;
+ }
+
+ private static updateTileDistance(tile: Tile, zoom: number) {
+ const modes = this.getModeArray();
if (
tile.coords.z !== zoom ||
tile.coords.part !== app.map._docLayer._selectedPart ||
@@ -1421,10 +1426,12 @@ class TileManager {
const part: number = app.map._docLayer._selectedPart;
const mode: number = app.map._docLayer._selectedMode;

+ const modes = this.getModeArray();
+
for (let i = coordsQueue.length - 1; i > 0; i--) {
if (
coordsQueue[i].part !== part ||
- coordsQueue[i].mode !== mode ||
+ !modes.includes(coordsQueue[i].mode) ||
!this.tileNeedsFetch(coordsQueue[i].key())
) {
coordsQueue.splice(i, 1);

"Szymon Kłos (via github)"

unread,
Feb 14, 2026, 7:23:23 AMFeb 14
to collaboraon...@googlegroups.com
browser/src/app/ViewLayout.ts | 9 +++++++++
browser/src/map/Map.js | 3 ++-
2 files changed, 11 insertions(+), 1 deletion(-)

New commits:
commit 18564d1a2a333860adc2f90558ec1d97d74e97c9
Author: Szymon Kłos <szymo...@collabora.com>
AuthorDate: Sat Feb 14 10:32:43 2026 +0000
Commit: Szymon Kłos <eszk...@gmail.com>
CommitDate: Sat Feb 14 13:23:06 2026 +0100

zoom: add required method to base view

We got error when changing page size in mutli-page view:
global.js:598 Exception TypeError: app.activeDocument.activeLayout.getDocumentScrollOffset is not a function

Missing method comes from:
commit db0011166aa49caa1196e0087a68e94f5f635dba
enhancement: make document movements smooth when making space for comments

This mistake is caused by the class hierarchy:
We have ViewLayoutWriter which inherits from a base class,
but ViewLayoutMultiPage or Compare are also for Writer...

For now let's use the new comment-positioning code only in the
layout which was adjusted for them in mentioned commit.

Signed-off-by: Szymon Kłos <szymo...@collabora.com>
Change-Id: Ifb3157d6acab2446f7b7f9be76322a69b2dde7db

diff --git a/browser/src/app/ViewLayout.ts b/browser/src/app/ViewLayout.ts
index 06dfbc2782..a31fa04be0 100644
--- a/browser/src/app/ViewLayout.ts
+++ b/browser/src/app/ViewLayout.ts
@@ -36,6 +36,8 @@ class ScrollProperties {
moveBy: number[] | null = null; // Pending move event (pX, pY).
}

+// FIXME: should be abstract to split Writer and other layouts
+// so we can have abstract methods and be warned about missing bits
class ViewLayoutBase {
public readonly type: string = 'ViewLayoutBase';

@@ -190,6 +192,13 @@ class ViewLayoutBase {
return app.sectionContainer.getDocumentAnchorSection();
}

+ /// used in Views which can show pages (Writer) to determine left alignment
+ // FIXME: confusing name, should be abstract
+ public getDocumentScrollOffset(): number {
+ app.console.error('not implemented');
+ return 0;
+ }
+
private calculateHorizontalScrollLength(
documentAnchor: CanvasSectionObject,
): void {
diff --git a/browser/src/map/Map.js b/browser/src/map/Map.js
index d2868bb80e..96ddd5dcdb 100644
--- a/browser/src/map/Map.js
+++ b/browser/src/map/Map.js
@@ -417,7 +417,8 @@ window.L.Map = window.L.Evented.extend({
if (!offset.x && !offset.y)
return this;

- if (this._docLayer && this._docLayer._docType === 'text' && offset.x != 0)
+ if (this._docLayer && this._docLayer._docType === 'text' && offset.x != 0 &&
+ app.activeDocument.activeLayout.type === 'ViewLayoutWriter')
offset.x += (app.activeDocument.activeLayout).getDocumentScrollOffset();

"Vladyslav Grynovetskyy (via github)"

unread,
Feb 14, 2026, 7:42:41 AMFeb 14
to collaboraon...@googlegroups.com
browser/src/control/Toolbar.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

New commits:
commit 049ee2370ca7d68d5eb2616c77fa4f8daf2d7208
Author: Vladyslav Grynovetskyy <grynov...@gmail.com>
AuthorDate: Mon Feb 9 18:31:56 2026 +0100
Commit: Andras Timar <andras...@collabora.com>
CommitDate: Sat Feb 14 13:42:35 2026 +0100

browser: allow GotoPage command in read-only mode

Signed-off-by: Vladyslav Grynovetskyy <grynov...@gmail.com>
Change-Id: If64ab136e24166eed22251ca620b09cb78ce1b2d

diff --git a/browser/src/control/Toolbar.js b/browser/src/control/Toolbar.js
index 3b514a173c..9d09424680 100644
--- a/browser/src/control/Toolbar.js
+++ b/browser/src/control/Toolbar.js
@@ -383,7 +383,7 @@ window.L.Map.include({
'.uno:ShowResolvedAnnotations',
'.uno:ToolbarMode?Mode:string=notebookbar_online.ui', '.uno:ToolbarMode?Mode:string=Default',
'.uno:ExportToEPUB', '.uno:ExportToPDF', '.uno:ExportDirectToPDF', '.uno:MoveKeepInsertMode', '.uno:ShowRuler',
- '.uno:Navigator'];
+ '.uno:Navigator', '.uno:GotoPage'];
if (app.isCommentEditingAllowed()) {
allowedCommands.push('.uno:InsertAnnotation','.uno:DeleteCommentThread', '.uno:DeleteAnnotation', '.uno:DeleteNote',
'.uno:DeleteComment', '.uno:ReplyComment', '.uno:ReplyToAnnotation', '.uno:PromoteComment', '.uno:ResolveComment',

"Michael Meeks (via github)"

unread,
Feb 14, 2026, 2:19:45 PMFeb 14
to collaboraon...@googlegroups.com
browser/src/app/Socket.ts | 3 +++
1 file changed, 3 insertions(+)

New commits:
commit 13a5b95c2eb2a7832bd2ee0d920f55ea8813b6c2
Author: Michael Meeks <michae...@collabora.com>
AuthorDate: Sat Feb 14 18:12:42 2026 +0000
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Sat Feb 14 19:18:52 2026 +0000

browser: clear delayed messages on socket close to avoid races.

Potentially these delayed messages could be replayed on re-connection
causing various tricky to reproduce problems.

Change-Id: I8ef3b51d8fc200af93da4b944339d38f26a57568
Signed-off-by: Michael Meeks <michae...@collabora.com>

diff --git a/browser/src/app/Socket.ts b/browser/src/app/Socket.ts
index a731352b2b..ea5cc9ce28 100644
--- a/browser/src/app/Socket.ts
+++ b/browser/src/app/Socket.ts
@@ -472,6 +472,9 @@ class Socket {
this._map._docLayer._resetDocumentInfo();
}

+ // We don't want them back on re-connection.
+ this._delayedMessages = [];
+
if (isActive && this._reconnecting) {
// Don't show this before first transparently trying to reconnect.
this._map.fire('error', {

"Andras Timar (via github)"

unread,
Feb 16, 2026, 1:04:31 AMFeb 16
to collaboraon...@googlegroups.com
browser/src/control/jsdialog/Widget.MultilineEdit.js | 4 ++++
1 file changed, 4 insertions(+)

New commits:
commit 477623b4aca513dbbb2e99ae6dd040c2d4e22b6e
Author: Andras Timar <andras...@collabora.com>
AuthorDate: Sun Feb 15 13:38:40 2026 +0100
Commit: Szymon Kłos <eszk...@gmail.com>
CommitDate: Mon Feb 16 07:03:44 2026 +0100

jsdialog: support readonly property for multiline edit widgets

Handle the "readonly" property from core's DumpAsPropertyTree to
set the HTML textarea as readOnly. This makes read-only multiline
edits non-editable while still allowing text selection and copying,
unlike the cursor=false path which creates a <p> element.

Fixes the writable edit boxes in the View Certificate dialog's
Details and Certificate Path tabs.

Signed-off-by: Andras Timar <andras...@collabora.com>
Change-Id: If40bebe459807574631f951de5f358b31eaa3167

diff --git a/browser/src/control/jsdialog/Widget.MultilineEdit.js b/browser/src/control/jsdialog/Widget.MultilineEdit.js
index c3d9ad2c71..987a67c504 100644
--- a/browser/src/control/jsdialog/Widget.MultilineEdit.js
+++ b/browser/src/control/jsdialog/Widget.MultilineEdit.js
@@ -82,6 +82,10 @@ function _multiLineEditControl(parentContainer, data, builder, callback) {
edit.disabled = true;
}

+ if (controlType === 'textarea' && data.readonly === true) {
+ edit.readOnly = true;
+ }
+
function _keyupChangeHandler() {
if (callback)
callback(this.value);

"Andras Timar (via github)"

unread,
Feb 16, 2026, 4:09:00 AMFeb 16
to collaboraon...@googlegroups.com
browser/src/control/jsdialog/Widget.ColorPicker.ts | 16 ++++++++++------
1 file changed, 10 insertions(+), 6 deletions(-)

New commits:
commit 6ffacc14a782798552f2cc360f4f271013cac8b9
Author: Andras Timar <andras...@collabora.com>
AuthorDate: Sat Feb 14 16:03:53 2026 +0100
Commit: Miklos Vajna <vmi...@collabora.com>
CommitDate: Mon Feb 16 10:08:44 2026 +0100

color picker: use standard color names for non-theme palette entries

Theme color names were always looked up first by hex value, so when a
StandardColors entry like "Dark Red 2" shared the same hex as theme
"Accent 6", the tooltip showed "Accent 6" instead.

Only use theme color names when the entry actually has theme data.

Signed-off-by: Andras Timar <andras...@collabora.com>
Change-Id: Ibbf0ac8c522f267a7fe9a7a8732d519b66c3543c

diff --git a/browser/src/control/jsdialog/Widget.ColorPicker.ts b/browser/src/control/jsdialog/Widget.ColorPicker.ts
index 2b0d76a9bd..c30d16f9e6 100644
--- a/browser/src/control/jsdialog/Widget.ColorPicker.ts
+++ b/browser/src/control/jsdialog/Widget.ColorPicker.ts
@@ -141,12 +141,16 @@ function createColor(

// Set color tooltips
var colorTooltip;
- const found = themeColors.find(
- (item: ThemeColor) => item.Value.toLowerCase() === colorItem.toLowerCase(),
- );
- if (found) colorTooltip = found.Name;
- else if (window.app.colorNames) colorTooltip = findColorName(colorItem);
- else colorTooltip = _('Unknown color');
+ if (themeData) {
+ const found = themeColors.find(
+ (item: ThemeColor) =>
+ item.Value.toLowerCase() === colorItem.toLowerCase(),
+ );
+ if (found) colorTooltip = found.Name;
+ }
+ if (!colorTooltip && window.app.colorNames)
+ colorTooltip = findColorName(colorItem);
+ if (!colorTooltip) colorTooltip = _('Unknown color');

if (window.enableAccessibility) {
color.setAttribute('aria-label', colorTooltip);

"Gökay Şatır (via github)"

unread,
Feb 16, 2026, 4:22:38 AMFeb 16
to collaboraon...@googlegroups.com
browser/src/app/TilesMiddleware.ts | 2 --
1 file changed, 2 deletions(-)

New commits:
commit 80d2e16953b57f63a24c32456e8f2506968b58b3
Author: Gökay Şatır <gokay...@collabora.com>
AuthorDate: Fri Feb 13 16:38:30 2026 +0300
Commit: Miklos Vajna <vmi...@collabora.com>
CommitDate: Mon Feb 16 10:21:48 2026 +0100

Remove unused/forgotten variable.

Signed-off-by: Gökay Şatır <gokay...@collabora.com>
Change-Id: Ibdd928bc74b2fef1d7a5c096dd85253308213238

diff --git a/browser/src/app/TilesMiddleware.ts b/browser/src/app/TilesMiddleware.ts
index c12147d6fd..c0b5add4d1 100644
--- a/browser/src/app/TilesMiddleware.ts
+++ b/browser/src/app/TilesMiddleware.ts
@@ -1424,8 +1424,6 @@ class TileManager {
coordsQueue: Array<TileCoordData>,
) {
const part: number = app.map._docLayer._selectedPart;
- const mode: number = app.map._docLayer._selectedMode;
-
const modes = this.getModeArray();

"Andras Timar (via github)"

unread,
Feb 16, 2026, 4:26:49 AMFeb 16
to collaboraon...@googlegroups.com
browser/src/control/jsdialog/Widget.ColorPickerButton.js | 16 +++++++++++++++
1 file changed, 16 insertions(+)

New commits:
commit 1580c34594c9348ffddeb5ffa9cc52d76e2d4969
Author: Andras Timar <andras...@collabora.com>
AuthorDate: Sat Feb 14 17:39:34 2026 +0100
Commit: Miklos Vajna <vmi...@collabora.com>
CommitDate: Mon Feb 16 10:26:14 2026 +0100

color picker button: update tooltip when color changes

The color picker button tooltip (e.g. "Font Color (Dark Red 2)") was
set once from the server JSON and never updated when the color changed.
Now the updateFunction also looks up the current color name from
the standard palette and updates the tooltip accordingly.

Signed-off-by: Andras Timar <andras...@collabora.com>
Change-Id: I18e249ab66d51935dccc42f350512339d41540fe

diff --git a/browser/src/control/jsdialog/Widget.ColorPickerButton.js b/browser/src/control/jsdialog/Widget.ColorPickerButton.js
index 25a371f223..4ed3fd2061 100644
--- a/browser/src/control/jsdialog/Widget.ColorPickerButton.js
+++ b/browser/src/control/jsdialog/Widget.ColorPickerButton.js
@@ -217,6 +217,22 @@ JSDialog.colorPickerButton = function (parentContainer, data, builder) {
} else {
valueNode.style.borderColor = 'var(--color-border)';
}
+
+ // Update the button tooltip with the current color name
+ if (menubutton.container && data.text) {
+ var colorName = '';
+ var hexColor = selectedColor;
+ if (hexColor && hexColor[0] === '#') hexColor = hexColor.substr(1);
+ if (hexColor && hexColor !== 'transparent' && window.app.colorNames) {
+ var entry = window.app.colorNames.find(function (c) {
+ return c.hexCode.toLowerCase() === hexColor.toLowerCase();
+ });
+ if (entry) colorName = entry.name;
+ }
+ var tooltip = data.text;
+ if (colorName) tooltip += ' (' + colorName + ')';
+ menubutton.container.setAttribute('data-cooltip', tooltip);
+ }
};

builder.map.on(

"Henry Castro (via github)"

unread,
Feb 16, 2026, 6:43:16 AMFeb 16
to collaboraon...@googlegroups.com
browser/src/control/jsdialog/Widget.MenuButton.js | 4 ++++
1 file changed, 4 insertions(+)

New commits:
commit 83bce2821c4b16e6e9de594e6501a3a25ed53997
Author: Henry Castro <hca...@collabora.com>
AuthorDate: Wed Feb 11 08:24:31 2026 -0400
Commit: Henry Castro <hca...@collabora.com>
CommitDate: Mon Feb 16 07:43:07 2026 -0400

browser: a11y: remove aria-pressed attribute

After initialization, remove the aria-pressed attribute from
the menu button, as it is a different instance with specific
characteristics.

Change-Id: I00aabc8120c51bc1ea0804fb74660f25c54457a5
Signed-off-by: Henry Castro <hca...@collabora.com>

diff --git a/browser/src/control/jsdialog/Widget.MenuButton.js b/browser/src/control/jsdialog/Widget.MenuButton.js
index 80d92b7bc5..16673fd208 100644
--- a/browser/src/control/jsdialog/Widget.MenuButton.js
+++ b/browser/src/control/jsdialog/Widget.MenuButton.js
@@ -72,6 +72,10 @@ function _menubuttonControl (parentContainer, data, builder) {
var options = {hasDropdownArrow: menuEntries.length > 1};
var control = builder._unoToolButton(parentContainer, data, builder, options);

+ if (!window.L.DomUtil.hasClass(control.container, 'selected')) {
+ control.button.removeAttribute('aria-pressed');
+ }
+
var isSplitButton = !!data.applyCallback;
// can be function or string with command identifier
const applyCallback =

"Michael Meeks (via github)"

unread,
Feb 16, 2026, 9:40:48 AMFeb 16
to collaboraon...@googlegroups.com
browser/src/canvas/sections/CommentListSection.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

New commits:
commit 9b03ea694ec1dd7c56760dbf246b6a4e4ed83fbc
Author: Michael Meeks <michae...@collabora.com>
AuthorDate: Sat Feb 14 15:55:14 2026 +0000
Commit: Pranam Lashkari <plashk...@gmail.com>
CommitDate: Mon Feb 16 15:39:56 2026 +0100

Fix mixup between editorid and viewid for current editor check.

Signed-off-by: Michael Meeks <michae...@collabora.com>
Change-Id: I2ee3f819c050aa8508f4174715310843fe635c2d

diff --git a/browser/src/canvas/sections/CommentListSection.ts b/browser/src/canvas/sections/CommentListSection.ts
index d6b4c12c72..1e2c5ef6bc 100644
--- a/browser/src/canvas/sections/CommentListSection.ts
+++ b/browser/src/canvas/sections/CommentListSection.ts
@@ -1583,7 +1583,7 @@ export class CommentSection extends CanvasSectionObject {
}

private actionPerformedByCurrentUser(obj: any): boolean {
- return obj.comment.author === this.map._viewInfo[this.map._docLayer._editorId].username;
+ return obj.comment.author === this.map._viewInfo[this.map._docLayer._viewId].username;
}

public onACKComment (obj: any): void {

"Parth Raiyani (via github)"

unread,
Feb 16, 2026, 2:02:02 PMFeb 16
to collaboraon...@googlegroups.com
browser/src/app/A11yValidator.ts | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)

New commits:
commit c107ad825d07f6477362e67965de1e9eee1a916b
Author: Parth Raiyani <parth....@collabora.com>
AuthorDate: Mon Feb 16 23:04:50 2026 +0530
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Mon Feb 16 19:01:17 2026 +0000

a11y: add check for aria-controls

- add check for aria-controls in A11yValidator to make sure it points to an element exist in DOM

Signed-off-by: Parth Raiyani <parth....@collabora.com>
Change-Id: I983925d078e3014867815f81b0929ccd32d4ec78

diff --git a/browser/src/app/A11yValidator.ts b/browser/src/app/A11yValidator.ts
index 6228e9ea0c..f371e289d8 100644
--- a/browser/src/app/A11yValidator.ts
+++ b/browser/src/app/A11yValidator.ts
@@ -33,6 +33,7 @@ class A11yValidator {
this.checks.push(this.checkImageAltAttribute.bind(this));
this.checks.push(this.checkLabelElement.bind(this));
this.checks.push(this.checkElementHasLabel.bind(this));
+ this.checks.push(this.checkAriaControls.bind(this));
}

checkWidget(type: string, element: HTMLElement): void {
@@ -205,6 +206,28 @@ class A11yValidator {
}
}

+ // TODO: there are some elements on which aria-controls only added
+ // when the relevant element exist in DOM. Need to handle that case as well.
+ private checkAriaControls(type: string, element: HTMLElement): void {
+ const controlledElementId = element.getAttribute('aria-controls') || '';
+ if (controlledElementId.trim() !== '') {
+ const referencedElement = document.getElementById(controlledElementId);
+
+ if (!referencedElement) {
+ throw new A11yValidatorException(
+ `In '${this.getDialogTitle(element)}' at '${this.getElementPath(element)}': element is widget of type '${type}' has aria-control attribute but mentioned element does not exist in DOM. Only add this attribute when mentioned element exist in DOM.`,
+ );
+ }
+ }
+
+ for (let i = 0; i < element.children.length; i++) {
+ const child = element.children[i];
+ if (this.shouldCheckChild(child)) {
+ this.checkAriaControls(type, child as HTMLElement);
+ }
+ }
+ }
+
private shouldCheckChild(child: Element): boolean {
return (
child instanceof HTMLElement &&

"Szymon Kłos (via github)"

unread,
Feb 17, 2026, 3:41:22 AMFeb 17
to collaboraon...@googlegroups.com
browser/src/control/jsdialog/Util.Dropdown.ts | 17 ++++++++++-------
1 file changed, 10 insertions(+), 7 deletions(-)

New commits:
commit ac0d8b560a18f0a22dfec80c03886de832b5552a
Author: Szymon Kłos <szymo...@collabora.com>
AuthorDate: Tue Feb 17 06:12:22 2026 +0000
Commit: Szymon Kłos <eszk...@gmail.com>
CommitDate: Tue Feb 17 09:40:55 2026 +0100

jsdialog: make log for dropdown less verbose

- it wasn't critical
- for some unhandled actions logged we had innerCallback which
worked well

Signed-off-by: Szymon Kłos <szymo...@collabora.com>
Change-Id: Ic0e1421712fef4801d3e31b9161c0ea885f6c06c

diff --git a/browser/src/control/jsdialog/Util.Dropdown.ts b/browser/src/control/jsdialog/Util.Dropdown.ts
index ab1fa7b006..bf46673584 100644
--- a/browser/src/control/jsdialog/Util.Dropdown.ts
+++ b/browser/src/control/jsdialog/Util.Dropdown.ts
@@ -226,12 +226,8 @@ JSDialog.OpenDropdown = function (
JSDialog.CloseDropdown(id);
return;
} else {
- app.console.error(
- 'Dropdown: unhandled action: "' +
- eventType +
- '" for entry: "' +
- JSON.stringify(entry) +
- '"',
+ app.console.debug(
+ 'Dropdown: potential unhandled action: "' + eventType + '"',
);
}
} else if (eventType === 'hidedropdown') {
@@ -249,7 +245,14 @@ JSDialog.OpenDropdown = function (
return;

if (eventType === 'selected') JSDialog.CloseDropdown(id);
- else console.debug('Dropdown: unhandled action: "' + eventType + '"');
+
+ app.console.warn(
+ 'Dropdown: unhandled action: "' +
+ eventType +
+ '" for entry: "' +
+ JSON.stringify(entry) +
+ '"',
+ );
};
};
window.L.Map.THIS.fire('closepopups'); // close popups if a dropdown menu is opened

"Darshan-upadhyay1110 (via github)"

unread,
Feb 19, 2026, 8:42:43 AMFeb 19
to collaboraon...@googlegroups.com
browser/src/control/Control.JSDialogBuilder.js | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)

New commits:
commit 4abf7785b3efecd4cba5e67f530c2410812913e4
Author: Darshan-upadhyay1110 <darshan....@collabora.com>
AuthorDate: Thu Feb 19 11:32:53 2026 +0530
Commit: Szymon Kłos <eszk...@gmail.com>
CommitDate: Thu Feb 19 14:42:24 2026 +0100

Fix last save state duration for save icon

- Before this patch because now we use modalId attr the handleCustomTooltip method needs to consider ModalId and not elem ModalId
- this patch will fix that and now we can see what is the time difference between last save and current time
- as fallback use element id if model id is missing

Signed-off-by: Darshan-upadhyay1110 <darshan....@collabora.com>
Change-Id: I809b5624cb41a0be0868e3530b081773771d2fcf

diff --git a/browser/src/control/Control.JSDialogBuilder.js b/browser/src/control/Control.JSDialogBuilder.js
index 05288f65c2..c784398535 100644
--- a/browser/src/control/Control.JSDialogBuilder.js
+++ b/browser/src/control/Control.JSDialogBuilder.js
@@ -2029,7 +2029,7 @@ window.L.Control.JSDialogBuilder = window.L.Control.extend({
$(controls.label).on('click', clickFunction);
// We need a way to also handle the custom tooltip for any tool button like save in shortcut bar
if (data.isCustomTooltip) {
- this._handleCutomTooltip(div, builder);
+ this._handleCustomTooltip(div, builder);
}
else if (!hasLabel || hasShortcut) {
$(div).on('mouseenter', mouseEnterFunction);
@@ -2055,8 +2055,10 @@ window.L.Control.JSDialogBuilder = window.L.Control.extend({
return controls;
},

- _handleCutomTooltip: function(elem, builder) {
- switch (elem.id) {
+ _handleCustomTooltip: function(elem, builder) {
+ // Prefer modelid, fallback to id if modelid is missing
+ const lookupId = elem.getAttribute('modelid') || elem.id;
+ switch (lookupId) {
case 'save':
$(elem).on('mouseenter', window.touch.mouseOnly(function() {
if (builder.map.tooltip)

"Caolán McNamara (via github)"

unread,
Feb 20, 2026, 5:39:48 AMFeb 20
to collaboraon...@googlegroups.com
browser/src/control/Control.JSDialog.js | 16 +++++++++-------
browser/src/control/Control.NotebookbarBuilder.js | 12 ++++++++++++
browser/src/control/Control.TopToolbar.js | 11 +++++++----
browser/src/control/jsdialog/Util.Dropdown.ts | 3 ++-
browser/src/control/jsdialog/Widget.Combobox.js | 8 ++++++--
5 files changed, 36 insertions(+), 14 deletions(-)

New commits:
commit 4cc97f63b841f6c445e876d1812002c3b888c71e
Author: Caolán McNamara <caolan....@collabora.com>
AuthorDate: Wed Feb 18 21:15:50 2026 +0000
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Fri Feb 20 10:38:56 2026 +0000

add a way to allow a combobox to set the focus on activating an entry

The compact toolbar, Control.TopToolbar.js already attempted this
for the fontname, fontsize, style dropdowns. But that was clobbered
by the later default async focus-moving handlers.

So return an explicit 'focusHandled' from the handler to indicate that
focus was taken care of and leave it alone, make the compact toolbars
handlers use that and keep their in-document focus preference.

Explicitly pass that to the ondropdown, close things so they leave
that focus alone.

Then for the notebookbar do the same special casing thing as the compact
toolbar does for the fontname/fontsize dropdowns.

Signed-off-by: Caolán McNamara <caolan....@collabora.com>
Change-Id: I4e438658863dac68c6116e2bfaf1650c3bca255c

diff --git a/browser/src/control/Control.JSDialog.js b/browser/src/control/Control.JSDialog.js
index a19798d59f..acac6b5f62 100644
--- a/browser/src/control/Control.JSDialog.js
+++ b/browser/src/control/Control.JSDialog.js
@@ -87,7 +87,7 @@ window.L.Control.JSDialog = window.L.Control.extend({
return builder;
},

- close: function(id, sendCloseEvent) {
+ close: function(id, sendCloseEvent, focusHandled) {
if (id !== undefined && this.dialogs[id]) {
const dialog = this.dialogs[id];
if (!sendCloseEvent && dialog.overlay && !dialog.isSubmenu) {
@@ -99,7 +99,7 @@ window.L.Control.JSDialog = window.L.Control.extend({
clearTimeout(dialog.timeoutId);

if (dialog.isPopup)
- this.closePopover(id, sendCloseEvent);
+ this.closePopover(id, sendCloseEvent, focusHandled);
else
this.closeDialog(id, sendCloseEvent);
return true;
@@ -142,7 +142,7 @@ window.L.Control.JSDialog = window.L.Control.extend({

// sendCloseEvent means that we only send a command to the server
// we want to kill HTML popup when we receive feedback from the server
- closePopover: function(id, sendCloseEvent) {
+ closePopover: function(id, sendCloseEvent, focusHandled) {
if (id === undefined || !this.dialogs[id]) {
app.console.warn('missing popover data');
return;
@@ -169,12 +169,14 @@ window.L.Control.JSDialog = window.L.Control.extend({
popupParent._onDropDown(false);

// Need to change focus to last element before we clear the current dialog
- this.focusToLastElement(id);
+ if (!focusHandled)
+ this.focusToLastElement(id);
this.clearDialog(id);
return;
}

- this.focusToLastElement(id);
+ if (!focusHandled)
+ this.focusToLastElement(id);
},

onCloseAll: function() {
@@ -842,9 +844,9 @@ window.L.Control.JSDialog = window.L.Control.extend({
const dialogs = Object.keys(this.dialogs);
const hadOpenedDialog = dialogs.length > 0;

- const didClose = this.close(instance.id, false);
+ const didClose = this.close(instance.id, false, instance.focusHandled);

- if (didClose) {
+ if (didClose && !instance.focusHandled) {
// Manage focus
this.focusAfterClose(hadOpenedDialog, dialogs);
}
diff --git a/browser/src/control/Control.NotebookbarBuilder.js b/browser/src/control/Control.NotebookbarBuilder.js
index 97021bd7a3..11cc8359a4 100644
--- a/browser/src/control/Control.NotebookbarBuilder.js
+++ b/browser/src/control/Control.NotebookbarBuilder.js
@@ -24,6 +24,18 @@ window.L.Control.NotebookbarBuilder = window.L.Control.JSDialogBuilder.extend({
},

_overrideHandlers: function() {
+ var builder = this;
+ const comboboxesFocusingDocument = ['fontnamecombobox', 'fontsizecombobox', 'styles'];
+ this.callback = function(objectType, eventType, object, data, builderArg) {
+ if (eventType === 'selected'
+ && comboboxesFocusingDocument.indexOf(object.id) >= 0) {
+ builder._defaultCallbackHandler(objectType, eventType, object, data, builderArg);
+ builder.map.focus();
+ return 'focusHandled';
+ }
+ return builder._defaultCallbackHandler(objectType, eventType, object, data, builderArg);
+ };
+
this._controlHandlers['bigtoolitem'] = this._bigtoolitemHandler;
this._controlHandlers['combobox'] = this._comboboxControl;
this._controlHandlers['exportmenubutton'] = this._exportMenuButton;
diff --git a/browser/src/control/Control.TopToolbar.js b/browser/src/control/Control.TopToolbar.js
index 18be0c2b20..9d09654cef 100644
--- a/browser/src/control/Control.TopToolbar.js
+++ b/browser/src/control/Control.TopToolbar.js
@@ -68,7 +68,7 @@ class TopToolbar extends JSDialog.Toolbar {
else if (eventType === 'change')
this.map.applyFont(data);
this.map.focus();
- return;
+ return 'focusHandled';
}
if (object.id === 'fontsizecombobox') {
if (eventType === 'selected')
@@ -76,13 +76,16 @@ class TopToolbar extends JSDialog.Toolbar {
else if (eventType === 'change')
this.map.applyFontSize(data);
this.map.focus();
- return;
+ return 'focusHandled';
}
if (object.id === 'styles') {
- if (eventType === 'selected')
+ if (eventType === 'selected') {
this.onStyleSelect({target: {value: data.substr(data.indexOf(';') + 1)}});
- else if (eventType === 'change')
+ return 'focusHandled';
+ } else if (eventType === 'change') {
this.onStyleSelect({target: {value: data}});
+ return 'focusHandled';
+ }
return;
}

diff --git a/browser/src/control/jsdialog/Util.Dropdown.ts b/browser/src/control/jsdialog/Util.Dropdown.ts
index bf46673584..b3f0076968 100644
--- a/browser/src/control/jsdialog/Util.Dropdown.ts
+++ b/browser/src/control/jsdialog/Util.Dropdown.ts
@@ -262,12 +262,13 @@ JSDialog.OpenDropdown = function (
});
};

-JSDialog.CloseDropdown = function (id: string) {
+JSDialog.CloseDropdown = function (id: string, focusHandled?: boolean) {
window.L.Map.THIS.fire('jsdialog', {
data: {
id: _createDropdownId(id),
jsontype: 'dialog',
action: 'close',
+ focusHandled: focusHandled === true,
},
});
};
diff --git a/browser/src/control/jsdialog/Widget.Combobox.js b/browser/src/control/jsdialog/Widget.Combobox.js
index a0bd35d45e..c58a18b9e5 100644
--- a/browser/src/control/jsdialog/Widget.Combobox.js
+++ b/browser/src/control/jsdialog/Widget.Combobox.js
@@ -313,15 +313,19 @@ JSDialog.combobox = function (parentContainer, data, builder) {
var parentBuilder = builder;
var callback = function(objectType, eventType, object, data) {
// send command with correct WindowId (from parent, not dropdown)
+ let result;
if (eventType !== 'close')
- parentBuilder.callback(objectType, eventType, object, data, parentBuilder);
+ result = parentBuilder.callback(objectType, eventType, object, data, parentBuilder);

// close after selection
if (eventType === 'selected') {
container.onSelect(_extractPos(data));
container.onSetText(_extractText(data));

- JSDialog.CloseDropdown(comboboxId);
+ // Pass through if the parent callback has already set the focus
+ // somewhere that shouldn't be changed by the CloseDropdown, e.g.
+ // toolbar font name/size
+ JSDialog.CloseDropdown(comboboxId, result === 'focusHandled');
}

return true;

"Szymon Kłos (via github)"

unread,
Feb 20, 2026, 5:48:08 AMFeb 20
to collaboraon...@googlegroups.com
browser/src/control/Control.SaveState.ts | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)

New commits:
commit 59c4ace625137be8cc411359b67e4a35019c81f3
Author: Szymon Kłos <szymo...@collabora.com>
AuthorDate: Fri Feb 20 09:28:29 2026 +0000
Commit: Miklos Vajna <vmi...@collabora.com>
CommitDate: Fri Feb 20 11:47:26 2026 +0100

SaveState: don't use missing icon

- be sure item is initialized before use
- it can happen we hide save icon due to integration settings

regression from commit 6697c6d13f16ffd5c05600cec5e4cf1ffbd233d1
Don't rely on unstable id for save state widget

Signed-off-by: Szymon Kłos <szymo...@collabora.com>
Change-Id: Iccc2c9754b5530cf80d82d43f6cce8a36d030dbc

diff --git a/browser/src/control/Control.SaveState.ts b/browser/src/control/Control.SaveState.ts
index 6416cd4dc5..c2944f0f63 100644
--- a/browser/src/control/Control.SaveState.ts
+++ b/browser/src/control/Control.SaveState.ts
@@ -28,7 +28,12 @@ class SaveState {

initialize() {
this.saveEle = document.querySelector('[id^="save"].unotoolbutton');
- this.saveIconEl = this.saveEle.querySelector('img');
+ if (this.saveEle) {
+ this.saveIconEl = this.saveEle.querySelector('img');
+ } else {
+ app.console.debug('SaveState: no save icon - might be hidden');
+ this.saveIconEl = null;
+ }
}

// Function to show the saving status
@@ -44,7 +49,7 @@ class SaveState {
const savingText = _('Saving');
this.saveEle.style.setProperty('--save-state', `"${savingText}"`);
this.saveEle.classList.add('saving');
- this.saveIconEl.classList.add('rotate-icon'); // Start the icon rotation
+ this.saveIconEl?.classList.add('rotate-icon'); // Start the icon rotation
this.saveEle.setAttribute('disabled', 'true'); // Disable the button
}
}
@@ -55,9 +60,9 @@ class SaveState {

if (!this.saveEle) this.initialize();

- if (!this.saveEle.classList.contains('savemodified')) {
+ if (this.saveEle && !this.saveEle.classList.contains('savemodified')) {
this.saveEle.classList.remove('saving');
- this.saveIconEl.classList.remove('rotate-icon'); // Stop the icon rotation
+ this.saveIconEl?.classList.remove('rotate-icon'); // Stop the icon rotation
// Dynamically set the content string for saved state
const savedText = _('Saved');
this.saveEle.style.setProperty('--save-state', `"${savedText}"`);

"Gökay Şatır (via github)"

unread,
Feb 20, 2026, 9:23:06 AMFeb 20
to collaboraon...@googlegroups.com
browser/src/app/ViewLayoutCompareChanges.ts | 1 +
browser/src/app/ViewLayoutMultiPage.ts | 1 +
2 files changed, 2 insertions(+)

New commits:
commit 6491d93db5c2c87e1bd88488c49b6882f92251a7
Author: Gökay Şatır <gokay...@collabora.com>
AuthorDate: Fri Feb 20 13:47:24 2026 +0300
Commit: Gökay Şatır <gokay...@gmail.com>
CommitDate: Fri Feb 20 17:22:11 2026 +0300

Issue: Current zoom is not sent to the core side in multi page and compare changes views.
Resulting with the missing tiles.

Fix: Send the client zoom while updating the view data.

This also seems to fix below issue for me:
* Switch to compare-changes view.
* Scroll down.
* Tiles are not rendered until user clicks somewhere.

Signed-off-by: Gökay Şatır <gokay...@collabora.com>
Change-Id: Ib6970982e694100d0a85289c32ca4c52c518c738

diff --git a/browser/src/app/ViewLayoutCompareChanges.ts b/browser/src/app/ViewLayoutCompareChanges.ts
index 3316ccd648..1558613424 100644
--- a/browser/src/app/ViewLayoutCompareChanges.ts
+++ b/browser/src/app/ViewLayoutCompareChanges.ts
@@ -104,6 +104,7 @@ class ViewLayoutCompareChanges extends ViewLayoutNewBase {
if (app.map._docLayer?._cursorMarker)
app.map._docLayer._cursorMarker.update();

+ app.map._docLayer._sendClientZoom();
this.sendClientVisibleArea();

this.refreshCurrentCoordList();
diff --git a/browser/src/app/ViewLayoutMultiPage.ts b/browser/src/app/ViewLayoutMultiPage.ts
index ed030cf763..435c5fe5b7 100644
--- a/browser/src/app/ViewLayoutMultiPage.ts
+++ b/browser/src/app/ViewLayoutMultiPage.ts
@@ -232,6 +232,7 @@ class ViewLayoutMultiPage extends ViewLayoutNewBase {
if (app.map._docLayer?._cursorMarker)
app.map._docLayer._cursorMarker.update();

+ app.map._docLayer._sendClientZoom();
this.sendClientVisibleArea();

this.refreshCurrentCoordList();

"Jeremy Whiting (via github)"

unread,
Feb 21, 2026, 1:15:16 PMFeb 21
to collaboraon...@googlegroups.com
browser/src/control/Toolbar.js | 6 ++++++
1 file changed, 6 insertions(+)

New commits:
commit bba52f28cad37428fdce945b6daac177533bd53d
Author: Jeremy Whiting <jeremy....@collabora.com>
AuthorDate: Sun Dec 7 12:37:19 2025 -0700
Commit: Szymon Kłos <eszk...@gmail.com>
CommitDate: Sat Feb 21 19:14:32 2026 +0100

Fix initial font size when switching to compact view.

When switching from tabbed view to compact view the font size
is uninitialized and shows 6 even though the current selected
text is likely not 6pt.
Initialize the font size correctly.

Signed-off-by: Jeremy Whiting <jeremy....@collabora.com>
Change-Id: I5d04f5f3dc343288a79c176021e1df11bcde344b
(cherry picked from commit c08189d055026fde950d74a33e12874122faf95a)

diff --git a/browser/src/control/Toolbar.js b/browser/src/control/Toolbar.js
index 9dfbac33db..bab65846d0 100644
--- a/browser/src/control/Toolbar.js
+++ b/browser/src/control/Toolbar.js
@@ -100,6 +100,12 @@ window.L.Map.include({

this.off('commandstatechanged', onCommandStateChanged);
this.on('commandstatechanged', onCommandStateChanged);
+
+ // Initialize with current state value if available
+ var currentState = this['stateChangeHandler'].getItemValue('.uno:FontHeight');
+ if (currentState) {
+ onCommandStateChanged.call(this, {commandName: '.uno:FontHeight', state: currentState});
+ }
},

applyFont: function (fontName) {

"Banobe Pascal (via github)"

unread,
Feb 21, 2026, 1:54:59 PMFeb 21
to collaboraon...@googlegroups.com
browser/src/control/Control.NotebookbarCalc.js | 23 ++++++++++++++++++-----
1 file changed, 18 insertions(+), 5 deletions(-)

New commits:
commit 79f9d9f753d80d3a73cd2aae6a0527f2d95442a9
Author: Banobe Pascal <banobe...@collabora.com>
AuthorDate: Wed Feb 4 02:45:51 2026 +0300
Commit: Szymon Kłos <eszk...@gmail.com>
CommitDate: Sat Feb 21 19:54:22 2026 +0100

calc: Add themes widget and “Add Theme” button to Notebookbar

- Switch from the theme dialog to an icon view themes widget
in the Notebookbar.
- Added new "Add theme" bigtoolitem button

Signed-off-by: Banobe Pascal <banobe...@collabora.com>
Change-Id: I3233c35a915808b4ed707ba64cab93f17f8df14a

diff --git a/browser/src/control/Control.NotebookbarCalc.js b/browser/src/control/Control.NotebookbarCalc.js
index 67b2f8e772..eb93439f34 100644
--- a/browser/src/control/Control.NotebookbarCalc.js
+++ b/browser/src/control/Control.NotebookbarCalc.js
@@ -2884,12 +2884,25 @@ window.L.Control.NotebookbarCalc = window.L.Control.NotebookbarWriter.extend({
},
{ type: 'separator', id: 'format-sparkline-break', orientation: 'vertical' },
{
- 'id': 'format-theme-dialog',
+ 'id': 'themes-group',
+ 'type': 'overflowgroup',
+ 'name': _('Themes'),
+ 'nofold': true,
+ 'icon': 'lc_themesthames.svg',
+ 'children': [
+ {
+ 'id': 'iconview_theme_colors', // has to match core id
+ 'type': 'iconview'
+ }
+ ]
+ },
+ {
+ 'id': 'add-theme-dialog',
'type': 'bigtoolitem',
- 'text': _UNO('.uno:ThemeDialog'),
- 'command': '.uno:ThemeDialog',
- 'accessibility': { focusBack: false, combination: 'J', de: null }
- }
+ 'text': _UNO('.uno:AddTheme'),
+ 'command': '.uno:AddTheme',
+ 'accessibility': { focusBack: false, combination: 'AT', de: null }
+ },
];

return this.getTabPage('Format', content);

"Caolán McNamara (via github)"

unread,
Feb 21, 2026, 2:49:01 PMFeb 21
to collaboraon...@googlegroups.com
browser/src/map/handler/Map.WOPI.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

New commits:
commit e4a5732f4b2361f39e32152db0377f3cfcc45cd4
Author: Caolán McNamara <caolan....@collabora.com>
AuthorDate: Fri Feb 20 21:17:02 2026 +0000
Commit: Andras Timar <andras...@collabora.com>
CommitDate: Sat Feb 21 20:48:11 2026 +0100

tidy regex check

Anchor the regex so it doesn't match trailing text.

Signed-off-by: Caolán McNamara <caolan....@collabora.com>
Change-Id: I0b5bec455b0d5f0556323bcdd85329e0a455af2a

diff --git a/browser/src/map/handler/Map.WOPI.js b/browser/src/map/handler/Map.WOPI.js
index 99115334d2..0103639892 100644
--- a/browser/src/map/handler/Map.WOPI.js
+++ b/browser/src/map/handler/Map.WOPI.js
@@ -295,7 +295,7 @@ window.L.Map.WOPI = window.L.Handler.extend({
this._allowedOrigins = ancestors;
// convert to JS regexps from localhost:* to https*://localhost:.*
for (i = 0; i < ancestors.length; i++) {
- this._allowedOrigins[i] = '(http|https)://' + ancestors[i].replace(/:\*/, ':?.*');
+ this._allowedOrigins[i] = '^(http|https)://' + ancestors[i].replace(/:\*/, ':?.*') + '$';
}
}


"Darshan-upadhyay1110 (via github)"

unread,
Feb 23, 2026, 1:12:07 AMFeb 23
to collaboraon...@googlegroups.com
browser/src/control/Control.UIManager.ts | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)

New commits:
commit 305b3b479d07e19eeb0a7ec781c295769a072610
Author: Darshan-upadhyay1110 <darshan....@collabora.com>
AuthorDate: Fri Feb 20 16:47:20 2026 +0530
Commit: Darshan-Upadhyay <61383886+Darsh...@users.noreply.github.com>
CommitDate: Mon Feb 23 11:41:45 2026 +0530

fix startpresentation using url parameter not working

- Recent follow me presentation work get mangled with this old direct presentation start using url param
- this patch will fix the issue if url has startpresentation mentioned it will work accordingly and if not follow me work flow will work as it issue

Signed-off-by: Darshan-upadhyay1110 <darshan....@collabora.com>
Change-Id: Ide4a824e76e8385dc8bf497e6653566621e2f871

diff --git a/browser/src/control/Control.UIManager.ts b/browser/src/control/Control.UIManager.ts
index 81ebbc3a3a..3646b1ee5b 100644
--- a/browser/src/control/Control.UIManager.ts
+++ b/browser/src/control/Control.UIManager.ts
@@ -595,7 +595,10 @@ class UIManager extends window.L.Control {
// check for "presentation" dispatch event only after document gets fully loaded
// in case if the leader is defined we have to wait a little longer to get the viewer info
const startPresentation = () => {
- if (startFolloMePresntationGet) {
+ if (startPresentationGet === 'true' || startPresentationGet === '1') {
+ app.dispatcher.dispatch('presentation');
+ }
+ else if (startFolloMePresntationGet) {
const dispatchFollowPresentation = () => {
app.dispatcher.dispatch('followpresentation');
this.map.off('slideshowfollowon', dispatchFollowPresentation);
@@ -622,9 +625,6 @@ class UIManager extends window.L.Control {
this.map.on('slideshowfollowon', dispatchFollowPresentation);
}
}
- else if (startPresentationGet === 'true' || startPresentationGet === '1') {
- app.dispatcher.dispatch('presentation');
- }

// docloaded event is fired multiple times, unfortunately
// but presentation should start only once

"Szymon Kłos (via github)"

unread,
Feb 23, 2026, 2:13:14 AMFeb 23
to collaboraon...@googlegroups.com
browser/src/app/DocumentBase.ts | 2 ++
browser/src/app/DocumentView.ts | 3 +++
2 files changed, 5 insertions(+)

New commits:
commit a4803e27e9e4363ab73ae36d35dc69e465a79cea
Author: Szymon Kłos <szymo...@collabora.com>
AuthorDate: Fri Feb 20 19:13:21 2026 +0000
Commit: Szymon Kłos <eszk...@gmail.com>
CommitDate: Mon Feb 23 08:12:24 2026 +0100

canvas: be carefull with adding sections

- we had a bug where selection was not visible in
multi-user sessions (but only our own)

- it was easy to reproduce, just opened 3 Writer sessions
and tried to select any text in them

- while debugging I found the line in CanvasSectionObject
if (this.containerObject) { // Is section added to container.
was failing the condition what indicated the selection was
not added

- setActiveViewID now removes the old active view's section
before creating the new one

- Constructor defensively removes any existing section with
the same name before calling addSection

- we should add some warnings that section was not added

It seems to be regression from commit 737006cc0554334c009d7f94a7508c9b65f10047
Remove _textCSelections and add a DocumentViewBase class instance for current user's text selections.

Signed-off-by: Szymon Kłos <szymo...@collabora.com>
Change-Id: I861c968d976c38f1fbec7d380848afa7da4e1ff0

diff --git a/browser/src/app/DocumentBase.ts b/browser/src/app/DocumentBase.ts
index 52de56af76..923b7f5f3c 100644
--- a/browser/src/app/DocumentBase.ts
+++ b/browser/src/app/DocumentBase.ts
@@ -61,6 +61,8 @@ class DocumentBase {
if (this.activeViewID !== activeViewID) {
this.activeViewID = activeViewID;
this.activeView.clearTextSelection();
+ // Remove the old active view's section before creating a new one.
+ app.sectionContainer.removeSection(this.activeView.selectionSection.name);
this.activeView = new DocumentViewBase(this.activeViewID);
this.activeView.setColor(this.activeViewSelectionColor);
}
diff --git a/browser/src/app/DocumentView.ts b/browser/src/app/DocumentView.ts
index 5d92a987af..0f9452f288 100644
--- a/browser/src/app/DocumentView.ts
+++ b/browser/src/app/DocumentView.ts
@@ -31,6 +31,9 @@ class DocumentViewBase {
0,
this.color,
);
+ // Remove any stale section with the same name so addSection succeeds.
+ if (app.sectionContainer.doesSectionExist(this._selectionSection.name))
+ app.sectionContainer.removeSection(this._selectionSection.name);
app.sectionContainer.addSection(this._selectionSection);
this._selectionSection.setShowSection(false);
}

"Caolán McNamara (via github)"

unread,
Feb 23, 2026, 4:29:03 AMFeb 23
to collaboraon...@googlegroups.com
browser/src/control/jsdialog/Definitions.Types.ts | 1
browser/src/control/jsdialog/Widget.TreeView.ts | 33 ++++++++++++++++------
2 files changed, 26 insertions(+), 8 deletions(-)

New commits:
commit 7b5574860096a996794c0c60be2e8a3ca6b8da2b
Author: Caolán McNamara <caolan....@collabora.com>
AuthorDate: Tue Feb 17 20:55:53 2026 +0000
Commit: Szymon Kłos <eszk...@gmail.com>
CommitDate: Mon Feb 23 10:28:23 2026 +0100

Let core tell us what tree, treegrid, listbox, grid role a TreeView has

One advantage of this is that, generally, core knows what an initially
empty treeview can be used for, so those shouldn't be miscategorized,
and we can be confident that a 'tree' has effectively just one column.

With the addition of 'tree' then a child of that needs to be a treeitem.

Signed-off-by: Caolán McNamara <caolan....@collabora.com>
Change-Id: Id0540d8682e4ecf2e4a7c1851b1c8505cbb06176

diff --git a/browser/src/control/jsdialog/Definitions.Types.ts b/browser/src/control/jsdialog/Definitions.Types.ts
index ae7400075a..0201ed412f 100644
--- a/browser/src/control/jsdialog/Definitions.Types.ts
+++ b/browser/src/control/jsdialog/Definitions.Types.ts
@@ -387,6 +387,7 @@ interface TreeWidgetJSON extends WidgetJSON {
customEntryRenderer?: boolean;
noSearchField?: boolean; // When true, the widget shouldn't have a search field added
sortLocally?: boolean; // When true, the widget will run sort algorithm in JS isntead of callback (lists only)
+ role?: string; // ARIA role from core: 'tree', 'treegrid', 'listbox', or 'grid'
}

interface IconViewEntry {
diff --git a/browser/src/control/jsdialog/Widget.TreeView.ts b/browser/src/control/jsdialog/Widget.TreeView.ts
index 8fbebaab8a..bb42c05940 100644
--- a/browser/src/control/jsdialog/Widget.TreeView.ts
+++ b/browser/src/control/jsdialog/Widget.TreeView.ts
@@ -69,6 +69,7 @@ var lastClickHelperId = '';
class TreeViewControl {
_isRealTree: boolean;
_isListbox: boolean;
+ _containerRole: string;
_container: HTMLElement;
_tbody: HTMLElement;
_thead: HTMLElement = null;
@@ -367,7 +368,13 @@ class TreeViewControl {
);
this._rows.set(String(entry.row), tr);
tr.setAttribute('level', String(level));
- tr.setAttribute('role', this._isListbox ? 'option' : 'row');
+ const rowRole =
+ this._containerRole === 'tree'
+ ? 'treeitem'
+ : this._containerRole === 'listbox'
+ ? 'option'
+ : 'row';
+ tr.setAttribute('role', rowRole);

let dummyColumns = 0;
if (this._hasState) dummyColumns++;
@@ -774,7 +781,10 @@ class TreeViewControl {
const element = rowElements[i];

// setup properties
- if (!this._isListbox) {
+ if (
+ this._containerRole === 'grid' ||
+ this._containerRole === 'treegrid'
+ ) {
element.setAttribute('role', 'gridcell');
}
}
@@ -1465,6 +1475,8 @@ class TreeViewControl {
}

static isRealTree(data: TreeWidgetJSON) {
+ if (data.role) return data.role === 'tree' || data.role === 'treegrid';
+
let isRealTreeView = false;
for (var i in data.entries) {
if (data.entries[i].children && data.entries[i].children.length) {
@@ -1690,6 +1702,9 @@ class TreeViewControl {
'ui-treeview-expanded-content',
parent,
);
+ if (this._containerRole === 'tree') subGrid.setAttribute('role', 'group');
+ else if (this._containerRole === 'treegrid')
+ subGrid.setAttribute('role', 'rowgroup');
entryElements.push(subGrid);

let dummyColumns = 0;
@@ -1787,6 +1802,8 @@ class TreeViewControl {
}

static isListbox(data: TreeWidgetJSON): boolean {
+ if (data.role) return data.role === 'listbox';
+
if (TreeViewControl.isRealTree(data)) return false;

const columns = TreeViewControl.countColumns(data);
@@ -1810,6 +1827,9 @@ class TreeViewControl {
) {
this._isRealTree = TreeViewControl.isRealTree(data);
this._isListbox = TreeViewControl.isListbox(data);
+ this._containerRole =
+ data.role ||
+ (this._isRealTree ? 'treegrid' : this._isListbox ? 'listbox' : 'grid');
this._columns = TreeViewControl.countColumns(data);
this._hasState = TreeViewControl.hasState(data);
this._hasIcon = TreeViewControl.hasIcon(data);
@@ -1833,12 +1853,9 @@ class TreeViewControl {
this.setupKeyEvents(data, builder);
this.setupFocusOutHandler();

- if (this._isRealTree) {
- this._container.setAttribute('role', 'treegrid');
- if (!data.headers || data.headers.length === 0)
- window.L.DomUtil.addClass(this._container, 'ui-treeview-tree');
- } else if (this._isListbox) this._container.setAttribute('role', 'listbox');
- else this._container.setAttribute('role', 'grid');
+ this._container.setAttribute('role', this._containerRole);
+ if (this._isRealTree && (!data.headers || data.headers.length === 0))
+ window.L.DomUtil.addClass(this._container, 'ui-treeview-tree');

this.preprocessColumnData(data.entries);
this.fillHeaders(data, data.headers, builder);

"Banobe Pascal (via github)"

unread,
Feb 23, 2026, 5:43:22 AMFeb 23
to collaboraon...@googlegroups.com
browser/src/slideshow/engine/SlideShowNavigator.ts | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)

New commits:
commit 57c4a292bedd22f36bcf50841e6d9ee3ca0802c3
Author: Banobe Pascal <banobe...@collabora.com>
AuthorDate: Fri Feb 20 14:17:53 2026 +0300
Commit: Szymon Kłos <eszk...@gmail.com>
CommitDate: Mon Feb 23 11:42:39 2026 +0100

slideshow: fix Follow Presenter jumping to previous slide on re-follow

- Attendees re-following the presenter while already on the same slide were
unexpectedly moved to the previous slide.
- followLeaderSlide() triggered rewindAllEffects(), which fell back to
rewindToPreviousSlide() when no effects had started, causing the slide
jump.
- Added a guard using hasAnyEffectStarted() before calling rewindAllEffects().
- If no effects have started, animation state is synchronized via
skipNEffects() instead.

Signed-off-by: Banobe Pascal <banobe...@collabora.com>
Change-Id: I259b30b517cb53636bceac0c7a7e2dfcf2e6ae79

diff --git a/browser/src/slideshow/engine/SlideShowNavigator.ts b/browser/src/slideshow/engine/SlideShowNavigator.ts
index b6703e5a38..cd63a60215 100644
--- a/browser/src/slideshow/engine/SlideShowNavigator.ts
+++ b/browser/src/slideshow/engine/SlideShowNavigator.ts
@@ -253,9 +253,10 @@ class SlideShowNavigator {
if (this.presenter.isFollowing()) return;
this.presenter.setFollowing(true);
// const currentEffect = this.currentLeaderEffect;
- if (this.currentLeaderSlide === this.currentSlide)
- this.slideShowHandler.rewindAllEffects();
- else this.displaySlide(this.currentLeaderSlide, true);
+ if (this.currentLeaderSlide === this.currentSlide) {
+ if (this.slideShowHandler.hasAnyEffectStarted())
+ this.slideShowHandler.rewindAllEffects();
+ } else this.displaySlide(this.currentLeaderSlide, true);
this.slideShowHandler.skipNEffects(this.currentLeaderEffect);
}


"Banobe Pascal (via github)"

unread,
Feb 23, 2026, 5:53:44 AMFeb 23
to collaboraon...@googlegroups.com
browser/src/slideshow/SlideShowPresenter.ts | 90 ++++++++++++++++++++-
browser/src/slideshow/engine/SlideShowNavigator.ts | 3
2 files changed, 89 insertions(+), 4 deletions(-)

New commits:
commit 8165c51179c2b22cf3871bf83c52c544a3c672ce
Author: Banobe Pascal <banobe...@collabora.com>
AuthorDate: Fri Feb 20 05:27:21 2026 +0300
Commit: Szymon Kłos <eszk...@gmail.com>
CommitDate: Mon Feb 23 11:53:05 2026 +0100

Slideshow: disable prev/next buttons when following presenter

- Disable navigation buttons when the user is following a presenter, preventing
them from advancing beyond the presenter's current slide.
- Keep Prev disabled on the first slide; keep Next disabled when the attendee is on the
presenter’s slide, and enable Next when the presenter is ahead.
- Ensure disabled buttons do not trigger navigation or affect follow/stop-follow state.
- Update tooltips to provide status context to the user on prev and next buttons

Signed-off-by: Banobe Pascal <banobe...@collabora.com>
Change-Id: I14f0e8f714a644ec6fb896ab06f8e7ad515ae225

diff --git a/browser/src/slideshow/SlideShowPresenter.ts b/browser/src/slideshow/SlideShowPresenter.ts
index 97ebb6b807..5b8e649909 100644
--- a/browser/src/slideshow/SlideShowPresenter.ts
+++ b/browser/src/slideshow/SlideShowPresenter.ts
@@ -151,6 +151,8 @@ class SlideShowPresenter {
private _isFollower: boolean = false;
private _isFollowing: boolean = false;
private _followBtn: HTMLElement | null = null;
+ private _prevButton: HTMLImageElement | null = null;
+ private _nextButton: HTMLImageElement | null = null;

private showFollow(me: boolean) {
this._map.uiManager.showButton('slide-presentation-follow', !me);
@@ -280,6 +282,8 @@ class SlideShowPresenter {
this.showFollow(false);
break;
}
+
+ this.updateControls();
}

private _handlePresenterCanvasClick(event: any) {
@@ -639,9 +643,19 @@ class SlideShowPresenter {
}

private _onPrevNextSlide = (e: Event) => {
- if (this.isFollower()) this.setFollowing(false);
- if ((e.target as any).id === 'previous') this._onPrevSlide(e);
- else if ((e.target as any).id === 'next') this._onNextSlide(e);
+ const currentSlide = this._slideShowNavigator.currentSlideIndex;
+ const isFollowing = this.isFollowing();
+ if ((e.target as any).id === 'previous') {
+ if (this._canGoPrev(currentSlide)) {
+ this._onPrevSlide(e);
+ if (isFollowing) this.setFollowing(false);
+ }
+ } else if ((e.target as any).id === 'next') {
+ if (this._canGoNext(currentSlide)) {
+ this._onNextSlide(e);
+ if (isFollowing) this.setFollowing(false);
+ }
+ }
};

private _onPrevSlide = (e: Event) => {
@@ -686,6 +700,67 @@ class SlideShowPresenter {
);
}

+ private _setButtonState(
+ button: HTMLImageElement,
+ disabled: boolean,
+ tooltip: string,
+ ) {
+ if (!button) return;
+ if (disabled) {
+ button.style.filter = 'brightness(0.5)';
+ button.style.cursor = 'default';
+ button.setAttribute('aria-disabled', 'true');
+ } else {
+ button.style.filter = '';
+ button.style.cursor = 'pointer';
+ button.setAttribute('aria-disabled', 'false');
+ }
+ button.setAttribute('aria-label', tooltip);
+ button.setAttribute('data-cooltip', tooltip);
+ }
+
+ private _canGoPrev(currentSlide: number): boolean {
+ if (this.isFollower()) {
+ return currentSlide > 0;
+ }
+ return true;
+ }
+
+ private _canGoNext(currentSlide: number): boolean {
+ if (this.isFollower()) {
+ const leaderSlide = this._slideShowNavigator.getLeaderSlide();
+ return leaderSlide !== -1 && currentSlide < leaderSlide;
+ }
+
+ // In normal mode, we can go next if there are more slides
+ return true;
+ }
+
+ private _updatePrevButtonState(currentSlide: number) {
+ const enabled = this._canGoPrev(currentSlide);
+ const tooltip = enabled ? _('Previous') : _("You're on the first slide");
+ this._setButtonState(this._prevButton, !enabled, tooltip);
+ }
+
+ private _updateNextButtonState(currentSlide: number) {
+ const enabled = this._canGoNext(currentSlide);
+ let tooltip = _('Next');
+ if (!enabled && this.isFollower()) {
+ tooltip = _('Waiting for presenter to advance');
+ }
+ this._setButtonState(this._nextButton, !enabled, tooltip);
+ }
+
+ updateControls() {
+ if (!this._prevButton || !this._nextButton || !this._slideShowNavigator)
+ return;
+
+ const currentSlide = this._slideShowNavigator.currentSlideIndex ?? 0;
+
+ this._updatePrevButtonState(currentSlide);
+ this._updateNextButtonState(currentSlide);
+ }
+
private _initializeSlideNavWidget(container: HTMLDivElement): void {
const closeImg = window.L.DomUtil.create('img', 'left-img', container);
const setImgSize = (img: HTMLImageElement) => {
@@ -712,6 +787,7 @@ class SlideShowPresenter {
leftImg.setAttribute('aria-label', slideshowPrevText);
leftImg.setAttribute('data-cooltip', slideshowPrevText);
setImgSize(leftImg);
+ this._prevButton = leftImg;
window.L.control.attachTooltipEventListener(leftImg, this._map);
app.LOUtil.setImage(leftImg, 'slideshow-slidePrevious.svg', this._map);
leftImg.addEventListener('click', this._onPrevNextSlide);
@@ -719,6 +795,7 @@ class SlideShowPresenter {
const rightImg = window.L.DomUtil.create('img', 'right-img', container);
rightImg.id = 'next';
const slideshowNextText = _('Next');
+ this._nextButton = rightImg;
window.L.control.attachTooltipEventListener(rightImg, this._map);
rightImg.setAttribute('aria-label', slideshowNextText);
rightImg.setAttribute('data-cooltip', slideshowNextText);
@@ -784,9 +861,13 @@ class SlideShowPresenter {
clearTimeout(this._slideControlsTimer);
}.bind(this),
);
- container.addEventListener('click', () => {
+ container.addEventListener('click', (e: Event) => {
+ const target = e.target as HTMLElement;
+ if (target.getAttribute('aria-disabled') === 'true') return;
this.setFollowing(false);
});
+
+ this.updateControls();
}

private startTimer(loopAndRepeatDuration: number) {
@@ -1322,6 +1403,7 @@ class SlideShowPresenter {
this._followBtn.setAttribute('data-cooltip', followText);
}
}
+ this.updateControls();
}

isFollowing(): boolean {
diff --git a/browser/src/slideshow/engine/SlideShowNavigator.ts b/browser/src/slideshow/engine/SlideShowNavigator.ts
index cd63a60215..6e542358c2 100644
--- a/browser/src/slideshow/engine/SlideShowNavigator.ts
+++ b/browser/src/slideshow/engine/SlideShowNavigator.ts
@@ -332,6 +332,9 @@ class SlideShowNavigator {
if (this.prevSlide >= this.theMetaPres.numberOfSlides)
this.prevSlide = undefined;
this.currentSlide = nNewSlide;
+ if (this.presenter.updateControls) {
+ this.presenter.updateControls();
+ }

if (this.currentSlide === this.prevSlide) {
NAVDBG.print(

"Gökay Şatır (via github)"

unread,
Feb 23, 2026, 6:04:03 AMFeb 23
to collaboraon...@googlegroups.com
browser/src/map/Map.js | 5 +++++
browser/src/map/handler/Map.Scroll.js | 8 ++++++++
2 files changed, 13 insertions(+)

New commits:
commit 6278bfa5d5561f983b3e67dbda50bdfd0d87e271
Author: Gökay Şatır <gokay...@collabora.com>
AuthorDate: Fri Feb 20 19:40:31 2026 +0300
Commit: Szymon Kłos <eszk...@gmail.com>
CommitDate: Mon Feb 23 12:03:38 2026 +0100

Document zoom: Disable animation while zooming.

Issue: User is presented the normal view's tile layou while zooming in multi page or compare changes view.
Zoom animation takes an old path that we need to upgrade. This is the first step for better animation.

Signed-off-by: Gökay Şatır <gokay...@collabora.com>
Change-Id: Ie783d49b82750ce685f82aa32bddc572ba79611a

diff --git a/browser/src/map/Map.js b/browser/src/map/Map.js
index 96ddd5dcdb..47a1fda0a5 100644
--- a/browser/src/map/Map.js
+++ b/browser/src/map/Map.js
@@ -683,6 +683,11 @@ window.L.Map = window.L.Evented.extend({
return this;
}

+ // Do not animate zoom in multi-page or compare changes view.
+ if (animate && app.activeDocument &&
+ ['ViewLayoutMultiPage', 'ViewLayoutCompareChanges'].includes(app.activeDocument.activeLayout.type))
+ animate = false;
+
var curCenter = this.getCenter();
if (this._docLayer && this._docLayer._docType === 'spreadsheet') {
// for spreadsheets, when the document is smaller than the viewing area
diff --git a/browser/src/map/handler/Map.Scroll.js b/browser/src/map/handler/Map.Scroll.js
index c175d5b386..841d3e2afa 100644
--- a/browser/src/map/handler/Map.Scroll.js
+++ b/browser/src/map/handler/Map.Scroll.js
@@ -119,6 +119,14 @@ window.L.Map.Scroll = window.L.Handler.extend({

this._stopZoomInterpolateRAFAt = this._zoomScrollTime.valueOf() + this._map.options.wheelZoomStepEndAfter;

+ // In multi-page or compare changes view, skip animation and zoom directly.
+ if (app.activeDocument && app.activeDocument.activeLayout &&
+ ['ViewLayoutMultiPage', 'ViewLayoutCompareChanges'].includes(app.activeDocument.activeLayout.type)) {
+ map.setZoom(this._zoom, null, false);
+ this._zoomScrollTime = undefined;
+ return;
+ }
+
if (newAnimation) {
this._inZoomAnimation = true;
this._map._docLayer.preZoomAnimation(this._zoomCenter);

"Szymon Kłos (via github)"

unread,
Feb 23, 2026, 7:45:07 AMFeb 23
to collaboraon...@googlegroups.com
browser/src/control/Control.SaveState.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

New commits:
commit 9adfec48b177bb325a7da570da073fe80449a9b5
Author: Szymon Kłos <szymo...@collabora.com>
AuthorDate: Fri Feb 20 10:54:30 2026 +0000
Commit: Darshan-Upadhyay <61383886+Darsh...@users.noreply.github.com>
CommitDate: Mon Feb 23 18:14:37 2026 +0530

SaveState: don't use missing icon in show modified

followup for commit 59c4ace625137be8cc411359b67e4a35019c81f3
it's just additional fuse, if saveEle is not found we already
skip this branch but be safer for future modification

Signed-off-by: Szymon Kłos <szymo...@collabora.com>
Change-Id: If6ac6dde657ce36eec23b872c6e273e9e858a7e0

diff --git a/browser/src/control/Control.SaveState.ts b/browser/src/control/Control.SaveState.ts
index c2944f0f63..516ae17dcb 100644
--- a/browser/src/control/Control.SaveState.ts
+++ b/browser/src/control/Control.SaveState.ts
@@ -80,7 +80,7 @@ class SaveState {
if (this.saveEle) {
this.saveEle.classList.remove('saving');
this.saveEle.classList.remove('saved');
- this.saveIconEl.classList.remove('rotate-icon'); // Stop the icon rotation
+ this.saveIconEl?.classList.remove('rotate-icon'); // Stop the icon rotation
this.saveEle.removeAttribute('disabled'); // Enable the button
this.saveEle.classList.add('savemodified');
}

"Gülşah Köse (via github)"

unread,
Feb 23, 2026, 9:04:32 AMFeb 23
to collaboraon...@googlegroups.com
browser/src/canvas/sections/ShapeHandleAnchorSubSection.ts | 2 ++
1 file changed, 2 insertions(+)

New commits:
commit 10ee580e02146b5a9f33419b6ca15c117e170ab2
Author: Gülşah Köse <gulsa...@collabora.com>
AuthorDate: Fri Feb 20 14:10:41 2026 +0300
Commit: Gülşah Köse <gulsa...@collabora.com>
CommitDate: Mon Feb 23 17:03:25 2026 +0300

Writer: Hide the shape drag preview on mouse up

When we drag and drop the anchor if the shape doesn't move at the end
shadow shape get stucks on the document.

With this patch we hide the preview on mouse up.

Signed-off-by: Gülşah Köse <gulsa...@collabora.com>
Change-Id: Id78d7de49d181140c37778a30b587364b3db4414

diff --git a/browser/src/canvas/sections/ShapeHandleAnchorSubSection.ts b/browser/src/canvas/sections/ShapeHandleAnchorSubSection.ts
index c0fa0c3bf1..d16c0e1b75 100644
--- a/browser/src/canvas/sections/ShapeHandleAnchorSubSection.ts
+++ b/browser/src/canvas/sections/ShapeHandleAnchorSubSection.ts
@@ -79,6 +79,8 @@ class ShapeHandleAnchorSubSection extends HTMLObjectSection {
else {
this.tableMouseUp(point, e);
}
+
+ this.sectionProperties.parentHandlerSection.hideSVG();
}
}


"Dennis Francis (via github)"

unread,
Feb 23, 2026, 11:35:08 AMFeb 23
to collaboraon...@googlegroups.com
browser/src/canvas/sections/CommentListSection.ts | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)

New commits:
commit 5c7d923d63bfe8f0f47b63a063dc611737bb9913
Author: Dennis Francis <dennis....@collabora.com>
AuthorDate: Fri Feb 20 13:38:31 2026 +0530
Commit: Dennis Francis <dennisfr...@gmail.com>
CommitDate: Mon Feb 23 22:04:38 2026 +0530

browser: view-jumps on comments: fix bad parse of cellPos

Bug: In a spreadsheet with comments spanning outside single viewport,
scroll down to the cell of last comment and hover over it => the view
jumps.

Reason: cellPos of the comment in twips is treated as a raw string hence
we get incorrect rectangles.

Signed-off-by: Dennis Francis <dennis....@collabora.com>
Change-Id: Ie920bb35fc1af0c9ef22f202145ce9ce481bbfe1

diff --git a/browser/src/canvas/sections/CommentListSection.ts b/browser/src/canvas/sections/CommentListSection.ts
index fb8c4e4a70..7c99cf0c4a 100644
--- a/browser/src/canvas/sections/CommentListSection.ts
+++ b/browser/src/canvas/sections/CommentListSection.ts
@@ -1896,10 +1896,15 @@ export class CommentSection extends CanvasSectionObject {
comment.cellRange = app.map._docLayer._parseCellRange(comment.cellRange);
}

- var cellPos = comment.cellRange ? app.map._docLayer._cellRangeToTwipRect(comment.cellRange).toRectangle() : null;
- comment.rectangles = this.stringToRectangles(comment.textRange || comment.anchorPos || comment.rectangle || cellPos); // Simple array of point arrays [x1, y1, x2, y2].
- comment.rectanglesOriginal = this.stringToRectangles(comment.textRange || comment.anchorPos || comment.rectangle || cellPos); // This unmodified version will be kept for re-calculations.
- comment.anchorPos = this.stringToRectangles(comment.anchorPos || comment.rectangle || cellPos)[0];
+ const cellPos = comment.cellRange ? app.map._docLayer._cellRangeToTwipRect(comment.cellRange).toRectangle() : null;
+ const rectangles = this.stringToRectangles(comment.textRange || comment.anchorPos || comment.rectangle); // Simple array of point arrays [x1, y1, x2, y2].
+ if (rectangles.length === 0 && cellPos.length) {
+ rectangles.push(cellPos);
+ }
+ console.assert(rectangles.length, 'Found no rectangles in comment!');
+ comment.rectangles = rectangles;
+ comment.rectanglesOriginal = structuredClone(rectangles);
+ comment.anchorPos = rectangles[0];
comment.anchorSPoint = new cool.SimplePoint(comment.anchorPos[0], comment.anchorPos[1]);

if (app.map._docLayer._docType === 'spreadsheet' && app.map._docLayer.sheetGeometry)

"Méven Car (via github)"

unread,
Feb 24, 2026, 9:19:17 AMFeb 24
to collaboraon...@googlegroups.com
browser/src/app/LOUtil.ts | 20 ++++++++++++++++++++
browser/src/control/Control.Menubar.ts | 7 ++++++-
browser/src/control/Control.Notebookbar.js | 18 +++---------------
3 files changed, 29 insertions(+), 16 deletions(-)

New commits:
commit d1e64a46e0aec88389e4e8421ef033ef4f27b36c
Author: Méven Car <meve...@collabora.com>
AuthorDate: Thu Jan 15 12:09:11 2026 +0100
Commit: Méven <mev...@gmail.com>
CommitDate: Tue Feb 24 15:19:04 2026 +0100

classic menu: set iconClass for document-logo

Share the code with the notebookbar.

Signed-off-by: Méven Car <meve...@collabora.com>
Change-Id: Ie05e50caa443d1c6f0e549a25fe965ad8380159a

diff --git a/browser/src/app/LOUtil.ts b/browser/src/app/LOUtil.ts
index 97116338f0..416ad92785 100644
--- a/browser/src/app/LOUtil.ts
+++ b/browser/src/app/LOUtil.ts
@@ -891,6 +891,26 @@ class LOUtil {
}
return '';
}
+
+ public static getDocumentLogoClass(docType: string) {
+ let iconClass: string;
+ let iconTooltip: string;
+ if (docType === 'text') {
+ iconClass = 'writer-icon-img';
+ iconTooltip = 'Writer';
+ } else if (docType === 'spreadsheet') {
+ iconClass = 'calc-icon-img';
+ iconTooltip = 'Calc';
+ } else if (docType === 'presentation') {
+ iconClass = 'impress-icon-img';
+ iconTooltip = 'Impress';
+ } else if (docType === 'drawing') {
+ iconClass = 'draw-icon-img';
+ iconTooltip = 'Draw';
+ }
+
+ return [iconClass, iconTooltip];
+ }
}

app.LOUtil = LOUtil;
diff --git a/browser/src/control/Control.Menubar.ts b/browser/src/control/Control.Menubar.ts
index f93b711c0d..6650ecaf48 100644
--- a/browser/src/control/Control.Menubar.ts
+++ b/browser/src/control/Control.Menubar.ts
@@ -2447,12 +2447,17 @@ class Menubar extends window.L.Control {

if (window.logoURL) {
aItem.style.backgroundImage = "url(" + window.logoURL + ")";
+ } else {
+ const docType = this._map.getDocType();
+ const [iconClass, iconTooltip] = app.LOUtil.getDocumentLogoClass(docType);
+ aItem.classList.add(iconClass);
+ aItem.setAttribute('data-cooltip', iconTooltip);
}

if (this._menubarCont != null)
this._menubarCont.insertBefore(liItem, this._menubarCont.firstChild);

- var $docLogo = $(aItem);
+ const $docLogo = $(aItem);
$docLogo.bind('click', {self: this}, this._createDocument);
$docLogo.bind('click', this._createDocument.bind(this));
}
diff --git a/browser/src/control/Control.Notebookbar.js b/browser/src/control/Control.Notebookbar.js
index becf0d0ade..cd77a8045c 100644
--- a/browser/src/control/Control.Notebookbar.js
+++ b/browser/src/control/Control.Notebookbar.js
@@ -85,24 +85,12 @@ window.L.Control.Notebookbar = window.L.Control.extend({
const docLogoHeader = window.L.DomUtil.create('div', '');
docLogoHeader.id = 'document-header';

- let iconClass = 'document-logo';
+ let iconClass = '';
let iconTooltip;
if (!window.logoURL) {
- if (docType === 'text') {
- iconClass += ' writer-icon-img';
- iconTooltip = 'Writer';
- } else if (docType === 'spreadsheet') {
- iconClass += ' calc-icon-img';
- iconTooltip = 'Calc';
- } else if (docType === 'presentation') {
- iconClass += ' impress-icon-img';
- iconTooltip = 'Impress';
- } else if (docType === 'drawing') {
- iconClass += ' draw-icon-img';
- iconTooltip = 'Draw';
- }
+ [iconClass, iconTooltip] = app.LOUtil.getDocumentLogoClass(docType);
}
- const docLogo = window.L.DomUtil.create('a', iconClass, docLogoHeader);
+ const docLogo = window.L.DomUtil.create('a', 'document-logo ' + iconClass, docLogoHeader);

docLogo.setAttribute('id', 'document-logo');
docLogo.setAttribute('type', 'action');

"Henry Castro (via github)"

unread,
Feb 24, 2026, 10:02:59 AMFeb 24
to collaboraon...@googlegroups.com
browser/src/control/Control.JSDialogBuilder.js | 9 +++++++++
1 file changed, 9 insertions(+)

New commits:
commit a2d3e665ed5f37258bffd59d6058a3155ee5d25d
Author: Henry Castro <hca...@collabora.com>
AuthorDate: Wed Feb 4 09:31:30 2026 -0400
Commit: Henry Castro <hca...@collabora.com>
CommitDate: Tue Feb 24 11:02:32 2026 -0400

browser: a11y: add custom-label and custom-label-for properties

Slightly adjust the container to add a label without
affecting the current layout.

Change-Id: I2f38c9b21167a517f0dbd1a3cab8be0d7639890e
Signed-off-by: Henry Castro <hca...@collabora.com>

diff --git a/browser/src/control/Control.JSDialogBuilder.js b/browser/src/control/Control.JSDialogBuilder.js
index 6ec52ad223..51ddd5cddc 100644
--- a/browser/src/control/Control.JSDialogBuilder.js
+++ b/browser/src/control/Control.JSDialogBuilder.js
@@ -2605,6 +2605,15 @@ window.L.Control.JSDialogBuilder = window.L.Control.extend({
var isContainer = this.isContainerType(childData.type);
if (hasManyChildren && isContainer) {
var table = window.L.DomUtil.createWithId('div', childData.id, containerToInsert);
+
+ if (childData.cargo && childData.cargo['custom-label']) {
+ var label = window.L.DomUtil.create('label', '', table);
+ label.textContent = childData.cargo['custom-label'];
+ label.htmlFor = childData.cargo['custom-label-for'] + '-input';
+ table.id = '';
+ table = window.L.DomUtil.createWithId('div', childData.id, table);
+ }
+
$(table).addClass(this.options.cssClass);

if (!isVertical) {

"Raul-Ionut Nastasie (via github)"

unread,
Feb 24, 2026, 12:47:41 PMFeb 24
to collaboraon...@googlegroups.com
browser/src/control/Control.NotebookbarWriter.js | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)

New commits:
commit d69d975657c5588e42758046fc054f0c81ac8e60
Author: Raul-Ionut Nastasie <raul-ionu...@collabora.com>
AuthorDate: Mon Feb 23 17:18:39 2026 +0100
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Tue Feb 24 17:47:07 2026 +0000

Made Validate Sidebar/Dialogs visible in any file in debug mode

Signed-off-by: Raul-Ionut Nastasie <raul-ionu...@collabora.com>
Change-Id: I6c75c2418a8ccb9d80ed458a995c5d7731d1c537

diff --git a/browser/src/control/Control.NotebookbarWriter.js b/browser/src/control/Control.NotebookbarWriter.js
index 1c589a5906..8924f71c95 100644
--- a/browser/src/control/Control.NotebookbarWriter.js
+++ b/browser/src/control/Control.NotebookbarWriter.js
@@ -482,22 +482,20 @@ window.L.Control.NotebookbarWriter = window.L.Control.Notebookbar.extend({
'command': '.uno:SidebarDeck.A11yCheckDeck',
'accessibility': { focusBack: false, combination: 'A', de: null }
} : {},
- hasAccessibilityCheck ?
{
'id': 'validatesidebara11y',
'type': 'bigcustomtoolitem',
'text': _('Validate Sidebar'),
'visible': isDebugOn ? 'true' : 'false',
'accessibility': { focusBack: true, combination: 'VS', de: null }
- } : {},
- hasAccessibilityCheck ?
+ },
{
'id': 'validatedialogsa11y',
'type': 'bigcustomtoolitem',
'text': _('Validate Dialog'),
'visible': isDebugOn ? 'true' : 'false',
'accessibility': { focusBack: true, combination: 'VD', de: null }
- } : {},
+ },
hasAccessibilitySupport || hasAccessibilityCheck ?
{
'id': 'help-accessibility-break',

"codewithvk (via github)"

unread,
Feb 25, 2026, 2:32:55 AMFeb 25
to collaboraon...@googlegroups.com
browser/src/control/jsdialog/Definitions.Types.ts | 1 +
browser/src/control/jsdialog/Widget.Containers.ts | 4 ++++
2 files changed, 5 insertions(+)

New commits:
commit bd347585aac3b41a6d672d82fe5ff48f19f7f7fa
Author: codewithvk <vivek....@collabora.com>
AuthorDate: Sat Feb 7 17:16:53 2026 +0530
Commit: Vivek :) <61119120+...@users.noreply.github.com>
CommitDate: Wed Feb 25 13:02:41 2026 +0530

a11y: add support for aria role attribute
Add a role property to HTML to allow accessibility roles (e.g., group) from JSON received from core.

This was discovered while improving accessibility for Sidebar -> Paragraph -> Indent, where groups require role=group.

Signed-off-by: codewithvk <vivek....@collabora.com>
Change-Id: Ifb66df539a70cff09f11d138b728e85c0e439174

diff --git a/browser/src/control/jsdialog/Definitions.Types.ts b/browser/src/control/jsdialog/Definitions.Types.ts
index 0201ed412f..fc293f404d 100644
--- a/browser/src/control/jsdialog/Definitions.Types.ts
+++ b/browser/src/control/jsdialog/Definitions.Types.ts
@@ -442,6 +442,7 @@ interface CheckboxWidgetJSON extends WidgetJSON {
interface AriaLabelAttributes {
label?: string;
description?: string;
+ role?: string;
}

interface SeparatorWidgetJSON extends WidgetJSON {
diff --git a/browser/src/control/jsdialog/Widget.Containers.ts b/browser/src/control/jsdialog/Widget.Containers.ts
index 73043c7909..9898ef2d4f 100644
--- a/browser/src/control/jsdialog/Widget.Containers.ts
+++ b/browser/src/control/jsdialog/Widget.Containers.ts
@@ -161,6 +161,10 @@ JSDialog.toolbox = function (
}
}

+ if (data.aria?.role) {
+ toolbox.setAttribute('role', data.aria.role);
+ }
+
const enabledCallback = function (enable: boolean) {
for (const j in data.children) {
const childId = data.children[j].id;

"Parth Raiyani (via github)"

unread,
Feb 25, 2026, 11:21:33 AMFeb 25
to collaboraon...@googlegroups.com
browser/src/control/jsdialog/Widget.OverflowGroup.ts | 1 +
1 file changed, 1 insertion(+)

New commits:
commit 0ccbda7b51ac7baa23216159168013a510d86d7f
Author: Parth Raiyani <parth....@collabora.com>
AuthorDate: Wed Feb 25 20:06:51 2026 +0530
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Wed Feb 25 16:21:03 2026 +0000

fix: add tabindex=-1 for over flow group container to avoid getting focus

Signed-off-by: Parth Raiyani <parth....@collabora.com>
Change-Id: I73d390ead3460b3f1a9f6114a105f9e083b53449

diff --git a/browser/src/control/jsdialog/Widget.OverflowGroup.ts b/browser/src/control/jsdialog/Widget.OverflowGroup.ts
index 0f0434ea5c..1442a9a06c 100644
--- a/browser/src/control/jsdialog/Widget.OverflowGroup.ts
+++ b/browser/src/control/jsdialog/Widget.OverflowGroup.ts
@@ -340,6 +340,7 @@ JSDialog.OverflowGroup = function (
parentContainer,
);
overflowGroupContainer.id = data.id;
+ overflowGroupContainer.setAttribute('tabindex', '-1');

const overflowGroupInnerContainer = window.L.DomUtil.create(
'div',

"Szymon Kłos (via github)"

unread,
Feb 25, 2026, 2:13:21 PMFeb 25
to collaboraon...@googlegroups.com
browser/src/canvas/sections/ShapeHandlesSection.ts | 3 +++
1 file changed, 3 insertions(+)

New commits:
commit 039745b08b2bfbb6e3808df9458a2d05d55ffea3
Author: Szymon Kłos <szymo...@collabora.com>
AuthorDate: Mon Feb 23 17:29:47 2026 +0000
Commit: Szymon Kłos <eszk...@gmail.com>
CommitDate: Wed Feb 25 20:12:28 2026 +0100

svg: adjust size when zoom changed

It helps with interactive editing in Impress.

Steps to reproduce bug:
1. open Impress
2. double-click on table or textbox to see selection and activate
editing
3. use zoom, + few times, then - few times

Result: table is incorrectly scaled and positioned
Expected: no such glitch

We use svg to render the "editable content" but it had size adjusted
only on initial insertion into DOM.

Signed-off-by: Szymon Kłos <szymo...@collabora.com>
Change-Id: I2a68bbadd5064742ca31ce0d933c27300971576c

diff --git a/browser/src/canvas/sections/ShapeHandlesSection.ts b/browser/src/canvas/sections/ShapeHandlesSection.ts
index 487ea51fdb..f9bf9f11cb 100644
--- a/browser/src/canvas/sections/ShapeHandlesSection.ts
+++ b/browser/src/canvas/sections/ShapeHandlesSection.ts
@@ -421,6 +421,9 @@ class ShapeHandlesSection extends CanvasSectionObject {

if (GraphicSelection.hasActiveSelection())
this.size = [GraphicSelection.rectangle.pWidth, GraphicSelection.rectangle.pHeight];
+
+ if (this.sectionProperties.svg)
+ this.adjustSVGProperties();
}

isSVGVisible() {

"Henry Castro (via github)"

unread,
Feb 26, 2026, 11:46:01 AMFeb 26
to collaboraon...@googlegroups.com
browser/src/control/Control.JSDialog.js | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)

New commits:
commit 5265b914eae63f96b405820a97575be5287df4fd
Author: Henry Castro <hca...@collabora.com>
AuthorDate: Thu Feb 26 09:07:13 2026 -0400
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Thu Feb 26 16:45:42 2026 +0000

brower: a11y: fix focus loss after dropdown closes

We save lastFocusedElement every time a dialog or dropdown opens.
However, during the interaction, the server can update the elements,
and our approach is to recreate them.
As a result, the saved lastFocusedElement reference stays in memory
but is no longer connected to the DOM.

Change-Id: I177a2b0d6283de553aa7fbb08db09c10513c6a09
Signed-off-by: Henry Castro <hca...@collabora.com>

diff --git a/browser/src/control/Control.JSDialog.js b/browser/src/control/Control.JSDialog.js
index 1b354f453b..923e95c45b 100644
--- a/browser/src/control/Control.JSDialog.js
+++ b/browser/src/control/Control.JSDialog.js
@@ -221,7 +221,14 @@ window.L.Control.JSDialog = window.L.Control.extend({
}

try {
- dialog.lastFocusedElement.focus();
+ if (dialog.lastFocusedElement.isConnected) {
+ dialog.lastFocusedElement.focus();
+ } else {
+ var focusId = document.getElementById(dialog.lastFocusedElementId);
+ if (focusId) {
+ focusId.focus();
+ }
+ }
}
catch (error) {
app.console.debug('Cannot focus last element in dialog with id: ' + id);
@@ -821,8 +828,10 @@ window.L.Control.JSDialog = window.L.Control.extend({
// Save last focused element, we will set the focus back to this element after this popup is closed.
if (this.dialogs[instance.id] && this.dialogs[instance.id].lastFocusedElement) {
instance.lastFocusedElement = this.dialogs[instance.id].lastFocusedElement;
+ instance.lastFocusedElementId = this.dialogs[instance.id].lastFocusedElementId;
} else if (!this.dialogs[instance.id] || !this.dialogs[instance.id].lastFocusedElement) { // Avoid to reset while updates.
instance.lastFocusedElement = document.activeElement;
+ instance.lastFocusedElementId = document.activeElement.id;
}

instance.callback = e.callback;

"codewithvk (via github)"

unread,
Feb 26, 2026, 11:47:04 AMFeb 26
to collaboraon...@googlegroups.com
browser/src/control/jsdialog/Widget.ColorPicker.ts | 1 +
1 file changed, 1 insertion(+)

New commits:
commit 782330401baa99ef54aea553ab316c1a126dec5c
Author: codewithvk <vivek....@collabora.com>
AuthorDate: Thu Feb 26 19:45:07 2026 +0530
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Thu Feb 26 16:46:01 2026 +0000

Color picker: fix duplicate command on Enter key

Insert table -> Miscellaneous -> Color picker to select color using keyboard (enter) then erlier was default color would override to default color and now it would work normal.

Signed-off-by: codewithvk <vivek....@collabora.com>
Change-Id: I6b4be87cc4070ef281829c9e999544c7bce982fd

diff --git a/browser/src/control/jsdialog/Widget.ColorPicker.ts b/browser/src/control/jsdialog/Widget.ColorPicker.ts
index c30d16f9e6..7c2aa4e739 100644
--- a/browser/src/control/jsdialog/Widget.ColorPicker.ts
+++ b/browser/src/control/jsdialog/Widget.ColorPicker.ts
@@ -168,6 +168,7 @@ function createColor(
});
color.addEventListener('keydown', (event: KeyboardEvent) => {
if (event.code === 'Enter') {
+ event.preventDefault();
handleColorSelection(
event.target as HTMLElement, // The clicked element
builder, // Pass the builder object

"Henry Castro (via github)"

unread,
Feb 27, 2026, 9:07:10 AMFeb 27
to collaboraon...@googlegroups.com
browser/src/control/jsdialog/Util.FocusCycle.js | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)

New commits:
commit 4c90f0e0ca46a146ae6ff562fdbf9b48b4a3baf6
Author: Henry Castro <hca...@collabora.com>
AuthorDate: Fri Feb 27 07:22:52 2026 -0400
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Fri Feb 27 14:06:30 2026 +0000

browser: a11y: fix initial focus visibility in dropdown

The color picker dropdown contains a button with the
display: none property.

Change-Id: I2d63ca3d282de4e9e8791d0ffa34655c57bbd518
Signed-off-by: Henry Castro <hca...@collabora.com>

diff --git a/browser/src/control/jsdialog/Util.FocusCycle.js b/browser/src/control/jsdialog/Util.FocusCycle.js
index b487a3e191..53b6a6b2ca 100644
--- a/browser/src/control/jsdialog/Util.FocusCycle.js
+++ b/browser/src/control/jsdialog/Util.FocusCycle.js
@@ -45,6 +45,9 @@ function getFocusableElements(container) {
ret = container.querySelectorAll('select:not([disabled]):not(.hidden)');
if (!ret.length)
ret = container.querySelectorAll('button:not([disabled]):not(.hidden)');
+
+ ret = Array.from(ret).filter(function(elem) { return elem.checkVisibility() });
+
return ret;
}

@@ -205,7 +208,7 @@ function findNextElementInContainer(container, currentElement, direction) {

let diffX = 0;
let diffY = 0;
-
+
// Ray casting sensitivity for spatial navigation
var rayCastingSensitivity = 10; // Pixels


"Dennis Francis (via github)"

unread,
Feb 28, 2026, 5:43:11 AMFeb 28
to collaboraon...@googlegroups.com
browser/src/canvas/sections/CommentListSection.ts | 7 +++++++
browser/src/canvas/sections/CommentSection.ts | 5 ++++-
2 files changed, 11 insertions(+), 1 deletion(-)

New commits:
commit 535b8a89b5ec3fd42de0761b85a13af0b1e2c87d
Author: Dennis Francis <dennis....@collabora.com>
AuthorDate: Mon Feb 23 22:30:20 2026 +0530
Commit: Dennis Francis <dennisfr...@gmail.com>
CommitDate: Sat Feb 28 16:13:05 2026 +0530

browser: calc: fix flicker on switching to a sheet...

...with many comments.

Bug: Open a spreadsheet with at least two sheets, one could be blank and
the other with lots of comments. When switching sheet from blank sheet
to the sheet with comments, momentarily the comment markers are drawn
misplaced and then soon drawn at correct positions, perceived as a
flicker.

Solution:
The draw of comment markers just after a sheet-switch but before
'commandstatechanged' message is wrong due to stale myTopLeft array of
each comment-section. So just wait for 'commandstatechanged' message
before drawing them.

Signed-off-by: Dennis Francis <dennis....@collabora.com>
Change-Id: I7e5f0fe379c6bf061720145ab3b03a979273c447

diff --git a/browser/src/canvas/sections/CommentListSection.ts b/browser/src/canvas/sections/CommentListSection.ts
index 7c99cf0c4a..9bb5859834 100644
--- a/browser/src/canvas/sections/CommentListSection.ts
+++ b/browser/src/canvas/sections/CommentListSection.ts
@@ -89,6 +89,9 @@ export class CommentSection extends CanvasSectionObject {
calcLastTab: number;
// Keep a reference to the original set of comments received.
calcMasterList: Array<any>;
+ // Has the 'commandstatechanged' msg arrived after a tab switch.
+ // This a precondition for drawing spreadsheet comment marker.
+ calcCommandStateChanged: boolean;
commentList: Array<Comment>;
selectedComment: Comment | null;
calcCurrentComment: Comment | null;
@@ -134,6 +137,7 @@ export class CommentSection extends CanvasSectionObject {
this.sectionProperties.docLayer = this.map._docLayer;
this.sectionProperties.calcLastTab = -1;
this.sectionProperties.calcMasterList = [];
+ this.sectionProperties.calcCommandStateChanged = true;
this.sectionProperties.commentList = new Array(0);
this.sectionProperties.selectedComment = null;
this.sectionProperties.arrow = null;
@@ -171,6 +175,7 @@ export class CommentSection extends CanvasSectionObject {
this.map.on('AnnotationScrollDown', this.onAnnotationScrollDown, this);

this.map.on('commandstatechanged', function (event: any) {
+ this.sectionProperties.calcCommandStateChanged = true;
if (event.commandName === '.uno:ShowResolvedAnnotations')
this.setViewResolved(event.state === 'true');
else if (event.commandName === 'showannotations')
@@ -2554,6 +2559,8 @@ export class CommentSection extends CanvasSectionObject {
return;
}

+ this.sectionProperties.calcCommandStateChanged = false;
+
this.importComments();
}

diff --git a/browser/src/canvas/sections/CommentSection.ts b/browser/src/canvas/sections/CommentSection.ts
index db5fb2b58d..3300e2e9ed 100644
--- a/browser/src/canvas/sections/CommentSection.ts
+++ b/browser/src/canvas/sections/CommentSection.ts
@@ -1437,7 +1437,10 @@ export class Comment extends CanvasSectionObject {
}
}
else if (app.map._docLayer._docType === 'spreadsheet' &&
- parseInt(this.sectionProperties.data.tab) === app.map._docLayer._selectedPart) {
+ parseInt(this.sectionProperties.data.tab) === app.map._docLayer._selectedPart &&
+ // Don't draw with stale section myTopLeft after a tab-switch.
+ // Wait for a 'commandstatechanged'.
+ this.sectionProperties.commentListSection.sectionProperties.calcCommandStateChanged === true) {

var cellSize = this.calcOptimumSizeForCalc();
if (cellSize[0] !== 0 && cellSize[1] !== 0) { // don't draw notes in hidden cells

"Skyler Grey (via github)"

unread,
Feb 28, 2026, 7:09:24 AMFeb 28
to collaboraon...@googlegroups.com
browser/src/canvas/CanvasSectionContainer.ts | 7 +++++++
1 file changed, 7 insertions(+)

New commits:
commit ed3cd374ee6c1dc438605d7532bf2fcba3faf4d8
Author: Skyler Grey <skyle...@collabora.com>
AuthorDate: Mon Feb 23 08:04:39 2026 +0000
Commit: Skyler Grey <git...@companies.starrysky.fyi>
CommitDate: Sat Feb 28 12:09:13 2026 +0000

fix(mobile): make clicks emulate touch event

We generate some click events when tapping on the canvas. Unfortunately,
these are "Click" events, so always treated as mouse events. This causes
some trouble when triggering certain handlers on mobile. For example, a
mobile device will have trouble editing an input field in impress since
as there is a race between the touch events and click events - it is
techncially possible but takes a lot of repeated and very fast tapping.

Overriding the pointerType fixes this.

Unfortunately, the pointerType is readonly - so overriding it isn't
possible normally. Thankfully, Object.defineProperty can completely
replace even readonly properties, solving the issue...

Signed-off-by: Skyler Grey <skyle...@collabora.com>
Change-Id: I9cfa646dbb9ff34551304bf58383f12a6a6a6964

diff --git a/browser/src/canvas/CanvasSectionContainer.ts b/browser/src/canvas/CanvasSectionContainer.ts
index 32ab73bfda..603ee37fca 100644
--- a/browser/src/canvas/CanvasSectionContainer.ts
+++ b/browser/src/canvas/CanvasSectionContainer.ts
@@ -1089,6 +1089,13 @@ class CanvasSectionContainer {
}

public onClick (e: MouseEvent) {
+ if (this.touchEventInProgress) {
+ Object.defineProperty(e, 'pointerType', {
+ value: 'touch',
+ writable: false
+ });
+ }
+
if (!this.draggingSomething) { // Prevent click event after dragging.
if (this.positionOnMouseDown !== null && this.positionOnMouseUp !== null) {
this.positionOnClick = this.convertPositionToCanvasLocale(e);

"Caolán McNamara (via github)"

unread,
Mar 2, 2026, 5:00:40 AMMar 2
to collaboraon...@googlegroups.com
browser/src/app/Log.ts | 8 ++++++--
browser/src/app/Socket.ts | 2 +-
2 files changed, 7 insertions(+), 3 deletions(-)

New commits:
commit 0f825c73af4da43ce09561ac12cc2bbe68d461d0
Author: Caolán McNamara <caolan....@collabora.com>
AuthorDate: Thu Feb 26 17:02:16 2026 +0000
Commit: Miklos Vajna <vmi...@collabora.com>
CommitDate: Mon Mar 2 10:59:35 2026 +0100

Don't truncate log lines during cypress tests

Signed-off-by: Caolán McNamara <caolan....@collabora.com>
Change-Id: I09d4b7a639ba6c3a49e59d9f411214c3885845da

diff --git a/browser/src/app/Log.ts b/browser/src/app/Log.ts
index a59c65e53b..c6e2c5028e 100644
--- a/browser/src/app/Log.ts
+++ b/browser/src/app/Log.ts
@@ -23,10 +23,14 @@ interface LogMsg {
class Logger {
private _logs: LogMsg[];
private startTime: number;
+ private _cypressTest: boolean;

constructor() {
this._logs = [];
this.startTime = null;
+ this._cypressTest =
+ typeof navigator !== 'undefined' &&
+ navigator.userAgent.toLowerCase().indexOf('cypress') !== -1;
}

public log(msg: string, direction: Direction, status: string = ''): void {
@@ -35,13 +39,13 @@ class Logger {

// Limit memory usage of log by only keeping the latest entries
let maxEntries = 100;
- if ((window as any).enableDebug) maxEntries = 1000;
+ if ((window as any).enableDebug || this._cypressTest) maxEntries = 1000;

if (time - this.startTime < 60 * 1000 /* ms */) maxEntries = 500; // enough to capture early start.
while (this._logs.length > maxEntries) this._logs.shift();

// Limit memory usage of log by limiting length of message
- const maxMsgLen = 128;
+ const maxMsgLen = this._cypressTest ? 1024 : 128;
if (msg.length > maxMsgLen) msg = msg.substring(0, maxMsgLen);
msg = msg.replace(/(\r\n|\n|\r)/gm, ' ');
this._logs.push({
diff --git a/browser/src/app/Socket.ts b/browser/src/app/Socket.ts
index f0eda0fbd7..8d5b432dc1 100644
--- a/browser/src/app/Socket.ts
+++ b/browser/src/app/Socket.ts
@@ -618,7 +618,7 @@ class Socket {
this._map._debug.setOverlayMessage('postMessage', type + ': ' + msg);
}

- if (!debugOn && msg.length > 256)
+ if (!debugOn && !window.L.Browser.cypressTest && msg.length > 256)
// for reasonable performance.
msg =
msg.substring(0, 256) + '<truncated ' + (msg.length - 256) + 'chars>';

"Dennis Francis (via github)"

unread,
Mar 2, 2026, 5:36:22 AMMar 2
to collaboraon...@googlegroups.com
browser/src/canvas/sections/CommentListSection.ts | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)

New commits:
commit 0238e9c46d245e0cefce7528d8d08a27fac21fb5
Author: Dennis Francis <dennis....@collabora.com>
AuthorDate: Fri Feb 27 23:22:11 2026 +0530
Commit: Dennis Francis <dennisfr...@gmail.com>
CommitDate: Mon Mar 2 16:06:08 2026 +0530

browser: comments: avoid unnecessary y jump when...

...the right end of the comment box is off-screen in a LTR document.

Bug: Consider a calc document where there is a cell say F70 has a
comment but the viewport is adjusted so that there is no space to draw
the comment box in the horizontal direction but not so in the vertical
direction. Hovering over this cell will make the view scroll away from
the cell in the vertical direction.

Solution: We are scrolling the view only in the Y direction, so only
check if the top and the bottom of the comment box are in the view-area
and don't consider the x components for making this decision.

Note: It would be nicer if we scroll horizontally too if left and right
of the comment box are outside view-area at least for calc. But that is
for the future.

Signed-off-by: Dennis Francis <dennis....@collabora.com>
Change-Id: I22c4789a7ec243dc34a4bee51d9ad154876cad4e

diff --git a/browser/src/canvas/sections/CommentListSection.ts b/browser/src/canvas/sections/CommentListSection.ts
index 9bb5859834..cbef45677a 100644
--- a/browser/src/canvas/sections/CommentListSection.ts
+++ b/browser/src/canvas/sections/CommentListSection.ts
@@ -963,18 +963,17 @@ export class CommentSection extends CanvasSectionObject {
const anchorPos = rootComment.sectionProperties.data.anchorSPoint;

const topLeftArray = anchorPos.toArray();
- const topLeftVisible = app.isPointVisibleInTheDisplayedArea(topLeftArray);
- const bottomRightVisible = app.isPointVisibleInTheDisplayedArea([
- topLeftArray[0] + (this.sectionProperties.commentWidth * app.pixelsToTwips),
+ const topVisible = app.isYVisibleInTheDisplayedArea(topLeftArray[1]);
+ const bottomVisible = app.isYVisibleInTheDisplayedArea(
topLeftArray[1] + Math.round(rootComment.getCommentHeight() * app.pixelsToTwips)
- ]);
+ );

const topBottom = this.getScreenTopBottom();

- if (!topLeftVisible || !bottomRightVisible) {
- if (!topLeftVisible)
+ if (!topVisible || !bottomVisible) {
+ if (!topVisible)
app.activeDocument.activeLayout.scroll(0, topBottom[0] - anchorPos.pY);
- else if (!bottomRightVisible)
+ else if (!bottomVisible)
app.activeDocument.activeLayout.scroll(0, (anchorPos.pY + rootComment.getCommentHeight() - topBottom[1]));

if (app.map._docLayer._docType === 'spreadsheet' && rootComment) {

"Pranam Lashkari (via github)"

unread,
Mar 2, 2026, 7:04:49 AMMar 2
to collaboraon...@googlegroups.com
browser/src/slideshow/engine/SlideShowHandler.ts | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)

New commits:
commit a60b2f7afd5a7eaaacc543a1c3ec4eede7c6417d
Author: Pranam Lashkari <lpr...@collabora.com>
AuthorDate: Sun Mar 1 18:11:59 2026 +0530
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Mon Mar 2 12:04:28 2026 +0000

fix: TypeError: can't access property "end"

Signed-off-by: Pranam Lashkari <lpr...@collabora.com>
Change-Id: I81cd35ce64d34c9de6470bb2f64ddd59878f5f91

diff --git a/browser/src/slideshow/engine/SlideShowHandler.ts b/browser/src/slideshow/engine/SlideShowHandler.ts
index d3de841ec9..c90bc6aaef 100644
--- a/browser/src/slideshow/engine/SlideShowHandler.ts
+++ b/browser/src/slideshow/engine/SlideShowHandler.ts
@@ -330,7 +330,10 @@ class SlideShowHandler {
ANIMDBG.print('SlideShowHandler.notifyNextEffectEnd invoked.');
this.bIsNextEffectRunning = false;

- this.aStartedEffectList[this.aStartedEffectIndexMap.get(-1)].end();
+ const effectIndex = this.aStartedEffectIndexMap.get(-1);
+ if (effectIndex !== undefined && this.aStartedEffectList[effectIndex]) {
+ this.aStartedEffectList[effectIndex].end();
+ }
if (this.automaticAdvanceTimeout !== null) {
if (this.automaticAdvanceTimeoutRewindedEffect === this.nCurrentEffect) {
this.automaticAdvanceTimeout = null;
@@ -488,7 +491,10 @@ class SlideShowHandler {
'SlideShow.notifyInteractiveAnimationSequenceEnd: no interactive effect playing.',
);

- this.aStartedEffectList[this.aStartedEffectIndexMap.get(nNodeId)].end();
+ const effectIndex = this.aStartedEffectIndexMap.get(nNodeId);
+ if (effectIndex !== undefined && this.aStartedEffectList[effectIndex]) {
+ this.aStartedEffectList[effectIndex].end();
+ }
--this.nTotalInteractivePlayingEffects;
}


"Pranam Lashkari (via github)"

unread,
Mar 2, 2026, 7:05:53 AMMar 2
to collaboraon...@googlegroups.com
browser/src/slideshow/RenderContext.ts | 5 +++++
browser/src/slideshow/engine/AnimatedElement.ts | 18 ++++++++++++++++++
2 files changed, 23 insertions(+)

New commits:
commit 244a16e448ad740901b64c0d514ca57d2ac90a7a
Author: Pranam Lashkari <lpr...@collabora.com>
AuthorDate: Sun Mar 1 18:47:12 2026 +0530
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Mon Mar 2 12:05:27 2026 +0000

slideshow: fixed type error in texture loading

problem:
Exception TypeError: WebGL2RenderingContext.texImage2D:
Argument 6 is not valid for any of the 6-argument overloads. emitting event slideshowfollow dispatcheffect
RenderContextGl.prototype.loadTexture@http://192.168.1.253:9980/browser/7c5d38d661/src/slideshow/RenderContext.js:83:12

Signed-off-by: Pranam Lashkari <lpr...@collabora.com>
Change-Id: I2b9776f51291c9d885eff49356d08216beee77e3

diff --git a/browser/src/slideshow/RenderContext.ts b/browser/src/slideshow/RenderContext.ts
index b8fd88672e..d97426c2ce 100644
--- a/browser/src/slideshow/RenderContext.ts
+++ b/browser/src/slideshow/RenderContext.ts
@@ -112,6 +112,11 @@ class RenderContextGl extends RenderContext {
): WebGLTexture | ImageBitmap {
if (this.isDisposed()) return null;

+ if (!image) {
+ app.console.error('RenderContextGl.loadTexture: Invalid image provided');
+ return null;
+ }
+
const gl = this.getGl();

const texture = gl.createTexture();
diff --git a/browser/src/slideshow/engine/AnimatedElement.ts b/browser/src/slideshow/engine/AnimatedElement.ts
index 021ec12a0a..4767e96847 100644
--- a/browser/src/slideshow/engine/AnimatedElement.ts
+++ b/browser/src/slideshow/engine/AnimatedElement.ts
@@ -509,6 +509,18 @@ class AnimatedElement {
layer: ImageBitmap,
bounds: BoundingBoxType,
): AnimatedObjectType {
+ if (!layer) {
+ window.app.console.error(
+ 'AnimatedElement.createBaseElement: layer is null/undefined',
+ );
+ return null;
+ }
+ if (!bounds || bounds.width <= 0 || bounds.height <= 0) {
+ window.app.console.error(
+ `AnimatedElement.createBaseElement: invalid bounds: ${JSON.stringify(bounds)}`,
+ );
+ return null;
+ }
const canvas = new OffscreenCanvas(bounds.width, bounds.height);
const context = canvas.getContext('2d');
context.drawImage(
@@ -597,6 +609,12 @@ class AnimatedElement {
}

private getTextureFromElement(element: AnimatedObjectType) {
+ if (!element || element.width <= 0 || element.height <= 0) {
+ window.app.console.error(
+ `AnimatedElement.getTextureFromElement: invalid element (null or zero-sized)`,
+ );
+ return null;
+ }
return this.tfContext.loadTexture(element);
}


"Pranam Lashkari (via github)"

unread,
Mar 2, 2026, 7:27:22 AMMar 2
to collaboraon...@googlegroups.com
browser/src/slideshow/engine/SlideShowHandler.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)

New commits:
commit daa391b041f7661f92016fc3f62ce64ae6eabc78
Author: Pranam Lashkari <lpr...@collabora.com>
AuthorDate: Sun Mar 1 17:27:12 2026 +0530
Commit: Pranam Lashkari <plashk...@gmail.com>
CommitDate: Mon Mar 2 13:26:59 2026 +0100

slideshow: fixed TypeError with animationsHandler

Signed-off-by: Pranam Lashkari <lpr...@collabora.com>
Change-Id: If62454a1b2b3c6c18e69ba2411203a23dac78cc9

diff --git a/browser/src/slideshow/engine/SlideShowHandler.ts b/browser/src/slideshow/engine/SlideShowHandler.ts
index c90bc6aaef..e4dc4cb4fc 100644
--- a/browser/src/slideshow/engine/SlideShowHandler.ts
+++ b/browser/src/slideshow/engine/SlideShowHandler.ts
@@ -300,7 +300,7 @@ class SlideShowHandler {

const sCurSlideHash = this.theMetaPres.getCurrentSlideHash();
const curMetaSlide = this.theMetaPres.getMetaSlide(sCurSlideHash);
- if (curMetaSlide.animationsHandler) {
+ if (curMetaSlide?.animationsHandler) {
const aAnimatedElementMap =
curMetaSlide.animationsHandler.getAnimatedElementMap();
let effect = 0;
@@ -386,7 +386,7 @@ class SlideShowHandler {

if (nOldSlideIndex !== undefined) {
const metaOldSlide = this.theMetaPres.getMetaSlideByIndex(nOldSlideIndex);
- if (metaOldSlide.animationsHandler) {
+ if (metaOldSlide?.animationsHandler) {
const aAnimatedElementMap =
metaOldSlide.animationsHandler.getAnimatedElementMap();

@@ -396,7 +396,7 @@ class SlideShowHandler {
}
}
const metaNewSlide = this.theMetaPres.getMetaSlideByIndex(nNewSlideIndex);
- if (metaNewSlide.animationsHandler) {
+ if (metaNewSlide?.animationsHandler) {
const aAnimatedElementMap =
metaNewSlide.animationsHandler.getAnimatedElementMap();


"Caolán McNamara (via github)"

unread,
Mar 2, 2026, 10:46:23 AMMar 2
to collaboraon...@googlegroups.com
browser/src/control/Control.Toolbar.js | 7 +++++++
1 file changed, 7 insertions(+)

New commits:
commit 61ef4b1ed1054d4950da7e38fd924c0cc9cb4825
Author: Caolán McNamara <caolan....@collabora.com>
AuthorDate: Mon Mar 2 12:56:06 2026 +0000
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Mon Mar 2 15:45:52 2026 +0000

add 'grid' role to these custom popups

NVDA was remaining in "browse mode", so keyboard navigation didn't
work unless toggled out of that mode manually, with these roles
set it automatically enters "focus mode"

Signed-off-by: Caolán McNamara <caolan....@collabora.com>
Change-Id: Icab77bd0deb599854dd7979411a250c65761c6b0

diff --git a/browser/src/control/Control.Toolbar.js b/browser/src/control/Control.Toolbar.js
index aebe6492ec..fc69ea53be 100644
--- a/browser/src/control/Control.Toolbar.js
+++ b/browser/src/control/Control.Toolbar.js
@@ -311,6 +311,7 @@ function getInsertTablePopupElements(closeCallback) {

const grid = document.createElement('div');
grid.className = 'inserttable-grid';
+ grid.setAttribute('role', 'grid');
grid.onmouseover = highlightTableFunction;
grid.onclick = sendInsertTableFunction;

@@ -344,10 +345,12 @@ function insertTable(grid = document.getElementsByClassName('inserttable-grid')[
for (var r = 0; r < rows; r++) {
const row = document.createElement('div');
row.className = 'row';
+ row.setAttribute('role', 'row');
grid.appendChild(row);

for (var c = 0; c < cols; c++) {
const col = document.createElement('button');
+ col.setAttribute('role', 'gridcell');
col.setAttribute('aria-label', (1 + r) + 'x' + (1 + c));
col.onfocus = highlightTableFunction;
col.className = 'col';
@@ -606,6 +609,7 @@ function insertShapes(shapeType, grid = document.getElementsByClassName('inserts
var idx = 0;
const row = document.createElement('div');
row.className = 'row';
+ row.setAttribute('role', 'row');
grid.appendChild(row);
for (let r = 0; r < rows; r++) {

@@ -618,6 +622,7 @@ function insertShapes(shapeType, grid = document.getElementsByClassName('inserts
const col = document.createElement('div');

col.className = 'col w2ui-icon ' + shape.img;
+ col.setAttribute('role', 'gridcell');
col.dataset.uno = shape.uno;
col.setAttribute('data-cooltip', shape.text);
window.L.control.attachTooltipEventListener(col, map);
@@ -637,6 +642,7 @@ function getShapesPopupElements(closeCallback) {

const grid = document.createElement('div');
grid.className = 'insertshape-grid';
+ grid.setAttribute('role', 'grid');
grid.onclick = onShapeClickFunction;
grid.onkeyup = onShapeKeyUpFunction;
grid.onkeydown = onShapeKeyDownFunction;
@@ -671,6 +677,7 @@ function getConnectorsPopupElements(closeCallback) {

const grid = document.createElement('div');
grid.className = 'insertshape-grid';
+ grid.setAttribute('role', 'grid');
grid.onclick = onShapeClickFunction;
grid.onkeyup = onShapeKeyUpFunction;
grid.onkeydown = onShapeKeyDownFunction;

"Parth Raiyani (via github)"

unread,
Mar 2, 2026, 2:59:59 PMMar 2
to collaboraon...@googlegroups.com
browser/src/control/Control.Toolbar.js | 1 +
1 file changed, 1 insertion(+)

New commits:
commit dbba53a3cf6145af274b608ea5d2db1e79cd918b
Author: Parth Raiyani <parth....@collabora.com>
AuthorDate: Mon Mar 2 18:40:54 2026 +0530
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Mon Mar 2 19:59:45 2026 +0000

fix: add aria-label for shape icon to be recognize by screen reader

Signed-off-by: Parth Raiyani <parth....@collabora.com>
Change-Id: If558558553d68147d8ff4e5468b3bad94478d482

diff --git a/browser/src/control/Control.Toolbar.js b/browser/src/control/Control.Toolbar.js
index fc69ea53be..9d9fa7557e 100644
--- a/browser/src/control/Control.Toolbar.js
+++ b/browser/src/control/Control.Toolbar.js
@@ -625,6 +625,7 @@ function insertShapes(shapeType, grid = document.getElementsByClassName('inserts
col.setAttribute('role', 'gridcell');
col.dataset.uno = shape.uno;
col.setAttribute('data-cooltip', shape.text);
+ col.setAttribute('aria-label', shape.text);
window.L.control.attachTooltipEventListener(col, map);
col.tabIndex = 0;
col.setAttribute('index', r + ':' + c);

"Andras Timar (via github)"

unread,
Mar 3, 2026, 3:44:25 AMMar 3
to collaboraon...@googlegroups.com
browser/src/control/jsdialog/Definitions.Menu.ts | 30 ++++++++++++++++-------
1 file changed, 21 insertions(+), 9 deletions(-)

New commits:
commit b37f5bac34e2a1eb737eb27b6c36ebf30e7af477
Author: Andras Timar <andras...@collabora.com>
AuthorDate: Mon Feb 23 08:40:22 2026 +0100
Commit: Miklos Vajna <vmi...@collabora.com>
CommitDate: Tue Mar 3 09:44:14 2026 +0100

fix .uno:LineStyle dispatch to send proper BorderLine2 struct

The LineStyle property was sent as flat short values, but the core's
SvxLineItem::PutValue expects a com.sun.star.table.BorderLine2 struct
(nMemId == 0 path). When TransformParameters failed to convert the
short to an SvxLineItem, the item was never put into the ItemSet,
causing a crash in SfxItemPool::GetUserOrPoolDefaultItem.

Send the full BorderLine2 struct matching the format used by
SetBorderStyle in Control.Toolbar.js.

Signed-off-by: Andras Timar <andras...@collabora.com>
Change-Id: I81e0249efcceb62c8a11a0f6aa197d402d3ac489

diff --git a/browser/src/control/jsdialog/Definitions.Menu.ts b/browser/src/control/jsdialog/Definitions.Menu.ts
index 8a2710565e..59ac50675a 100644
--- a/browser/src/control/jsdialog/Definitions.Menu.ts
+++ b/browser/src/control/jsdialog/Definitions.Menu.ts
@@ -142,18 +142,30 @@ enum UNO_BorderLineStyle {

function getLineStyleModificationCommand(
LineStyle: UNO_BorderLineStyle,
- n1: number, // Corresponds to SvxBorderLineWidth
- n2: number,
- n3: number,
+ nOut: number, // outer line width, maps to SvxBorderLine nOut
+ nIn: number, // inner line width, maps to SvxBorderLine nIn
+ nDist: number, // distance between lines
): string {
- const borderLine2Properties = {
- LineStyle: { type: 'short', value: LineStyle },
- InnerLineWidth: { type: 'short', value: n1 },
- OuterLineWidth: { type: 'short', value: n2 },
- LineDistance: { type: 'short', value: n3 },
+ // The LineStyle property must be a BorderLine2 struct for
+ // SvxLineItem::PutValue to parse it correctly (nMemId == 0).
+ const params = {
+ LineStyle: {
+ type: 'com.sun.star.table.BorderLine2',
+ value: {
+ Color: { type: 'com.sun.star.util.Color', value: 0 },
+ InnerLineWidth: { type: 'short', value: nIn },
+ OuterLineWidth: { type: 'short', value: nOut },
+ LineDistance: { type: 'short', value: nDist },
+ LineStyle: { type: 'short', value: LineStyle },
+ LineWidth: {
+ type: 'unsigned long',
+ value: nOut + nIn + nDist,
+ },
+ },
+ },
};

- const jsonParams = JSON.stringify(borderLine2Properties);
+ const jsonParams = JSON.stringify(params);

// The UNO command name itself, from `scslots.hxx`
return `.uno:LineStyle ${jsonParams}`;

"Henry Castro (via github)"

unread,
Mar 3, 2026, 2:17:39 PMMar 3
to collaboraon...@googlegroups.com
browser/src/dom/NotebookbarAccessibility.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)

New commits:
commit f83040018cc9a1c58ae1481f4c8469e96eb8b258
Author: Henry Castro <hca...@collabora.com>
AuthorDate: Tue Mar 3 09:37:13 2026 -0400
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Tue Mar 3 19:16:58 2026 +0000

browser: a11y: fix click not working on UNO tool button

The element retrieved by ID is assumed to be a button type,
since .click() is a trusted method that works for some input
elements. However, the main container of the UNO tool button
is a <div> tag, which cannot be clicked programmatically.

Change-Id: I8da5e39ebd1a7277a6d7cc4764bd51a91c110cbb
Signed-off-by: Henry Castro <hca...@collabora.com>

diff --git a/browser/src/dom/NotebookbarAccessibility.js b/browser/src/dom/NotebookbarAccessibility.js
index fa2b8b8c40..3ab92b4903 100644
--- a/browser/src/dom/NotebookbarAccessibility.js
+++ b/browser/src/dom/NotebookbarAccessibility.js
@@ -192,7 +192,8 @@ var NotebookbarAccessibility = function() {
else if (this.state === 1) {
itemWasClicked = true;
this.setTabItemDescription(element);
- element.click();
+ var clickTarget = element.querySelector('button.unobutton') || element;
+ clickTarget.click();
if (this.filteredItem && this.filteredItem.focusBack === true) {
this.focusToMap();
}

"Caolán McNamara (via github)"

unread,
Mar 3, 2026, 4:17:49 PMMar 3
to collaboraon...@googlegroups.com
browser/src/dom/NotebookbarAccessibilityDefinitions.js | 3 +++
1 file changed, 3 insertions(+)

New commits:
commit ebef0204b7f54a7caf3e82ba703ee17ad94d7965
Author: Caolán McNamara <caolan....@collabora.com>
AuthorDate: Tue Mar 3 13:41:34 2026 +0000
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Tue Mar 3 21:17:22 2026 +0000

make shortcut work for 'Ruler' and 'Status Bar' checkboxes

Signed-off-by: Caolán McNamara <caolan....@collabora.com>
Change-Id: Ia56e0e412e08fde0bcb4ff8db6c9ff2002220000

diff --git a/browser/src/dom/NotebookbarAccessibilityDefinitions.js b/browser/src/dom/NotebookbarAccessibilityDefinitions.js
index a15c18ad4c..489f235d76 100644
--- a/browser/src/dom/NotebookbarAccessibilityDefinitions.js
+++ b/browser/src/dom/NotebookbarAccessibilityDefinitions.js
@@ -47,6 +47,9 @@ var NotebookbarAccessibilityDefinitions = function() {
} else if (element) {
// regular uno button
list.push({ id: id + '-button', focusBack: rawList[i].accessibility.focusBack, combination: combination });
+ } else if (document.getElementById(id + '-input')) {
+ // checkbox
+ list.push({ id: id + '-input', focusBack: rawList[i].accessibility.focusBack, combination: combination });
} else {
// other
list.push({ id: id, focusBack: rawList[i].accessibility.focusBack, combination: combination });

"codewithvk (via github)"

unread,
Mar 4, 2026, 1:36:42 AMMar 4
to collaboraon...@googlegroups.com
browser/src/control/jsdialog/Util.Accessibility.ts | 12 ------------
1 file changed, 12 deletions(-)

New commits:
commit 3e4e2ac73385d0830c73c1b90397aba744958c15
Author: codewithvk <vivek....@collabora.com>
AuthorDate: Tue Mar 3 16:34:40 2026 +0530
Commit: Parth Raiyani <58837400...@users.noreply.github.com>
CommitDate: Wed Mar 4 12:06:03 2026 +0530

a11y: remove false positive missing aria label warning

Signed-off-by: codewithvk <vivek....@collabora.com>
Change-Id: I71430f45d86754ba3e8376d513f314af65fc44d3

diff --git a/browser/src/control/jsdialog/Util.Accessibility.ts b/browser/src/control/jsdialog/Util.Accessibility.ts
index e42e778672..a8d9f5889d 100644
--- a/browser/src/control/jsdialog/Util.Accessibility.ts
+++ b/browser/src/control/jsdialog/Util.Accessibility.ts
@@ -121,18 +121,6 @@ JSDialog.AddAriaLabel = function (
element.setAttribute('aria-label', data.aria.label);
} else if (data.text) {
element.setAttribute('aria-label', builder._cleanText(data.text));
- } else {
- // No valid label source - backend need to add label
- app.console.warn(
- '[A11y] Missing aria label: element has no accessible label. ',
- {
- elementId: element.id,
- elementTag: element.tagName,
- elementClass: element.className,
- dataId: data.id,
- dataType: data.type,
- },
- );
}
};


"Caolán McNamara (via github)"

unread,
Mar 4, 2026, 2:10:52 AMMar 4
to collaboraon...@googlegroups.com
browser/src/control/jsdialog/Widget.MenuButton.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)

New commits:
commit 631a12640af7490550da7be98dc3b9b7a02a7520
Author: Caolán McNamara <caolan....@collabora.com>
AuthorDate: Tue Mar 3 15:16:32 2026 +0000
Commit: Miklos Vajna <vmi...@collabora.com>
CommitDate: Wed Mar 4 08:10:14 2026 +0100

don't steal focus on launching integrator dialogs

there is a race here and online might win again the intergator and
steal focus away from those dialogs, so don't take focus on
saveas- and exportas-

Signed-off-by: Caolán McNamara <caolan....@collabora.com>
Change-Id: Ic43518df483917d6f65343544e1bda7f0868e842

diff --git a/browser/src/control/jsdialog/Widget.MenuButton.js b/browser/src/control/jsdialog/Widget.MenuButton.js
index 16673fd208..68b0338d9d 100644
--- a/browser/src/control/jsdialog/Widget.MenuButton.js
+++ b/browser/src/control/jsdialog/Widget.MenuButton.js
@@ -112,7 +112,8 @@ function _menubuttonControl (parentContainer, data, builder) {
return true;
} else if (eventType === 'selected' && entry && entry.action) {
app.dispatcher.dispatch(entry.action);
- JSDialog.CloseDropdown(dropdownId);
+ const opensExternal = entry.action.startsWith('exportas-') || entry.action.startsWith('saveas-');
+ JSDialog.CloseDropdown(dropdownId, opensExternal);
return true;
} else if (eventType === 'selected' && entry && entry.id) {
builder.callback('menubutton', 'select', control.container, entry.id, builder);

"Gökay Şatır (via github)"

unread,
Mar 4, 2026, 2:54:37 AMMar 4
to collaboraon...@googlegroups.com
browser/src/app/ViewLayoutMultiPage.ts | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)

New commits:
commit c99433c084669868909c0bf9a93d54a3dffd998e
Author: Gökay Şatır <gokay...@collabora.com>
AuthorDate: Tue Feb 24 14:48:12 2026 +0300
Commit: Miklos Vajna <vmi...@collabora.com>
CommitDate: Wed Mar 4 08:54:28 2026 +0100

MultiPageView: React to size changes.

Issue: At the moment this class is informed, the canvas size is not set yet.
This results in layouting the view according to previous size.

Fix: Do the reset in layouting cycle.

Signed-off-by: Gökay Şatır <gokay...@collabora.com>
Change-Id: I7efd202c1990a3f252a0d4555601d9f41fff059b

diff --git a/browser/src/app/ViewLayoutMultiPage.ts b/browser/src/app/ViewLayoutMultiPage.ts
index 435c5fe5b7..4bfea51f07 100644
--- a/browser/src/app/ViewLayoutMultiPage.ts
+++ b/browser/src/app/ViewLayoutMultiPage.ts
@@ -335,8 +335,10 @@ class ViewLayoutMultiPage extends ViewLayoutNewBase {
public reset() {
if (!app.file.writer.pageRectangleList.length) return;

- this.resetViewLayout();
- this.updateViewData();
+ app.layoutingService.appendLayoutingTask(() => {
+ this.resetViewLayout();
+ this.updateViewData();
+ });
}

public getTotalSideSpace() {

"Andras Timar (via github)"

unread,
Mar 4, 2026, 2:59:43 AMMar 4
to collaboraon...@googlegroups.com
browser/src/control/Control.StatusBar.js | 22 ++++++++++++----------
1 file changed, 12 insertions(+), 10 deletions(-)

New commits:
commit 265bfb57c8d7d9ed524b84178681c8471397e2b6
Author: Andras Timar <andras...@collabora.com>
AuthorDate: Tue Mar 3 14:46:57 2026 +0100
Commit: Miklos Vajna <vmi...@collabora.com>
CommitDate: Wed Mar 4 08:59:01 2026 +0100

support multi-selection in Calc cell function menu on the status bar

The status bar cell function menu (Average, Sum, Count, etc.)
now supports toggling multiple functions, matching the desktop
behavior. Previously clicking a menu item replaced the entire
selection with just that one item.

The selection state is a bitmask — use XOR to toggle the clicked
bit against the current state from the state change handler.
Clicking 'None' clears everything.

Signed-off-by: Andras Timar <andras...@collabora.com>
Change-Id: Ia08d087cc9f1bbd751b7907809f82c3f407a3071

diff --git a/browser/src/control/Control.StatusBar.js b/browser/src/control/Control.StatusBar.js
index 750f644194..a41c2bab26 100644
--- a/browser/src/control/Control.StatusBar.js
+++ b/browser/src/control/Control.StatusBar.js
@@ -81,17 +81,19 @@ class StatusBar extends JSDialog.Toolbar {
this.map.setZoom(selected[0].scale, null, true /* animate? */);
return;
} else if (object.id === 'StateTableCellMenu') {
- // TODO: multi-selection
- var selected = [];
- if (data === '1') { // 'None' was clicked, remove all other options
- selected = ['1'];
- } else { // Something else was clicked, remove the 'None' option from the array
- selected = [data];
- }
+ var clicked = parseInt(data);
+ var current = parseInt(app.map['stateChangeHandler'].getItemValue('.uno:StatusBarFunc')) || 0;

- var value = 0;
- for (var it = 0; it < selected.length; it++) {
- value = +value + parseInt(selected[it]);
+ var value;
+ if (clicked === 1) {
+ // 'None' was clicked — clear everything
+ value = 0;
+ } else {
+ // Toggle the clicked bit
+ value = current ^ clicked;
+ // Clear the 'None' bit (1) if any function is now active
+ if (value & ~1)
+ value = value & ~1;
}

var command = {

"Gökay Şatır (via github)"

unread,
Mar 4, 2026, 3:07:56 AMMar 4
to collaboraon...@googlegroups.com
browser/src/app/ViewLayoutMultiPage.ts | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)

New commits:
commit dd695bbfbcd6791f708bb80f17e992fdd5ec06c5
Author: Gökay Şatır <gokay...@collabora.com>
AuthorDate: Wed Feb 25 13:55:26 2026 +0300
Commit: Miklos Vajna <vmi...@collabora.com>
CommitDate: Wed Mar 4 09:07:33 2026 +0100

MultiPage View: Improve x ordinate checking.

Issue: Sometimes view is scrolled horizontally when not necessary.
Fix improve the X ordinate check.

Signed-off-by: Gökay Şatır <gokay...@collabora.com>
Change-Id: If0a9e8de1946fa9640535729e83b0ba4385cc8c2

diff --git a/browser/src/app/ViewLayoutMultiPage.ts b/browser/src/app/ViewLayoutMultiPage.ts
index 4bfea51f07..d92daa44b8 100644
--- a/browser/src/app/ViewLayoutMultiPage.ts
+++ b/browser/src/app/ViewLayoutMultiPage.ts
@@ -298,7 +298,14 @@ class ViewLayoutMultiPage extends ViewLayoutNewBase {
if (layoutR) {
let scrolled = false;

- if (!this.viewedRectangle.containsX(point.x)) {
+ // Check if the target X is already visible in the viewport.
+ const viewportWidth = this.getDocumentAnchorSection().size[0];
+ const xVisibleInViewport =
+ viewR.pX1 >= this.scrollProperties.viewX &&
+ viewR.pX1 + layoutR.pWidth <=
+ this.scrollProperties.viewX + viewportWidth;
+
+ if (!xVisibleInViewport) {
this.scrollProperties.startX = Math.round(
(viewR.pX1 / this._viewSize.pX) *
this.scrollProperties.horizontalScrollLength,

"Miklos Vajna (via github)"

unread,
Mar 4, 2026, 4:52:15 AMMar 4
to collaboraon...@googlegroups.com
browser/src/app/ViewLayoutCompareChanges.ts | 18 +++++++++++-------
1 file changed, 11 insertions(+), 7 deletions(-)

New commits:
commit 207880a8965e169966cdc206c54e0183e85ad65a
Author: Miklos Vajna <vmi...@collabora.com>
AuthorDate: Wed Mar 4 09:42:10 2026 +0100
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Wed Mar 4 09:51:52 2026 +0000

browser, document compare: annotate overriding functions with 'override'

Similar to how this is done already in TooltipAnchorSection.ts.

Signed-off-by: Miklos Vajna <vmi...@collabora.com>
Change-Id: I4624de3d68c11c7f41daa9c8b3b14424cd373526

diff --git a/browser/src/app/ViewLayoutCompareChanges.ts b/browser/src/app/ViewLayoutCompareChanges.ts
index 75f2a3e6a6..4910a389ac 100644
--- a/browser/src/app/ViewLayoutCompareChanges.ts
+++ b/browser/src/app/ViewLayoutCompareChanges.ts
@@ -44,7 +44,7 @@ class ViewLayoutCompareChanges extends ViewLayoutNewBase {
this.refreshView();
}

- public adjustViewZoomLevel() {
+ public override adjustViewZoomLevel() {
Util.ensureValue(app.activeDocument);

const min = 0.1;
@@ -62,7 +62,7 @@ class ViewLayoutCompareChanges extends ViewLayoutNewBase {
app.map.setZoom(zoom, { animate: false });
}

- protected refreshCurrentCoordList() {
+ protected override refreshCurrentCoordList() {
super.refreshCurrentCoordList();

const additionalCoords: Array<TileCoordData> = [];
@@ -140,7 +140,7 @@ class ViewLayoutCompareChanges extends ViewLayoutNewBase {
else return this.halfWidth + this.viewGap - this.scrollProperties.viewX;
}

- public documentToViewX(point: cool.SimplePoint): number {
+ public override documentToViewX(point: cool.SimplePoint): number {
Util.ensureValue(app.activeDocument);

// Default to right side.
@@ -149,11 +149,13 @@ class ViewLayoutCompareChanges extends ViewLayoutNewBase {
return point.pX + this.getDeflectionX(point.mode);
}

- public documentToViewY(point: cool.SimplePoint): number {
+ public override documentToViewY(point: cool.SimplePoint): number {
return point.pY + this.yStart - this.scrollProperties.viewY;
}

- public canvasToDocumentPoint(point: cool.SimplePoint): cool.SimplePoint {
+ public override canvasToDocumentPoint(
+ point: cool.SimplePoint,
+ ): cool.SimplePoint {
const result = point.clone();

point.mode =
@@ -168,11 +170,13 @@ class ViewLayoutCompareChanges extends ViewLayoutNewBase {
return result;
}

- public canScrollHorizontal(documentAnchor: CanvasSectionObject): boolean {
+ public override canScrollHorizontal(
+ documentAnchor: CanvasSectionObject,
+ ): boolean {
return this.viewSize.pX > Math.round(documentAnchor.size[0] * 0.5);
}

- public scroll(pX: number, pY: number): boolean {
+ public override scroll(pX: number, pY: number): boolean {
const scrolled = super.scroll(pX, pY);

if (scrolled) {

"Gökay Şatır (via github)"

unread,
Mar 4, 2026, 5:58:26 AMMar 4
to collaboraon...@googlegroups.com
browser/src/canvas/CanvasSectionContainer.ts | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)

New commits:
commit 713381217ecf6f888b7b093802a8965503053674
Author: Gökay Şatır <gokay...@collabora.com>
AuthorDate: Thu Feb 19 15:17:53 2026 +0300
Commit: Gökay Şatır <gokay...@gmail.com>
CommitDate: Wed Mar 4 13:58:14 2026 +0300

CanvasSectionContainer: Clear mouse positions when window losts focus.

When user starts an event that lasts for some time, like dragging, CanvasSectionContainer assumes that it will handle the event until the end.
If user starts an event and then the window loses focus, CanvasSectionContainer can not end the event.

Example scenario:
* Start selecting text.
* While holding the mouse button, use CTRL+TAB (or similar, based on OS) to switch to another tab.
* Now the click event has not ended yet and the focused window has changed.
* From the OS point, everything is normal.
* FROM CSC point of view, mouse button is still pressed.
* If user releases the button before returning to the document page, CSC will not handle the mouse up event.

Possible Issues:
* This already causes stuck selection issue.
* There may be some other issues with moving the shapes etc.

Fix:
* Try to end the mouse event gracefully if a dragging event is started when window loses focus.

Signed-off-by: Gökay Şatır <gokay...@collabora.com>
Change-Id: I71e6a014e25a02dd56ac8b585a5be128af68942a

diff --git a/browser/src/canvas/CanvasSectionContainer.ts b/browser/src/canvas/CanvasSectionContainer.ts
index 603ee37fca..899203ff51 100644
--- a/browser/src/canvas/CanvasSectionContainer.ts
+++ b/browser/src/canvas/CanvasSectionContainer.ts
@@ -230,6 +230,7 @@ class CanvasSectionContainer {
this.canvas.ontouchcancel = this.onTouchCancel.bind(this);
this.canvas.ondrop = this.onDrop.bind(this);
this.canvas.ondragover = this.onDragOver.bind(this);
+ window.addEventListener('blur', this.onWindowBlur.bind(this));

// Some explanation first.
// When the user uses the mouse wheel for scrolling, different browsers use different technics for calculating the deltaY and deltaX values.
@@ -1312,6 +1313,31 @@ class CanvasSectionContainer {
}
}

+ // When the browser window/tab loses focus during a drag, we never receive the mouseup event.
+ // This leaves draggingSomething=true and sections (e.g. MouseControl) never send 'buttonup' to the core.
+ // This may cause a stuck selection in some cases.
+ private onWindowBlur () {
+ if (!this.draggingSomething)
+ return;
+
+ // Propagate a synthetic mouseUp to the section that received the original
+ // mouseDown so it can clean up (e.g. MouseControl sends 'buttonup' to core).
+ if (this.sectionOnMouseDown) {
+ var section: CanvasSectionObject = this.getSectionWithName(this.sectionOnMouseDown);
+ if (section) {
+ var position = this.positionOnMouseUp || this.mousePosition || this.positionOnMouseDown;
+ // Create a synthetic MouseEvent so section handlers can safely access
+ // event properties (e.g. stopPropagation, modifiers).
+ var syntheticEvent = new MouseEvent('mouseup', { button: 0, buttons: 0 });
+ this.propagateOnMouseUp(section, this.convertPositionToSectionLocale(section, position), syntheticEvent);
+ }
+ }
+
+ this.clearMousePositions();
+ this.mousePosition = null;
+ this.mouseIsInside = false;
+ }
+
public onTouchStart (e: TouchEvent) { // Should be ignored unless this.draggingSomething = true.
if (e.touches.length === 1) {
this.clearMousePositions();

"Parth Raiyani (via github)"

unread,
Mar 4, 2026, 7:22:17 AMMar 4
to collaboraon...@googlegroups.com
browser/src/control/Control.NotebookbarWriter.js | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)

New commits:
commit 8dbb5822ce5faa4e7bb52bb52bfdd26441da6f7e
Author: Parth Raiyani <parth....@collabora.com>
AuthorDate: Wed Mar 4 16:31:58 2026 +0530
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Wed Mar 4 12:21:50 2026 +0000

a11y: enhance accessibility attributes for formula tab items

- this adds missing shortcuts for formula tab

Signed-off-by: Parth Raiyani <parth....@collabora.com>
Change-Id: I7dc14682209d61c9bbccc5599d19934cb619acaa
Signed-off-by: Parth Raiyani <parth....@collabora.com>

diff --git a/browser/src/control/Control.NotebookbarWriter.js b/browser/src/control/Control.NotebookbarWriter.js
index 1e501c4ee3..fe29e519c3 100644
--- a/browser/src/control/Control.NotebookbarWriter.js
+++ b/browser/src/control/Control.NotebookbarWriter.js
@@ -2742,31 +2742,39 @@ window.L.Control.NotebookbarWriter = window.L.Control.Notebookbar.extend({
getFormulaTab: function() {
var content = [
{
+ 'id': 'change-font',
'type': 'bigtoolitem',
'text': _UNO('.uno:ChangeFont', 'text'),
'command': '.uno:ChangeFont',
- 'icon': 'lc_fontdialog.svg'
+ 'icon': 'lc_fontdialog.svg',
+ 'accessibility': { focusBack: true, combination: 'CF', de: null },
},
{ type: 'separator', id: 'formula-changefont-break', orientation: 'vertical' },
{
+ 'id': 'change-font-size',
'type': 'bigtoolitem',
'text': _UNO('.uno:ChangeFontSize', 'text'),
'command': '.uno:ChangeFontSize',
- 'icon': 'lc_fontheight.svg'
+ 'icon': 'lc_fontheight.svg',
+ 'accessibility': { focusBack: true, combination: 'FZ', de: null },
},
{ type: 'separator', id: 'formula-changefontsize-break', orientation: 'vertical' },
{
+ 'id': 'change-distance',
'type': 'bigtoolitem',
'text': _UNO('.uno:ChangeDistance', 'text'),
'command': '.uno:ChangeDistance',
'icon': 'lc_spacing.svg',
+ 'accessibility': { focusBack: true, combination: 'CD', de: null },
},
{ type: 'separator', id: 'formula-changedistance-break', orientation: 'vertical' },
{
+ 'id': 'change-alignment',
'type': 'bigtoolitem',
'text': _UNO('.uno:ChangeAlignment', 'text'),
'command': '.uno:ChangeAlignment',
- 'icon': 'lc_fontworkalignmentfloater.svg'
+ 'icon': 'lc_fontworkalignmentfloater.svg',
+ 'accessibility': { focusBack: true, combination: 'CA', de: null },
}
];
return this.getTabPage(formulaTabName, content);

"Raul-Ionut Nastasie (via github)"

unread,
Mar 4, 2026, 10:29:54 AMMar 4
to collaboraon...@googlegroups.com
browser/src/control/Control.NavigatorPanel.ts | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)

New commits:
commit 68efbfb2227d2d25bbf6073e2b3ea4b9053bdde0
Author: Raul-Ionut Nastasie <raul-ionu...@collabora.com>
AuthorDate: Wed Feb 25 18:24:10 2026 +0100
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Wed Mar 4 15:29:27 2026 +0000

Navigator bar automatically gets focused when shown

Signed-off-by: Raul-Ionut Nastasie <raul-ionu...@collabora.com>
Change-Id: Id14622b821fd56d21509b2560499edda6ea9db9a

diff --git a/browser/src/control/Control.NavigatorPanel.ts b/browser/src/control/Control.NavigatorPanel.ts
index 0259f44cf5..1fc3000b63 100644
--- a/browser/src/control/Control.NavigatorPanel.ts
+++ b/browser/src/control/Control.NavigatorPanel.ts
@@ -36,6 +36,7 @@ class NavigatorPanel extends SidebarBase {
this.map.on('focussearch', this.focusSearch, this);
this.navigationPanel = document.getElementById(`navigation-sidebar`);
this.navigationPanel.setAttribute('aria-label', _('Navigation Panel'));
+ this.navigationPanel.setAttribute('tabindex', '-1');

this.floatingNavIcon = document.getElementById(`navigator-floating-icon`);
this.presentationControlsWrapper = this.navigationPanel.querySelector(
@@ -69,7 +70,7 @@ class NavigatorPanel extends SidebarBase {
!window.mode.isMobile()
) {
// Navigator panel should be visible and by default we should open slide sorter in case of impress/draw
- this.showNavigationPanel();
+ this.showNavigationPanel(false);
}
}

@@ -311,7 +312,7 @@ class NavigatorPanel extends SidebarBase {
this.floatingNavIcon.addEventListener(
'click',
function () {
- this.showNavigationPanel();
+ this.showNavigationPanel(true);
if (app.map.isPresentationOrDrawing()) {
this.switchNavigationTab('tab-slide-sorter');
} else {
@@ -339,7 +340,7 @@ class NavigatorPanel extends SidebarBase {
this.builder.build(this.container, [navigatorData], false);
// There is case where user can directly click navigator from notebookbar view option
// in that case we first show the navigation panel and then switch to tab view
- this.showNavigationPanel();
+ this.showNavigationPanel(false);
// TODO: remove jQuery animation
$('#navigator-dock-wrapper').show(200);
app.showNavigator = true;
@@ -425,12 +426,14 @@ class NavigatorPanel extends SidebarBase {
}
}

- showNavigationPanel() {
+ showNavigationPanel(setFocus: boolean) {
app.layoutingService.appendLayoutingTask(() => {
this.navigationPanel.classList.add('visible');
this.floatingNavIcon.classList.remove('visible');
// this will update the indentation marks for elements like ruler
app.map.fire('fixruleroffset');
+
+ if (setFocus) this.navigationPanel.focus();
});
}


"Andras Timar (via github)"

unread,
Mar 4, 2026, 12:12:16 PMMar 4
to collaboraon...@googlegroups.com
browser/src/control/Control.AboutDialog.ts | 2 +-
browser/src/control/Control.Zotero.js | 2 +-
browser/src/map/Clipboard.js | 4 ++--
browser/src/map/handler/Map.SlideShow.js | 2 +-
browser/src/slideshow/SlideShowPresenter.ts | 2 +-
5 files changed, 6 insertions(+), 6 deletions(-)

New commits:
commit 4df2b40e91949e9e147b77dcdf8c83dbdbfc71b7
Author: Andras Timar <andras...@collabora.com>
AuthorDate: Wed Mar 4 16:28:16 2026 +0100
Commit: Andras Timar <andras...@collabora.com>
CommitDate: Wed Mar 4 18:11:35 2026 +0100

Mark user-facing UI strings for translation

Wrap several untranslated UI strings with _() so they can be
localized: snackbar messages in About dialog and Zotero, modal
dialog text for hidden slides, and clipboard warning dialog text
including Copy/Cut/Paste labels.

Signed-off-by: Andras Timar <andras...@collabora.com>
Change-Id: I68bc393f53c4134c6b3360dc768e2a58272fb58d

diff --git a/browser/src/control/Control.AboutDialog.ts b/browser/src/control/Control.AboutDialog.ts
index f152187a7f..c028e6d5c0 100644
--- a/browser/src/control/Control.AboutDialog.ts
+++ b/browser/src/control/Control.AboutDialog.ts
@@ -424,7 +424,7 @@ class AboutDialog {
private contentHasBeenCopiedShowSnackbar() {
const timeout = 1000;
this.map.uiManager.showSnackbar(
- 'Version information has been copied',
+ _('Version information has been copied'),
null,
null,
timeout,
diff --git a/browser/src/control/Control.Zotero.js b/browser/src/control/Control.Zotero.js
index 407a6d4be6..f7985a4bc6 100644
--- a/browser/src/control/Control.Zotero.js
+++ b/browser/src/control/Control.Zotero.js
@@ -1525,7 +1525,7 @@ window.L.Control.Zotero = window.L.Control.extend({
}
this.map.sendUnoCommand(command, parametes, true);
this.resetCitation();
- this.map.uiManager.showSnackbar('Unlinked citations');
+ this.map.uiManager.showSnackbar(_('Unlinked citations'));
},

unlinkCitations: function() {
diff --git a/browser/src/map/Clipboard.js b/browser/src/map/Clipboard.js
index 4457257ddf..e644e1df51 100644
--- a/browser/src/map/Clipboard.js
+++ b/browser/src/map/Clipboard.js
@@ -1466,7 +1466,7 @@ window.L.Clipboard = window.L.Class.extend({
else {
const ctrlText = app.util.replaceCtrlAltInMac('Ctrl');
const p = document.createElement('p');
- p.textContent = 'Your browser has very limited access to the clipboard, so use these keyboard shortcuts:';
+ p.textContent = _('Your browser has very limited access to the clipboard, so use these keyboard shortcuts:');
innerDiv.appendChild(p);

const table = document.createElement('table');
@@ -1500,7 +1500,7 @@ window.L.Clipboard = window.L.Class.extend({
table.appendChild(row);
for (let i = 0; i < 3; i++) {
const cell = document.createElement('td');
- cell.textContent = i === 0 ? 'Copy': (i === 1 ? 'Cut': 'Paste');
+ cell.textContent = i === 0 ? _('Copy'): (i === 1 ? _('Cut'): _('Paste'));
row.appendChild(cell);
}
}
diff --git a/browser/src/map/handler/Map.SlideShow.js b/browser/src/map/handler/Map.SlideShow.js
index da28dd858b..f24714a9fa 100644
--- a/browser/src/map/handler/Map.SlideShow.js
+++ b/browser/src/map/handler/Map.SlideShow.js
@@ -57,7 +57,7 @@ window.L.Map.SlideShow = window.L.Handler.extend({

if (app.impress.areAllSlidesHidden()) {
this._map.uiManager.showInfoModal('allslidehidden-modal', _('Empty Slide Show'),
- 'All slides are hidden!', '', _('OK'), function () { }, false, 'allslidehidden-modal-response');
+ _('All slides are hidden!'), '', _('OK'), function () { }, false, 'allslidehidden-modal-response');
return;
}

diff --git a/browser/src/slideshow/SlideShowPresenter.ts b/browser/src/slideshow/SlideShowPresenter.ts
index 91277e666c..9000bab74a 100644
--- a/browser/src/slideshow/SlideShowPresenter.ts
+++ b/browser/src/slideshow/SlideShowPresenter.ts
@@ -1248,7 +1248,7 @@ class SlideShowPresenter {
this._map.uiManager.showInfoModal(
'allslidehidden-modal',
_('Empty Slide Show'),
- 'All slides are hidden!',
+ _('All slides are hidden!'),
'',
_('OK'),
() => {

"Gökay Şatır (via github)"

unread,
Mar 5, 2026, 3:50:59 AMMar 5
to collaboraon...@googlegroups.com
browser/src/app/ViewLayoutMultiPage.ts | 3 ++
browser/src/control/HRuler.ts | 35 ++++++++++++++++++++++++++----
browser/src/layer/tile/CanvasTileLayer.js | 7 ++++++
3 files changed, 41 insertions(+), 4 deletions(-)

New commits:
commit 99141db602c702e89d28a99b080970498fa92a66
Author: Gökay Şatır <gokay...@collabora.com>
AuthorDate: Tue Mar 3 15:59:47 2026 +0300
Commit: Miklos Vajna <vmi...@collabora.com>
CommitDate: Thu Mar 5 09:50:52 2026 +0100

Multipage view - horizontal ruler: Ruler position needs adjustments in multi page view.

Reason: Multi page view doesn't overlap screen coordinates with document coordinates. Ruler position needes to be recalculated for the view.

Signed-off-by: Gökay Şatır <gokay...@collabora.com>
Change-Id: Ib0199781486fb0d3b660fcdc9d80b876bc18448f

diff --git a/browser/src/app/ViewLayoutMultiPage.ts b/browser/src/app/ViewLayoutMultiPage.ts
index d92daa44b8..b830509154 100644
--- a/browser/src/app/ViewLayoutMultiPage.ts
+++ b/browser/src/app/ViewLayoutMultiPage.ts
@@ -237,6 +237,9 @@ class ViewLayoutMultiPage extends ViewLayoutNewBase {

this.refreshCurrentCoordList();
TileManager.checkRequestTiles(this.currentCoordList);
+
+ // We most likely scrolled the view. We also need to check ruler position.
+ if (app.UI.horizontalRuler) app.UI.horizontalRuler._fixOffset();
}

public documentToViewX(point: cool.SimplePoint): number {
diff --git a/browser/src/control/HRuler.ts b/browser/src/control/HRuler.ts
index f7e7ee1ff0..f39a81eefb 100644
--- a/browser/src/control/HRuler.ts
+++ b/browser/src/control/HRuler.ts
@@ -813,11 +813,38 @@ class HRuler extends Ruler {
protected _fixOffsetImpl(): void {
if (!app.activeDocument || app.activeDocument.fileSize.x === 0) return;

- const rulerOffset =
- -app.activeDocument.activeLayout.viewedRectangle.cX1 +
- this.options.tileMargin * app.getScale();
+ const layout = app.activeDocument.activeLayout;
+
+ if (layout.type === 'ViewLayoutMultiPage') {
+ const multiPageLayout = layout as ViewLayoutMultiPage;
+ const pageRectList = app.file.writer.pageRectangleList;
+ if (pageRectList.length === 0) return;
+
+ // Find which page the cursor is on.
+ let pageIndex = 0;
+ const cursorRect = app.file.textCursor.rectangle;
+ if (cursorRect) {
+ const cursorPoint = new cool.SimplePoint(cursorRect.x1, cursorRect.y1);
+ pageIndex = multiPageLayout.getClosestRectangleIndex(cursorPoint);
+ }
+
+ // Get the page's top-left corner in document coordinates.
+ const pageRect = pageRectList[pageIndex];
+ const pageTopLeft = new cool.SimplePoint(pageRect[0], pageRect[1]);
+
+ // Convert to screen position (core pixels -> CSS pixels).
+ const screenXCorePixels = layout.documentToViewX(pageTopLeft);
+ const rulerOffset = screenXCorePixels / app.dpiScale;

- this._rFace.style.marginInlineStart = rulerOffset + 'px';
+ const newValue = rulerOffset + 'px';
+ if (this._rFace.style.marginInlineStart !== newValue)
+ this._rFace.style.marginInlineStart = newValue;
+ } else {
+ const rulerOffset =
+ -layout.viewedRectangle.cX1 + this.options.tileMargin * app.getScale();
+
+ this._rFace.style.marginInlineStart = rulerOffset + 'px';
+ }

this._updateParagraphIndentations();
}
diff --git a/browser/src/layer/tile/CanvasTileLayer.js b/browser/src/layer/tile/CanvasTileLayer.js
index 4a15d057e7..ccde4a23d2 100644
--- a/browser/src/layer/tile/CanvasTileLayer.js
+++ b/browser/src/layer/tile/CanvasTileLayer.js
@@ -1792,6 +1792,13 @@ window.L.CanvasTileLayer = window.L.Layer.extend({

// Only for reference equality comparison.
this._lastVisibleCursorRef = app.file.textCursor.rectangle.clone();
+
+ // Normally we don't need to refresh the ruler offset.
+ // But in multi page view, user may have clicked at the page next to the current one.
+ // In that case, we need to fix offset again (if required - it checks values before changing offset).
+ const layout = app.activeDocument ? (app.activeDocument.activeLayout ? app.activeDocument.activeLayout.type : "") : "";
+ if (layout === 'ViewLayoutMultiPage' && app.UI.horizontalRuler)
+ app.UI.horizontalRuler._fixOffset();
},

_isHyperlinkChanged: function(hyperlink)

"Livingstone Asabahebwa (via github)"

unread,
Mar 5, 2026, 4:11:30 AMMar 5
to collaboraon...@googlegroups.com
browser/src/control/jsdialog/Widget.Frame.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)

New commits:
commit 28b01212c3539f7273b60d1341021e4cbcd0036c
Author: Livingstone Asabahebwa <lasab...@gmail.com>
AuthorDate: Thu Sep 18 22:56:53 2025 +0300
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Thu Mar 5 09:11:20 2026 +0000

replace var with const/let

Signed-off-by: Livingstone Asabahebwa <lasab...@gmail.com>
Change-Id: I4468be2d04845ad892e4406f6b18c3f8a1250998

diff --git a/browser/src/control/jsdialog/Widget.Frame.js b/browser/src/control/jsdialog/Widget.Frame.js
index 3e34c3f591..b8968e74eb 100644
--- a/browser/src/control/jsdialog/Widget.Frame.js
+++ b/browser/src/control/jsdialog/Widget.Frame.js
@@ -31,8 +31,8 @@ function _extractLabelText(data) {
return data.text;
}

- for (var i = 0; i < data.children.length; i++) {
- var label = _extractLabelText(data.children[i]);
+ for (let i = 0; i < data.children.length; i++) {
+ const label = _extractLabelText(data.children[i]);
if (label) return label;
}


"Henry Castro (via github)"

unread,
Mar 5, 2026, 9:58:43 AMMar 5
to collaboraon...@googlegroups.com
browser/src/control/Control.NotebookbarWriter.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

New commits:
commit 96ae87905fdfd0b93b2b7d4175c29548d0ee1050
Author: Henry Castro <hca...@collabora.com>
AuthorDate: Thu Mar 5 09:29:33 2026 -0400
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Thu Mar 5 14:57:39 2026 +0000

browser: a11y: rename shortcut for running macros

Change-Id: I7ca58e343a807b9d30e189cd8d2bc436b524dce2
Signed-off-by: Henry Castro <hca...@collabora.com>

diff --git a/browser/src/control/Control.NotebookbarWriter.js b/browser/src/control/Control.NotebookbarWriter.js
index 602df67e01..3eb8c2b904 100644
--- a/browser/src/control/Control.NotebookbarWriter.js
+++ b/browser/src/control/Control.NotebookbarWriter.js
@@ -305,7 +305,7 @@ window.L.Control.NotebookbarWriter = window.L.Control.Notebookbar.extend({
'type': 'bigtoolitem',
'text': _UNO('.uno:RunMacro', 'text'),
'command': '.uno:RunMacro',
- 'accessibility': { focusBack: true, combination: 'R' }
+ 'accessibility': { focusBack: true, combination: 'Z' }
}
]
});

"Mike Kaganski (via github)"

unread,
Mar 6, 2026, 10:18:20 AMMar 6
to collaboraon...@googlegroups.com
browser/src/map/handler/Map.WOPI.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)

New commits:
commit 8a5606efc664933b1cc4c94dd83ded19a561c8da
Author: Mike Kaganski <mike.k...@collabora.com>
AuthorDate: Fri Mar 6 11:55:37 2026 +0500
Commit: Miklos Vajna <vmi...@collabora.com>
CommitDate: Fri Mar 6 16:17:49 2026 +0100

Make Action_ResolveComment handler only try to resolve Writer comments

Only Writer has this functionality, so don't send messages to modules
that can't handle them. Thanks vmiklos for noting that in
https://github.com/CollaboraOnline/online/pull/14841/changes#r2889939783

Signed-off-by: Mike Kaganski <mike.k...@collabora.com>
Change-Id: I545ec9ccc7062844d2eba99d7a5efe9adf235d5d

diff --git a/browser/src/map/handler/Map.WOPI.js b/browser/src/map/handler/Map.WOPI.js
index 182cad6f8d..2a41517ca5 100644
--- a/browser/src/map/handler/Map.WOPI.js
+++ b/browser/src/map/handler/Map.WOPI.js
@@ -788,7 +788,8 @@ window.L.Map.WOPI = window.L.Handler.extend({
this._map.mention.openMentionPopup(list);
}
else if (msg.MessageId === 'Action_ResolveComment') {
- if (msg.Values) {
+ // Currently only Writer has "Resolve Comment" feature.
+ if (msg.Values && this._map._docLayer._docType === 'text') {
const commentSection = app.sectionContainer.getSectionWithName(app.CSections.CommentList.name);
if (commentSection) {
const comment = commentSection.getComment(msg.Values.Id);

"Gökay Şatır (via github)"

unread,
Mar 9, 2026, 7:53:12 AMMar 9
to collaboraon...@googlegroups.com
browser/src/app/TilesMiddleware.ts | 11 +++++++++++
browser/src/app/ViewLayoutMultiPage.ts | 2 ++
browser/src/canvas/sections/TilesSection.ts | 2 --
3 files changed, 13 insertions(+), 2 deletions(-)

New commits:
commit a507ae2dcd7c9a2650c6e7cd50bfb760ef046052
Author: Gökay Şatır <gokay...@collabora.com>
AuthorDate: Wed Mar 4 12:36:01 2026 +0300
Commit: Gökay Şatır <gokay...@gmail.com>
CommitDate: Mon Mar 9 14:52:38 2026 +0300

Issue: Tile refreshing problem upon switching to other browser tabs.

To reproduce:
* Open a text file.
* Switch to another tab in the browser.
* Turn back to the document tab.
* Notice that some tiles are not drawn.
* Scrolling the view may not help. Clickon the document or moving the mouse onto a scroll bar refreshes the view.

Fixes:
Tile refreshing mechanism already pauses drawing while working: Remove "areViewTilesReady" check from "drawForViewLayoutMultiPage".
"getMissingTiles" function uses beginTransaction and endTransaction while checking the availability of the tiles. "checkRequestTiles" also needs to use the same convention, because "checkRequestTiles" also has calls to "rehydrateTile". Or the draw request may not be issued after tiles are ready.
"rehydrateCurrentTiles" is assigned to deferDrawing call. It is expected to run only once. Undo the setting of deferDrawing as soon as rehydrateCurrentTiles function starts. So it guards against unexpected interruptions of draw calls. It doesn't have any effect on current logic.
"endTransactionHandleBitmaps": Ensure that requestReDraw is called at least once. It may not get called in tileReady function.

Signed-off-by: Gökay Şatır <gokay...@collabora.com>
Change-Id: I3bca62745e63aa6ee33d63c126834d3e7b385d04

diff --git a/browser/src/app/TilesMiddleware.ts b/browser/src/app/TilesMiddleware.ts
index 8c20270ff9..9fa06cccb4 100644
--- a/browser/src/app/TilesMiddleware.ts
+++ b/browser/src/app/TilesMiddleware.ts
@@ -490,6 +490,8 @@ class TileManager {
this.pausedForCoherency = false;
}
app.sectionContainer.deferDrawing(null);
+ // Ensure that requestReDraw is called at least once. It may not get called in tileReady function.
+ app.sectionContainer.requestReDraw();
}

if (this.nPendingWorkerTasks === 0)
@@ -940,6 +942,15 @@ class TileManager {
}

private static rehydrateCurrentTiles() {
+ // Clear the deferred callback immediately. This function is a one-shot
+ // callback that drains dehydratedCurrentTiles; keeping it registered
+ // causes every subsequent requestReDraw() to invoke it instead of
+ // scheduling a requestAnimationFrame, which freezes the canvas.
+ // Drawing is paused via pauseDrawing()/resumeDrawing() inside
+ // rehydrateTile() until worker bitmaps arrive, so clearing the
+ // callback here does not cause premature draws.
+ app.sectionContainer.deferDrawing(null);
+
// If the graphics memory of visible tiles was reclaimed, we have tiles that
// have a valid delta cache, but no corresponding bitmap.
this.beginTransaction();
diff --git a/browser/src/app/ViewLayoutMultiPage.ts b/browser/src/app/ViewLayoutMultiPage.ts
index 2434b824d5..d11f846d01 100644
--- a/browser/src/app/ViewLayoutMultiPage.ts
+++ b/browser/src/app/ViewLayoutMultiPage.ts
@@ -236,7 +236,9 @@ class ViewLayoutMultiPage extends ViewLayoutNewBase {
this.sendClientVisibleArea();

this.refreshCurrentCoordList();
+ TileManager.beginTransaction();
TileManager.checkRequestTiles(this.currentCoordList);
+ TileManager.endTransaction(null);

// We most likely scrolled the view. We also need to check ruler position.
if (app.UI.horizontalRuler) app.UI.horizontalRuler.fixOffset();
diff --git a/browser/src/canvas/sections/TilesSection.ts b/browser/src/canvas/sections/TilesSection.ts
index b625101ac4..84ccee1195 100644
--- a/browser/src/canvas/sections/TilesSection.ts
+++ b/browser/src/canvas/sections/TilesSection.ts
@@ -335,8 +335,6 @@ export class TilesSection extends CanvasSectionObject {
}

private drawForViewLayoutMultiPage() {
- if (!app.activeDocument.activeLayout.areViewTilesReady()) return; // Draw after we have all the tiles.
-
const view = app.activeDocument.activeLayout as ViewLayoutMultiPage;

const visibleCoordList: Array<TileCoordData> = view.getCurrentCoordList();

"codewithvk (via github)"

unread,
Mar 9, 2026, 9:57:29 AMMar 9
to collaboraon...@googlegroups.com
browser/src/control/Control.NotebookbarWriter.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)

New commits:
commit e3941a2fc23d9bfd85dc62208455a59797c628be
Author: codewithvk <vivek....@collabora.com>
AuthorDate: Mon Mar 9 16:42:08 2026 +0530
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Mon Mar 9 13:56:47 2026 +0000

fix(a11y): Add accessibility key for Translate button in Review tab

Signed-off-by: codewithvk <vivek....@collabora.com>
Change-Id: Ide32fdcd9f3c3d5ac2a9be6f10eff50540eca17d

diff --git a/browser/src/control/Control.NotebookbarWriter.js b/browser/src/control/Control.NotebookbarWriter.js
index 3eb8c2b904..48288daba0 100644
--- a/browser/src/control/Control.NotebookbarWriter.js
+++ b/browser/src/control/Control.NotebookbarWriter.js
@@ -2188,7 +2188,8 @@ window.L.Control.NotebookbarWriter = window.L.Control.Notebookbar.extend({
'id': 'review-translate',
'type': 'bigtoolitem',
'text': _UNO('.uno:Translate', 'text'),
- 'command': '.uno:Translate'
+ 'command': '.uno:Translate',
+ 'accessibility': { focusBack: false, combination: 'ZT', de: null }
}: {},
{
'type': 'container',

"Caolán McNamara (via github)"

unread,
Mar 10, 2026, 8:28:57 AMMar 10
to collaboraon...@googlegroups.com
browser/src/map/handler/Map.WOPI.js | 1 +
1 file changed, 1 insertion(+)

New commits:
commit 2fdac71ba9912b1fe21e882eebfe3f2d35b8b256
Author: Caolán McNamara <caolan....@collabora.com>
AuthorDate: Tue Mar 3 20:41:40 2026 +0000
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Tue Mar 10 12:28:46 2026 +0000

grab focus on document when Grab_Focus post message

Integrator also send Grab_Focus post message while closing some dialogs to get back focus to document.

Signed-off-by: Caolán McNamara <caolan....@collabora.com>
Change-Id: I59cdd500dc03770ce078db570b99b356dcb524c0

diff --git a/browser/src/map/handler/Map.WOPI.js b/browser/src/map/handler/Map.WOPI.js
index 2a41517ca5..0a7bef9e4b 100644
--- a/browser/src/map/handler/Map.WOPI.js
+++ b/browser/src/map/handler/Map.WOPI.js
@@ -550,6 +550,7 @@ window.L.Map.WOPI = window.L.Handler.extend({

if (msg.MessageId === 'Grab_Focus') {
app.idleHandler._activate();
+ app.map.focus();
return;
}


"Szymon Kłos (via github)"

unread,
Mar 11, 2026, 3:32:05 AMMar 11
to collaboraon...@googlegroups.com
browser/src/control/Control.JSDialogBuilder.js | 2 ++
browser/src/control/Control.Notebookbar.js | 9 ++-------
2 files changed, 4 insertions(+), 7 deletions(-)

New commits:
commit 2c1fd2421bbc95923e748e892cf3781ef32a8bfb
Author: Szymon Kłos <szymo...@collabora.com>
AuthorDate: Tue Mar 10 05:43:24 2026 +0000
Commit: Szymon Kłos <eszk...@gmail.com>
CommitDate: Wed Mar 11 08:31:39 2026 +0100

jsdialog: be sure action_type is present

- this fixes problem with "undefined" action_type when
selecting image in Writer (tabbed mode)
- executeAction takes inner data (component already checked
top level fields to route it into executor)
- add assert so we can notice it when happens again

Signed-off-by: Szymon Kłos <szymo...@collabora.com>
Change-Id: Ifd9e481767071f61f7633a5ec168b4478d88350b

diff --git a/browser/src/control/Control.JSDialogBuilder.js b/browser/src/control/Control.JSDialogBuilder.js
index 782d8366ff..b8f4e9b4f6 100644
--- a/browser/src/control/Control.JSDialogBuilder.js
+++ b/browser/src/control/Control.JSDialogBuilder.js
@@ -2314,6 +2314,8 @@ window.L.Control.JSDialogBuilder = window.L.Control.extend({
return;
}

+ console.assert(data.action_type);
+
switch (data.action_type) {
case 'grab_focus':
if (typeof control.onFocus === 'function')
diff --git a/browser/src/control/Control.Notebookbar.js b/browser/src/control/Control.Notebookbar.js
index 3846f2e4cb..16c07316a8 100644
--- a/browser/src/control/Control.Notebookbar.js
+++ b/browser/src/control/Control.Notebookbar.js
@@ -440,13 +440,8 @@ window.L.Control.Notebookbar = window.L.Control.extend({
if (!id) return;

this.builder.executeAction(this.container, {
- id: this.builder.windowId,
- action: 'action',
- jsontype: 'notebookbar',
- data: {
- control_id: id,
- action_type: show ? 'show' : 'hide',
- }
+ control_id: id,
+ action_type: show ? 'show' : 'hide',
});

JSDialog.RefreshScrollables();

"Miklos Vajna (via github)"

unread,
Mar 11, 2026, 9:29:30 AMMar 11
to collaboraon...@googlegroups.com
browser/src/app/ViewLayoutMultiPage.ts | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)

New commits:
commit c9caa416e89b4c4686edc60f7f85ea53677f3019
Author: Miklos Vajna <vmi...@collabora.com>
AuthorDate: Wed Mar 11 09:26:54 2026 +0100
Commit: Miklos Vajna <vmi...@collabora.com>
CommitDate: Wed Mar 11 14:29:11 2026 +0100

browser, multi-page layout: annotate overriding functions with 'override'

Similar to commit 207880a8965e169966cdc206c54e0183e85ad65a (browser,
document compare: annotate overriding functions with 'override',
2026-03-04), to help readability.

Signed-off-by: Miklos Vajna <vmi...@collabora.com>
Change-Id: Ia9e733e84d917b296094029de3ad29cdc6747e39

diff --git a/browser/src/app/ViewLayoutMultiPage.ts b/browser/src/app/ViewLayoutMultiPage.ts
index d11f846d01..144bb07f68 100644
--- a/browser/src/app/ViewLayoutMultiPage.ts
+++ b/browser/src/app/ViewLayoutMultiPage.ts
@@ -26,7 +26,7 @@ class ViewLayoutMultiPage extends ViewLayoutNewBase {
this.reset();
}

- public adjustViewZoomLevel() {
+ public override adjustViewZoomLevel() {
Util.ensureValue(app.activeDocument);

const min = 0.1;
@@ -244,7 +244,7 @@ class ViewLayoutMultiPage extends ViewLayoutNewBase {
if (app.UI.horizontalRuler) app.UI.horizontalRuler.fixOffset();
}

- public documentToViewX(point: cool.SimplePoint): number {
+ public override documentToViewX(point: cool.SimplePoint): number {
const index = this.getClosestRectangleIndex(point);
return (
this.viewRectangles[index].pX1 +
@@ -254,7 +254,7 @@ class ViewLayoutMultiPage extends ViewLayoutNewBase {
);
}

- public documentToViewY(point: cool.SimplePoint): number {
+ public override documentToViewY(point: cool.SimplePoint): number {
const index = this.getClosestRectangleIndex(point);
return (
this.viewRectangles[index].pY1 +
@@ -264,7 +264,9 @@ class ViewLayoutMultiPage extends ViewLayoutNewBase {
);
}

- public canvasToDocumentPoint(point: cool.SimplePoint): cool.SimplePoint {
+ public override canvasToDocumentPoint(
+ point: cool.SimplePoint,
+ ): cool.SimplePoint {
point.pX += this.scrollProperties.viewX;
point.pY += this.scrollProperties.viewY;

@@ -282,7 +284,7 @@ class ViewLayoutMultiPage extends ViewLayoutNewBase {
return result;
}

- public scroll(pX: number, pY: number): boolean {
+ public override scroll(pX: number, pY: number): boolean {
const scrolled = super.scroll(pX, pY);

if (scrolled) {
@@ -293,7 +295,7 @@ class ViewLayoutMultiPage extends ViewLayoutNewBase {
return scrolled;
}

- public scrollTo(pX: number, pY: number): void {
+ public override scrollTo(pX: number, pY: number): void {
const point = cool.SimplePoint.fromCorePixels([pX, pY]);
if (!this.viewedRectangle.containsPoint(point.toArray())) {
const index = this.getClosestRectangleIndex(point);

"Henry Castro (via github)"

unread,
Mar 11, 2026, 1:21:10 PMMar 11
to collaboraon...@googlegroups.com
browser/src/control/Control.Menubar.ts | 4 ++--
browser/src/unocommands.js | 1 +
2 files changed, 3 insertions(+), 2 deletions(-)

New commits:
commit c6576de2a02f4bec76d00d33adf74388bae595e6
Author: Henry Castro <hca...@collabora.com>
AuthorDate: Wed Mar 11 09:40:33 2026 -0400
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Wed Mar 11 17:20:23 2026 +0000

browser: a11y: fix menubar accessibility check command

Change-Id: I1b05ffcead4e7e5d62eef3e6270f144af11c41dd
Signed-off-by: Henry Castro <hca...@collabora.com>

diff --git a/browser/src/control/Control.Menubar.ts b/browser/src/control/Control.Menubar.ts
index 8288e760ff..a45a63d824 100644
--- a/browser/src/control/Control.Menubar.ts
+++ b/browser/src/control/Control.Menubar.ts
@@ -403,7 +403,7 @@ class Menubar extends window.L.Control {
{uno: '.uno:WordCountDialog'},
window.enableAccessibility ?
{name: _('Screen Reading'), id: 'togglea11ystate', type: 'action'} : {},
- {uno: '.uno:AccessibilityCheck'},
+ {uno: '.uno:SidebarDeck.A11yCheckDeck'},
{type: 'separator'},
{name: _UNO('.uno:AutoFormatMenu', 'text'), type: 'menu', menu: [
{uno: '.uno:OnlineAutoFormat'}]},
@@ -1864,7 +1864,7 @@ class Menubar extends window.L.Control {
if (!$(menu).hasClass('has-submenu') && ($mainMenuState[0] as HTMLInputElement).checked) {
$mainMenuState[0].click();
}
-
+
if (menu?.parentElement?.id === 'menu-file' && window.mode.isCODesktop() && app.map.backstageView)
app.map.backstageView.toggle();
}
diff --git a/browser/src/unocommands.js b/browser/src/unocommands.js
index 2cdacb665c..87e29b61cc 100644
--- a/browser/src/unocommands.js
+++ b/browser/src/unocommands.js
@@ -567,6 +567,7 @@ var unoCommandsArray = {
'SetObjectToForeground':{global:{menu:_('To Foreground'),},},
'SetOptimalColumnWidth':{presentation:{menu:_('Optimal Column Width'),},spreadsheet:{menu:_('~Optimal Width...'),},text:{menu:_('Optimal Column Width'),},},
'SetOptimalRowHeight':{presentation:{menu:_('Optimal Row Height'),},spreadsheet:{menu:_('~Optimal Height...'),},text:{menu:_('Optimal Row Height'),},},
+ 'SidebarDeck.A11yCheckDeck':{text:{menu:_('~Accessibility Check...'),},},
'Shadowed':{global:{context:_('Toggle Shadow'),menu:_('Shadow'),},},
'ShapesMenu':{global:{menu:_('~Shape'),},},
'SheetMenu':{spreadsheet:{menu:_('~Sheet'),},},

"Henry Castro (via github)"

unread,
Mar 12, 2026, 7:53:47 AMMar 12
to collaboraon...@googlegroups.com
browser/src/control/jsdialog/Widget.TreeView.ts | 38 +++++++++++++++++++++---
1 file changed, 34 insertions(+), 4 deletions(-)

New commits:
commit 41a4512a6dcefff32a1ce51cbd60c7437b627855
Author: Henry Castro <hca...@collabora.com>
AuthorDate: Wed Mar 11 08:12:23 2026 -0400
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Thu Mar 12 11:53:40 2026 +0000

browser: a11y: select row when receiving focus

The TreeView row only highlights the border when using
the arrow keys to navigate, but in some dialogs
(such as the Bookmark dialog), selecting a row enables
or disables operation buttons like Rename/Delete/Goto.

Change-Id: I3a285fe98daa6224e6d7d635bf4593b6ad089587
Signed-off-by: Henry Castro <hca...@collabora.com>

diff --git a/browser/src/control/jsdialog/Widget.TreeView.ts b/browser/src/control/jsdialog/Widget.TreeView.ts
index bb42c05940..cd242fb39b 100644
--- a/browser/src/control/jsdialog/Widget.TreeView.ts
+++ b/browser/src/control/jsdialog/Widget.TreeView.ts
@@ -368,6 +368,7 @@ class TreeViewControl {
);
this._rows.set(String(entry.row), tr);
tr.setAttribute('level', String(level));
+ (tr as any)._row = entry.row;
const rowRole =
this._containerRole === 'tree'
? 'treeitem'
@@ -1356,6 +1357,8 @@ class TreeViewControl {
listElements: Array<HTMLElement>,
fromIndex: number,
toIndex: number,
+ builder: JSBuilder,
+ data: TreeWidgetJSON,
) {
var nextElement = listElements.at(toIndex);
nextElement.tabIndex = 0;
@@ -1381,6 +1384,14 @@ class TreeViewControl {
) as Array<HTMLElement>;
if (oldInput && oldInput.length) oldInput.at(0).tabIndex = -1;
}
+
+ (builder as any).callback(
+ 'treeview',
+ 'select',
+ data,
+ (nextElement as any)._row,
+ builder,
+ );
}

getCurrentEntry(listElements: Array<HTMLElement>) {
@@ -1429,7 +1440,8 @@ class TreeViewControl {
var currIndex = this.getCurrentEntry(listElements);

if (event.key === 'ArrowDown') {
- if (currIndex < 0) this.changeFocusedRow(listElements, currIndex, 0);
+ if (currIndex < 0)
+ this.changeFocusedRow(listElements, currIndex, 0, builder, data);
else {
var nextIndex = currIndex + 1;
while (
@@ -1438,18 +1450,36 @@ class TreeViewControl {
)
nextIndex++;
if (nextIndex < treeLength)
- this.changeFocusedRow(listElements, currIndex, nextIndex);
+ this.changeFocusedRow(
+ listElements,
+ currIndex,
+ nextIndex,
+ builder,
+ data,
+ );
}
preventDef = true;
} else if (event.key === 'ArrowUp') {
if (currIndex < 0)
- this.changeFocusedRow(listElements, currIndex, treeLength - 1);
+ this.changeFocusedRow(
+ listElements,
+ currIndex,
+ treeLength - 1,
+ builder,
+ data,
+ );
else {
var nextIndex = currIndex - 1;
while (nextIndex >= 0 && listElements[nextIndex].clientHeight <= 0)
nextIndex--;
if (nextIndex >= 0)
- this.changeFocusedRow(listElements, currIndex, nextIndex);
+ this.changeFocusedRow(
+ listElements,
+ currIndex,
+ nextIndex,
+ builder,
+ data,
+ );
}

preventDef = true;

"Pranam Lashkari (via github)"

unread,
Mar 12, 2026, 8:31:30 AMMar 12
to collaboraon...@googlegroups.com
browser/src/control/Control.PartsPreview.js | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)

New commits:
commit 26847651d07f9b627ff22610eb16781e043a6e9c
Author: Pranam Lashkari <lpr...@collabora.com>
AuthorDate: Thu Mar 12 17:13:30 2026 +0530
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Thu Mar 12 12:31:02 2026 +0000

slide copy: unify paste button in slide deck

problem:
paste entry in context menu clicked between previews,
and on previews both pasted differently

paste when clicked between slide preview was only
visible when slide was copied in the same doc in same tab
and failed to consider when the slides were copied from
different browser/tab/doc

now all the paste button uses the uno paste command

Signed-off-by: Pranam Lashkari <lpr...@collabora.com>
Change-Id: I0038ade93675da1742e322dc5df1f9601469ef6b

diff --git a/browser/src/control/Control.PartsPreview.js b/browser/src/control/Control.PartsPreview.js
index 1aa20eb375..ba5bee11e3 100644
--- a/browser/src/control/Control.PartsPreview.js
+++ b/browser/src/control/Control.PartsPreview.js
@@ -268,16 +268,25 @@ window.L.Control.PartsPreview = window.L.Control.extend({
className: 'cool-font',
items: {
paste: {
- name: app.IconUtil.createMenuItemLink(_('Paste Slide'), 'Paste'),
+ name: app.IconUtil.createMenuItemLink(_('Paste'), 'Paste'),
isHtmlName: true,
callback: function(key, options) {
if (!nPos)
nPos = that._findClickedPart(options.$trigger[0]);
- that._setPart(that.copiedSlide);
- that._map.duplicatePage(nPos);
+ if (that.copiedSlide) {
+ // Same-tab paste: use duplicate allows insertion at a position
+ that._setPart(that.copiedSlide);
+ that._map.duplicatePage(nPos);
+ } else {
+ // Cross-tab/browser paste: use system clipboard
+ that._map.setPart(nPos - 1); // new slide is inserted after set slide
+ that._map._clip.filterExecCopyPaste('.uno:Paste');
+ }
},
visible: function() {
- return that.copiedSlide;
+ // Show paste if we have a local copied slide OR
+ // the system clipboard API is available (may have content from another tab)
+ return that.copiedSlide || window.L.Browser.clipboardApiAvailable;
}
},
newslide: {

"Sahil Gautam (via github)"

unread,
Mar 12, 2026, 9:56:10 AMMar 12
to collaboraon...@googlegroups.com
browser/src/layer/tile/CanvasTileLayer.js | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)

New commits:
commit 793f5ba22b74d1a6f89f6d6c345eedd4fe7c8663
Author: Sahil Gautam <sahil....@collabora.com>
AuthorDate: Thu Feb 26 20:15:55 2026 +0530
Commit: pedropintosilva <65948705+ped...@users.noreply.github.com>
CommitDate: Thu Mar 12 14:55:51 2026 +0100

zoom: set an upper limit of 170% on dynamic zoom

After a point, the more we zoom into the document, the less content is
visible, therefore it makes sense to have an upper limit.

Signed-off-by: Sahil Gautam <sahil....@collabora.com>
Change-Id: I08856c29ec285bd8bacaa7b8bef92f2f9e0a67bc

diff --git a/browser/src/layer/tile/CanvasTileLayer.js b/browser/src/layer/tile/CanvasTileLayer.js
index 0b17071a12..c824d9f6a2 100644
--- a/browser/src/layer/tile/CanvasTileLayer.js
+++ b/browser/src/layer/tile/CanvasTileLayer.js
@@ -3175,8 +3175,10 @@ window.L.CanvasTileLayer = window.L.Layer.extend({
return;
}

- if (this.isImpress() && !maxZoom)
- maxZoom = 10;
+ if (!maxZoom) {
+ if (this.isImpress()) maxZoom = 10;
+ else if (this.isWriter()) maxZoom = 13;
+ }

if (this._invalidateZoomFirstFit) {
recalcFirstFit = true;

"Dennis Francis (via github)"

unread,
Mar 12, 2026, 12:22:24 PMMar 12
to collaboraon...@googlegroups.com
browser/src/app/A11yValidator.ts | 4
browser/src/app/Debug.ts | 4
browser/src/app/TilesMiddleware.ts | 3
browser/src/app/iface/Clipboard.Interface.ts | 2
browser/src/app/iface/DocLayer.Interface.ts | 77 ++++++++
browser/src/app/iface/Map.Interface.ts | 108 +++++++++++-
browser/src/canvas/sections/ShapeHandleScalingSubSection.ts | 2
browser/src/canvas/sections/SplitterLinesSection.ts | 6
browser/src/control/Control.AboutDialog.ts | 2
browser/src/control/Control.ESignatureDialog.ts | 1
browser/src/control/Control.Menubar.ts | 7
browser/src/control/jsdialog/Component.Base.ts | 4
browser/src/global.d.ts | 2
browser/src/layer/tile/SheetGeometry.ts | 4
browser/src/slideshow/SlideShowPresenter.ts | 2
browser/src/slideshow/engine/MetaPresentation.ts | 4
16 files changed, 213 insertions(+), 19 deletions(-)

New commits:
commit d421f308bf6ed05165c3c3edb4fcb81edf068730
Author: Dennis Francis <dennis....@collabora.com>
AuthorDate: Thu Mar 5 22:16:13 2026 +0530
Commit: Dennis Francis <dennisfr...@gmail.com>
CommitDate: Thu Mar 12 21:51:52 2026 +0530

browser: devX: type app.map and fix all errors

... as a stopgap solution till converting L.Map and L.*Layer.

First use case is: all typescript code using map via app.map directly or
indirectly, get exact types of the methods/properties of map and some of
its deeper internal structures. So if someone incorrectly passes some
wrong type data to its methods will be caught at compile time. Secondly
in IDEs using LSP will show correct auto-complete of map's public
interior as the developer types 'app.map.' in one of the existing
typescript code.

The core change of this patch is just in global.d.ts. The other changes
are needed to satisfy fully typed requirements of map properties/methods
we now use in typescript code

Signed-off-by: Dennis Francis <dennis....@collabora.com>
Change-Id: I79c55de4e0cf5a87f011d007ebcf2f770fdc1ef4

diff --git a/browser/src/app/A11yValidator.ts b/browser/src/app/A11yValidator.ts
index f371e289d8..07c11d5bb8 100644
--- a/browser/src/app/A11yValidator.ts
+++ b/browser/src/app/A11yValidator.ts
@@ -348,7 +348,9 @@ class A11yValidator {
return;
}

- const errorCount = this.validateContainer(currentSidebar.container);
+ const container = currentSidebar.getContainer();
+ Util.ensureValue(container);
+ const errorCount = this.validateContainer(container);

if (errorCount === 0) {
console.error('A11yValidator: sidebar passed all checks');
diff --git a/browser/src/app/Debug.ts b/browser/src/app/Debug.ts
index 322993554e..8054e5cb87 100644
--- a/browser/src/app/Debug.ts
+++ b/browser/src/app/Debug.ts
@@ -61,14 +61,14 @@ class DebugManager {

private tileOverlaysOn: boolean;

- private tileInvalidationsOn: boolean;
+ public tileInvalidationsOn: boolean;
private _tileInvalidationMessages: Map<number, string>;
private _tileInvalidationId: number;
private _tileInvalidationKeypressQueue: number[];
private _tileInvalidationKeypressTimes: DebugTimeArray;
private _tileInvalidationTimeoutId: TimeoutHdl;

- private tileDataOn: boolean;
+ public tileDataOn: boolean;
private _tileDataTotalMessages: number;
private _tileDataTotalLoads: number;
private _tileDataTotalUpdates: number;
diff --git a/browser/src/app/TilesMiddleware.ts b/browser/src/app/TilesMiddleware.ts
index 9fa06cccb4..00f8799f16 100644
--- a/browser/src/app/TilesMiddleware.ts
+++ b/browser/src/app/TilesMiddleware.ts
@@ -2127,7 +2127,8 @@ class TileManager {
tile.updateCount++;
this.nullDeltaUpdate++;
if (app.map._docLayer._emptyDeltaDiv) {
- app.map._docLayer._emptyDeltaDiv.innerText = this.nullDeltaUpdate;
+ app.map._docLayer._emptyDeltaDiv.innerText =
+ this.nullDeltaUpdate.toString();
}
if (app.map._debug.tileDataOn) {
app.map._debug.tileDataAddUpdate();
diff --git a/browser/src/app/iface/Clipboard.Interface.ts b/browser/src/app/iface/Clipboard.Interface.ts
index 0d107c1074..d7af521979 100644
--- a/browser/src/app/iface/Clipboard.Interface.ts
+++ b/browser/src/app/iface/Clipboard.Interface.ts
@@ -1,3 +1,5 @@
interface ClipboardInterface {
setKey(key: string): void;
+ onComplexSelection(text?: string): void;
+ [key: string]: any;
}
diff --git a/browser/src/app/iface/DocLayer.Interface.ts b/browser/src/app/iface/DocLayer.Interface.ts
index 1ee4e6b87c..aafcda3e97 100644
--- a/browser/src/app/iface/DocLayer.Interface.ts
+++ b/browser/src/app/iface/DocLayer.Interface.ts
@@ -17,8 +17,16 @@ interface PainterInterface {
_removeSplitsSection(): void;
_addDebugOverlaySection(): void;
_removeDebugOverlaySection(): void;
+ _paintContext(): {
+ viewBounds: cool.Bounds;
+ paneBoundsList: cool.Bounds[];
+ paneBoundsActive: boolean;
+ splitPos: cool.Point;
+ };
}

+type MapUpdaterType = (newMapCenterLatLng: LatLngLike) => void;
+
interface DocLayerInterface {
_toolbarCommandValues: any;

@@ -32,7 +40,7 @@ interface DocLayerInterface {
isCalcRTL(): boolean;

_pixelsToTwips(cssPix: cool.PointLike): cool.PointLike;
- _latLngToTwips(latlng: { lat: number; lng: number }): cool.Point;
+ _latLngToTwips(latlng: LatLngLike): cool.Point;

_postMouseEvent(
typ: string,
@@ -58,4 +66,71 @@ interface DocLayerInterface {
options: {
tileWidthTwips: number;
};
+
+ sheetGeometry?: cool.SheetGeometry;
+ _cellSelectionArea?: cool.SimpleRectangle;
+ scrollToPos(pos: { x: number; y: number } | LatLngLike): void;
+
+ _selectedPart: number;
+ _oleCSelections: CSelections;
+ _shapeGridOffset: cool.SimplePoint;
+ getFiledBasedViewVerticalOffset(): number;
+ focus(acceptInput?: boolean): void;
+ _updateCursorAndOverlay(): void;
+ _twipsToPixels(twips: cool.Point): cool.Point;
+ _allowViewJump(): boolean;
+ _selectionContentRequest?: TimeoutHdl;
+ recalculateZoomOnResize(): void;
+ _splitPanesContext?: cool.SplitPanesContext;
+ getSplitPanesContext(): cool.SplitPanesContext | undefined;
+ _moveInProgress: boolean;
+ _moveTileRequests: string[];
+ _debug: DebugManager;
+ _canonicalIdInitialized: boolean;
+ _gotFirstCellCursor?: boolean;
+ _sendClientZoom(forceUpdate?: boolean): void;
+ _emptyDeltaDiv?: HTMLDivElement;
+ _partHeightTwips: number;
+ _isZooming: boolean;
+ _spaceBetweenParts: number;
+ _partWidthTwips: number;
+ _parts: number;
+ _fitWidthZoom(
+ e?: { oldSize: number; newsize: number },
+ maxZoom?: number,
+ recalcFirstFit?: boolean,
+ ): void;
+ _corePixelsToTwips(corePixels: { x: number; y: number }): cool.Point;
+ // Is this still in user?
+ _ySplitter: any;
+ _xSplitter: any;
+ _cursorMarker?: Cursor;
+ _syncTileContainerSize(force?: boolean): boolean;
+ _postSelectTextEvent(type: string, x: number, y: number): void;
+ _viewId: number;
+ _openCommentWizard(annotation?: cool.Comment): void;
+ _parseCellRange(cellRange: string): cool.Bounds;
+ _cellRangeToTwipRect(cellRange: cool.Bounds): cool.Bounds;
+ preZoomAnimation(pinchStartCenter: LatLngLike): void;
+ zoomStep(zoom: number, newCenter: LatLngLike): void;
+ zoomStepEnd(
+ zoom: number,
+ newCenter: LatLngLike,
+ mapUpdater: MapUpdaterType,
+ runAtFinish: () => void,
+ noGap?: boolean,
+ ): void;
+ postZoomAnimation(): void;
+ _checkSelectedPart(): void;
+ requestNewFiledBasedViewTiles(): void;
+ _docPixelSize: cool.PointLike;
+ _closeMobileWizard(): void;
+ _openMobileWizard(data: WidgetJSON): void;
+
+ // TODO: window.L.Control.PartsPreview.
+ _preview?: any;
+ getCommentWizardStructure(
+ menuStructure?: WidgetJSON,
+ onlyThread?: any,
+ ): WidgetJSON;
}
diff --git a/browser/src/app/iface/Map.Interface.ts b/browser/src/app/iface/Map.Interface.ts
index 708ecb7d3d..27ab208780 100644
--- a/browser/src/app/iface/Map.Interface.ts
+++ b/browser/src/app/iface/Map.Interface.ts
@@ -16,6 +16,11 @@ interface CRSInterface {
scale(zoom: number): number;
}

+interface LatLngLike {
+ lat: number;
+ lng: number;
+}
+
interface MapInterface extends Evented {
_docLayer: DocLayerInterface;
uiManager: UIManager;
@@ -31,11 +36,11 @@ interface MapInterface extends Evented {
): void;

stateChangeHandler: {
- getItemValue(unoCmd: string): string;
+ getItemValue(unoCmd: string): any;
setItemValue(unoCmd: string, value: any): void;
};

- sendUnoCommand(unoCmd: string): void;
+ sendUnoCommand(unoCmd: string, json?: any, force?: boolean): void;

getDocType(): 'text' | 'presentation' | 'spreadsheet' | 'drawing';
isText(): boolean;
@@ -43,7 +48,7 @@ interface MapInterface extends Evented {

getDocSize(): cool.Point;
getSize(): cool.Point;
- getCenter(): { lat: number; lng: number };
+ getCenter(): LatLngLike;
_getCurrentFontName(): string;

_docLoadedOnce: boolean;
@@ -77,6 +82,9 @@ interface MapInterface extends Evented {
BaseFileName: string;
HideExportOption: boolean;
UserCanWrite: boolean;
+ HideChangeTrackingControls: boolean;
+ EnableRemoteLinkPicker: boolean;
+ HideSaveOption: boolean;
};

loadDocument(socket?: SockInterface): void;
@@ -104,7 +112,7 @@ interface MapInterface extends Evented {
isLockedUser(): boolean;
isRestrictedUser(): boolean;

- focus(): void;
+ focus(acceptInput?: boolean): void;
editorHasFocus(): boolean;

_fireInitComplete(condition: string): void;
@@ -124,4 +132,96 @@ interface MapInterface extends Evented {
userList: UserList;
sidebar: Sidebar;
getViewColor(viewId: number): number;
+
+ // TODO fix types:
+ jsdialog: any;
+ zotero: any;
+
+ _cacheSVG: string[];
+ calcInputBarHasFocus(): boolean;
+ lockAccessibilityOn(): void;
+ getPixelBounds(center?: LatLngLike, zoom?: number): cool.Bounds;
+ getPixelBoundsCore(center?: LatLngLike, zoom?: number): cool.Bounds;
+ _partsDirection: number;
+ getZoomScale(toZoom: number, fomZoom?: number): number;
+ _docLoaded: boolean;
+ contextToolbar?: ContextToolbar;
+ getSplitPanesContext(): cool.SplitPanesContext | undefined;
+ getScaleZoom(scale: number, fromZoom?: number): number;
+ scrollingIsHandled: boolean;
+ showComments(on?: boolean): void;
+ showResolvedComments(on?: boolean): void;
+ navigator: NavigatorPanel;
+ setPart(
+ part: number | string,
+ external?: boolean,
+ calledFromSetPartHandler?: boolean,
+ ): void;
+ mouseEventToLatLng(e: any): LatLngLike;
+ _limitZoom(zoom: number): number;
+ setView(
+ center: [number, number],
+ zoom?: number,
+ reset?: boolean,
+ ): MapInterface;
+ isViewReadOnly(viewid: number): boolean;
+ scrollHandler: typeof window.L.Map.Scroll;
+ context?: { appId: string; context: string };
+ eSignature?: cool.ESignature;
+
+ // TODO: Fix type.
+ formulabar: any;
+ backstageView: any;
+ topToolbar: any;
+ statusBar: any;
+
+ mobileSearchBar: MobileSearchBar;
+ _disableDefaultAction: Record<string, boolean>;
+ save(
+ dontTerminateEdit: boolean,
+ dontSaveIfUnmodified: boolean,
+ extendedData?: string,
+ ): void;
+ _everModified: boolean;
+ print(options?: string): void;
+ openRevisionHistory(): void;
+ openShare(): void;
+ showHelp(id: string): void;
+ openSaveAs(format?: string): void;
+ downloadAs(
+ name: string,
+ format?: string,
+ options?: string | null,
+ id?: string,
+ ): void;
+ insertComment(): void;
+ zoomIn(delta: number, options?: any, animate?: boolean): MapInterface;
+ zoomOut(delta: number, options?: any, animate?: boolean): MapInterface;
+ cancelSearch(): void;
+ goToPage(page: string): void;
+ serverAuditDialog?: ServerAuditDialog;
+ _lockAccessibilityOn: boolean;
+ setAccessibilityState(enable: boolean): void;
+
+ // TODO: L.Map.Keyboard
+ keyboard: any;
+
+ onFormulaBarBlur(): void;
+ onFormulaBarFocus(): void;
+ formulabarBlur(): void;
+ formulabarFocus(): void;
+ formulabarSetDirty(): void;
+ _functionWizardData: WidgetJSON;
+
+ insertPage: ((nPos?: number) => void) & { scrollToEnd: boolean };
+ deletePage(nPos?: number): void;
+ duplicatePage(pos?: number): void;
+ slideShowPresenter?: SlideShowPresenter;
+ hideSlide(): void;
+ showSlide(): void;
+ sidebarFromNotebookbar: SidebarFromNotebookbarPanel;
+ mobileTopBar?: MobileTopBar;
+
+ // TODO: window.L.control.lokDialog
+ dialog: any;
}
diff --git a/browser/src/canvas/sections/ShapeHandleScalingSubSection.ts b/browser/src/canvas/sections/ShapeHandleScalingSubSection.ts
index aaf99a7aa0..bc9e166910 100644
--- a/browser/src/canvas/sections/ShapeHandleScalingSubSection.ts
+++ b/browser/src/canvas/sections/ShapeHandleScalingSubSection.ts
@@ -180,7 +180,7 @@ class ShapeHandleScalingSubSection extends CanvasSectionObject {
let keep = e.ctrlKey && e.shiftKey;

// For images, the keepRatio shortcut works the opposite way.
- if (app.map.context.context === 'Graphic')
+ if (app.map.context && app.map.context.context === 'Graphic')
keep = !keep;

return keep;
diff --git a/browser/src/canvas/sections/SplitterLinesSection.ts b/browser/src/canvas/sections/SplitterLinesSection.ts
index 1d8ac4146e..cd2ccd3fa9 100644
--- a/browser/src/canvas/sections/SplitterLinesSection.ts
+++ b/browser/src/canvas/sections/SplitterLinesSection.ts
@@ -61,9 +61,11 @@ class SplitterLinesSection extends CanvasSectionObject {
private setBoxGradient(splitPos: any, isVertical: boolean) {
let selectionBackgroundGradient = null;

+ Util.ensureValue(app.map._docLayer.sheetGeometry);
// last row geometry data will be a good for setting deafult raw height
- const spanlist =
- app.map._docLayer.sheetGeometry.getRowsGeometry()._visibleSizes._spanlist;
+ const spanlist = app.map._docLayer.sheetGeometry
+ .getRowsGeometry()
+ .getVisibleSizes()._spanlist;
const rowData = spanlist[spanlist.length - 1];

// Create a linear gradient based on the extracted color stops
diff --git a/browser/src/control/Control.AboutDialog.ts b/browser/src/control/Control.AboutDialog.ts
index 28b9a06ada..7c945b7bdb 100644
--- a/browser/src/control/Control.AboutDialog.ts
+++ b/browser/src/control/Control.AboutDialog.ts
@@ -215,7 +215,7 @@ class AboutDialog {
link.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
app.socket.sendMessage('uno .uno:WidgetTestDialog');
- app.map.uiManager.closeModal('modal-dialog-about-dialog-box', false);
+ app.map.uiManager.closeModal('modal-dialog-about-dialog-box');
});

elements.jsDialog.appendChild(label);
diff --git a/browser/src/control/Control.ESignatureDialog.ts b/browser/src/control/Control.ESignatureDialog.ts
index af22b7a372..b4c95329aa 100644
--- a/browser/src/control/Control.ESignatureDialog.ts
+++ b/browser/src/control/Control.ESignatureDialog.ts
@@ -221,6 +221,7 @@ namespace cool {
const countryCode =
this.availableCountries[countries.selectedIndex].code;
this.close();
+ Util.ensureValue(app.map.eSignature);
app.map.eSignature.handleSelectedProvider(countryCode, providerId);
} else if (eventType === 'selected' && object.id === 'countrylb') {
// The selected country changed, update the list of providers
diff --git a/browser/src/control/Control.Menubar.ts b/browser/src/control/Control.Menubar.ts
index a45a63d824..f62c047767 100644
--- a/browser/src/control/Control.Menubar.ts
+++ b/browser/src/control/Control.Menubar.ts
@@ -1951,7 +1951,7 @@ class Menubar extends window.L.Control {
} else {
$(aItem).removeClass(constChecked);
}
- if (this.options.math.includes(unoCommand) && app.map.context.context !== 'Math') {
+ if (this.options.math.includes(unoCommand) && app.map.context && app.map.context.context !== 'Math') {
$(aItem).addClass('disabled');
}
} else if (type === 'action') { // enable all except fullscreen on windows
@@ -2290,7 +2290,10 @@ class Menubar extends window.L.Control {
};
app.map.sendUnoCommand('.uno:InsertSignatureLine', args);
const finishMessage = _('The signature line can now be moved or resized as needed.');
- const finishFunc = () => app.map.eSignature.insert();
+ const finishFunc = () => {
+ Util.ensureValue(app.map.eSignature);
+ app.map.eSignature.insert();
+ };
app.map.uiManager.showSnackbar(finishMessage, _('Finish electronic signing'), finishFunc, -1);
} else {
app.map.sendUnoCommand('.uno:InsertSignatureLine');
diff --git a/browser/src/control/jsdialog/Component.Base.ts b/browser/src/control/jsdialog/Component.Base.ts
index d6a1b7d156..0429bd5cc9 100644
--- a/browser/src/control/jsdialog/Component.Base.ts
+++ b/browser/src/control/jsdialog/Component.Base.ts
@@ -30,6 +30,10 @@ abstract class JSDialogComponent {
this.model = new JSDialogModelState(name);
}

+ public getContainer(): HTMLElement | undefined {
+ return this.container;
+ }
+
/// connects component to the JSDialogMessageRouter
protected registerMessageHandlers() {
this.map.on('jsdialogupdate', this.onJSUpdate, this);
diff --git a/browser/src/global.d.ts b/browser/src/global.d.ts
index 810fec9c89..e220740e56 100644
--- a/browser/src/global.d.ts
+++ b/browser/src/global.d.ts
@@ -196,7 +196,7 @@ interface AppInterface {
colorPalettes: any; // TODO declare according to Widget.ColorPicker.ts
colorNames: any; // TODO declare according to Widget.ColorPicker.ts
console: Console;
- map: any; // TODO should be window.L.Map
+ map: MapInterface; // TODO should be window.L.Map
// file defined in: src/docstate.ts
file: {
editComment: boolean;
diff --git a/browser/src/layer/tile/SheetGeometry.ts b/browser/src/layer/tile/SheetGeometry.ts
index 09b22d6ba8..afd936744f 100644
--- a/browser/src/layer/tile/SheetGeometry.ts
+++ b/browser/src/layer/tile/SheetGeometry.ts
@@ -577,6 +577,10 @@ export class SheetDimension {
this._visibleSizes = undefined;
}

+ public getVisibleSizes(): SpanList {
+ return this._visibleSizes;
+ }
+
public getInvisibleSpanList(): BoolSpanList { return this._invisibleSpanList; }

public update(jsonObject: SheetDimensionCoreData): boolean {
diff --git a/browser/src/slideshow/SlideShowPresenter.ts b/browser/src/slideshow/SlideShowPresenter.ts
index 7a77f81d9c..514d1dd894 100644
--- a/browser/src/slideshow/SlideShowPresenter.ts
+++ b/browser/src/slideshow/SlideShowPresenter.ts
@@ -134,7 +134,7 @@ class SlideShowPresenter {
private _slideControlsTimer: ReturnType<typeof setTimeout> | null = null;
private _slideShowHandler: SlideShowHandler;
private _slideShowNavigator: SlideShowNavigator;
- private _metaPresentation: MetaPresentation;
+ public _metaPresentation: MetaPresentation;
private _startSlide: number;
private _startEffect: number;
private _presentationInfoChanged: boolean = false;
diff --git a/browser/src/slideshow/engine/MetaPresentation.ts b/browser/src/slideshow/engine/MetaPresentation.ts
index 62d423cdaa..34e3dbdc50 100644
--- a/browser/src/slideshow/engine/MetaPresentation.ts
+++ b/browser/src/slideshow/engine/MetaPresentation.ts
@@ -12,8 +12,8 @@
*/

class MetaPresentation {
- private docWidth: number;
- private docHeight: number;
+ public docWidth: number;
+ public docHeight: number;
private _numberOfSlides: number;
private firstSlideHash: string;
private lastSlideHash: string;

"Pranam Lashkari (via github)"

unread,
Mar 13, 2026, 4:17:42 AMMar 13
to collaboraon...@googlegroups.com
browser/src/control/Control.PartsPreview.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

New commits:
commit 2b0d026a61e241e4625406090c4fb82fdba4034c
Author: Pranam Lashkari <lpr...@collabora.com>
AuthorDate: Fri Mar 13 02:58:10 2026 +0530
Commit: Pranam Lashkari <plashk...@gmail.com>
CommitDate: Fri Mar 13 09:17:21 2026 +0100

js condition correction

nPos is integer value, if value is 0 which is valid,
condition would still evaluate to zero

Signed-off-by: Pranam Lashkari <lpr...@collabora.com>
Change-Id: Ib1b5d36377fe0b103c4eb24f86f91335a5517590

diff --git a/browser/src/control/Control.PartsPreview.js b/browser/src/control/Control.PartsPreview.js
index ba5bee11e3..5e76ebdc27 100644
--- a/browser/src/control/Control.PartsPreview.js
+++ b/browser/src/control/Control.PartsPreview.js
@@ -271,7 +271,7 @@ window.L.Control.PartsPreview = window.L.Control.extend({
name: app.IconUtil.createMenuItemLink(_('Paste'), 'Paste'),
isHtmlName: true,
callback: function(key, options) {
- if (!nPos)
+ if (nPos === undefined)
nPos = that._findClickedPart(options.$trigger[0]);
if (that.copiedSlide) {

"Szymon Kłos (via github)"

unread,
Mar 13, 2026, 9:39:02 AMMar 13
to collaboraon...@googlegroups.com
browser/src/canvas/CanvasSectionContainer.ts | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)

New commits:
commit 3ac8dc59d1e85ef05c0ae3b851dbc42a36800bc4
Author: Szymon Kłos <szymo...@collabora.com>
AuthorDate: Fri Mar 13 05:13:24 2026 +0000
Commit: Szymon Kłos <eszk...@gmail.com>
CommitDate: Fri Mar 13 14:38:48 2026 +0100

perf: use unified callback for scroll animation #14984

Fixes #14984

- when we animate we use onAnimate callback of some sections
- example of such section is ScrollSection
- after we did the animation we need to schedule next frame
and so fat it was done directly by calling requestAnimationFrame
- that was causing the duplication of requests, let's use usual
requestReDraw which checks if we already requested a frame
- adjust also a callback so we check at the end if we should schedule
another frame

Signed-off-by: Szymon Kłos <szymo...@collabora.com>
Change-Id: I2843814fc41a918d2e6bcf4ea74dd204e66eefba

diff --git a/browser/src/canvas/CanvasSectionContainer.ts b/browser/src/canvas/CanvasSectionContainer.ts
index a8028b7446..55f59e909c 100644
--- a/browser/src/canvas/CanvasSectionContainer.ts
+++ b/browser/src/canvas/CanvasSectionContainer.ts
@@ -676,6 +676,9 @@ class CanvasSectionContainer {
this.drawSections();
this.flushLayoutingTasks();
this.canvas.style.visibility = 'unset';
+
+ // need to check if we should continue animation
+ this.animate(timestamp);
}

public requestReDraw() {
@@ -2100,7 +2103,7 @@ class CanvasSectionContainer {
if (this.continueAnimating) {
if (section) section.onAnimate(this.frameCount, this.elapsedTime);
this.frameCount++;
- requestAnimationFrame(this.animate.bind(this));
+ this.requestReDraw();
}
else {
if (section) {

"Tomaž Vajngerl (via github)"

unread,
Mar 16, 2026, 1:39:22 AMMar 16
to collaboraon...@googlegroups.com
browser/src/canvas/sections/ShapeHandleScalingSubSection.ts | 3 +++
1 file changed, 3 insertions(+)

New commits:
commit 464afd1e6006995f07865d1b9cc271ce7b475998
Author: Tomaž Vajngerl <tomaz.v...@collabora.co.uk>
AuthorDate: Sat Feb 28 23:37:47 2026 +0900
Commit: Tomaž Vajngerl <qui...@users.noreply.github.com>
CommitDate: Mon Mar 16 14:38:39 2026 +0900

cool#14745 Crop handle dragging fix

Crop and resize shared the same code path, causing crop handles to
incorrectly change shape width/height. Fixed by checking if we are
cropping in doWeKeepRatio.

Signed-off-by: Tomaž Vajngerl <tomaz.v...@collabora.co.uk>
Change-Id: I0d0d5408ec8c0304f03c4cdf527659a063c12fcb

diff --git a/browser/src/canvas/sections/ShapeHandleScalingSubSection.ts b/browser/src/canvas/sections/ShapeHandleScalingSubSection.ts
index bc9e166910..b9a1b9c879 100644
--- a/browser/src/canvas/sections/ShapeHandleScalingSubSection.ts
+++ b/browser/src/canvas/sections/ShapeHandleScalingSubSection.ts
@@ -177,6 +177,9 @@ class ShapeHandleScalingSubSection extends CanvasSectionObject {
}

private doWeKeepRatio(e: MouseEvent) {
+ if (this.sectionProperties.cropModeEnabled)
+ return false;
+

"Gökay Şatır (via github)"

unread,
Mar 16, 2026, 4:06:26 AMMar 16
to collaboraon...@googlegroups.com
browser/src/canvas/sections/CommentListSection.ts | 20 ++++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)

New commits:
commit 7efd6ea6d80c155e777ff59bac1c0a4f8d381a98
Author: Gökay Şatır <gokay...@collabora.com>
AuthorDate: Tue Mar 10 11:47:27 2026 +0300
Commit: Szymon Kłos <eszk...@gmail.com>
CommitDate: Mon Mar 16 09:05:59 2026 +0100

Add a guard for multiple onNewDocumentTopLeft calls in one cycle.

Signed-off-by: Gökay Şatır <gokay...@collabora.com>
Change-Id: Ic02f5eb7d257d026688fb033da18fac8842b3faf

diff --git a/browser/src/canvas/sections/CommentListSection.ts b/browser/src/canvas/sections/CommentListSection.ts
index 284068fe99..59e02eab3c 100644
--- a/browser/src/canvas/sections/CommentListSection.ts
+++ b/browser/src/canvas/sections/CommentListSection.ts
@@ -125,6 +125,11 @@ export class CommentSection extends CanvasSectionObject {
// To associate comment id with its index in commentList array.
private idIndexMap: Map<any, number>;

+ // Dirty flag: set by onNewDocumentTopLeft, consumed by onDraw.
+ // Collapses multiple onNewDocumentTopLeft calls into a single layout
+ // pass and avoids the extra requestReDraw that updateDOM would trigger.
+ private _commentPositionDirty: boolean = false;
+
private annotationMinSize: number;
private annotationMaxSize: number;
escapeListener: (e: KeyboardEvent) => void;
@@ -1363,10 +1368,17 @@ export class CommentSection extends CanvasSectionObject {
this.sectionProperties.selectedComment.hide();
}

- var previousAnimationState = this.disableLayoutAnimation;
- this.disableLayoutAnimation = true;
- this.update(false);
- this.disableLayoutAnimation = previousAnimationState;
+ this._commentPositionDirty = true;
+ }
+
+ public onDraw (frameCount?: number, elapsedTime?: number): void {
+ if (this._commentPositionDirty) {
+ this._commentPositionDirty = false;
+ var previousAnimationState = this.disableLayoutAnimation;
+ this.disableLayoutAnimation = true;
+ this.update(false);
+ this.disableLayoutAnimation = previousAnimationState;
+ }
}

private showHideComments (): void {

"Gökay Şatır (via github)"

unread,
Mar 16, 2026, 4:59:30 AMMar 16
to collaboraon...@googlegroups.com
browser/src/map/handler/Map.KeyboardShortcuts.ts | 2 ++
1 file changed, 2 insertions(+)

New commits:
commit 9fece1be52b9c70e1515d1136a4abdd89be3ed1e
Author: Gökay Şatır <gokay...@collabora.com>
AuthorDate: Mon Mar 16 11:00:48 2026 +0300
Commit: Szymon Kłos <eszk...@gmail.com>
CommitDate: Mon Mar 16 09:59:22 2026 +0100

Disable core side template dialog in online.

Signed-off-by: Gökay Şatır <gokay...@collabora.com>
Change-Id: I7c439be48c8c71b38044858ad8df9994228a4c43

diff --git a/browser/src/map/handler/Map.KeyboardShortcuts.ts b/browser/src/map/handler/Map.KeyboardShortcuts.ts
index 615eaf5ff0..fde095db8f 100644
--- a/browser/src/map/handler/Map.KeyboardShortcuts.ts
+++ b/browser/src/map/handler/Map.KeyboardShortcuts.ts
@@ -317,7 +317,9 @@ keyboardShortcuts.definitions.set('default', new Array<ShortcutDescriptor>(
Disable F5 or assign it something to prevent browser refresh.
Disable multi-sheet selection shortcuts in Calc.
Disable F2 in Writer, formula bar is unsupported, and messes with further input.
+ Disable CTRL+SHIFT+N because core side template dialog is not supported on Online.
*/
+ new ShortcutDescriptor({ eventType: 'keydown', modifier: Mod.CTRL | Mod.SHIFT, key: 'N' }),
new ShortcutDescriptor({ eventType: 'keydown', key: 'F1', dispatchAction: 'showhelp' }),
new ShortcutDescriptor({ eventType: 'keydown', modifier: Mod.ALT, key: 'F1', dispatchAction: 'focustonotebookbar' }),
new ShortcutDescriptor({ eventType: 'keydown', modifier: Mod.CTRL, key: 'f', dispatchAction: 'home-search' }),

"Szymon Kłos (via github)"

unread,
Mar 16, 2026, 10:31:49 AMMar 16
to collaboraon...@googlegroups.com
browser/src/app/TableStylesService.ts | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)

New commits:
commit 8370f7a0193e852406c72ccdf1ac320aa5dbbad3
Author: Szymon Kłos <szymo...@collabora.com>
AuthorDate: Mon Mar 16 10:16:16 2026 +0000
Commit: Caolán McNamara <cao...@gmail.com>
CommitDate: Mon Mar 16 14:30:57 2026 +0000

Table Design: apply None

- None should apply plain style without any coloring

Signed-off-by: Szymon Kłos <szymo...@collabora.com>
Change-Id: Ie38ed640f3d014cb816ca1513f144d572de3ff6a

diff --git a/browser/src/app/TableStylesService.ts b/browser/src/app/TableStylesService.ts
index a61517454b..0cbf6e6c0e 100644
--- a/browser/src/app/TableStylesService.ts
+++ b/browser/src/app/TableStylesService.ts
@@ -220,6 +220,14 @@ class TableStylesService {
};
}

+ public getNoneStyle(): TableStyleEntry {
+ return {
+ Name: 'None',
+ UIName: _('None'),
+ Elements: [],
+ } as TableStyleEntry;
+ }
+
private styleHasElement(
style: TableStyleEntry,
elementType: TableStyleElementType,
@@ -231,7 +239,8 @@ class TableStylesService {
}

public applyStyle(newStyleNumber: number) {
- const tableStyleEntry = this.styles[newStyleNumber];
+ const tableStyleEntry =
+ newStyleNumber === -1 ? this.getNoneStyle() : this.styles[newStyleNumber];
if (!tableStyleEntry) {
app.console.error(
'TableStylesService: not found style with id: ' + newStyleNumber,
@@ -342,7 +351,10 @@ class TableStylesService {
image: 'images/lc_table_none.svg',
width: 50,
height: 50,
- selected: !currentStyle || currentStyle.TableStyleName === '',
+ selected:
+ !currentStyle ||
+ currentStyle.TableStyleName === '' ||
+ currentStyle.TableStyleName === 'None',
} as IconViewEntry);

this.styles.forEach((element) => {

"Andreas-Kainz (via github)"

unread,
Mar 17, 2026, 6:06:16 AMMar 17
to collaboraon...@googlegroups.com
browser/src/control/Control.Menubar.ts | 21 +++
browser/src/control/Control.NotebookbarWriter.js | 129 +++++++++++------------
2 files changed, 87 insertions(+), 63 deletions(-)

New commits:
commit 11680ea3db781c7a0ee61d8f5419bdd80c5cf713
Author: Andreas Kainz <andr...@abwesend.de>
AuthorDate: Mon Mar 2 22:31:33 2026 +0100
Commit: pedropintosilva <65948705+ped...@users.noreply.github.com>
CommitDate: Tue Mar 17 11:05:39 2026 +0100

Sync Help Tab and Menubar

Signed-off-by: Andreas-Kainz <andr...@abwesend.de>
Co-authored-by: Pedro Pinto Silva <pedro...@collabora.com>
Change-Id: Ie12229db4b4e6a8fcb528183883ea489baeccab0

diff --git a/browser/src/control/Control.Menubar.ts b/browser/src/control/Control.Menubar.ts
index f62c047767..3cf6f61dbc 100644
--- a/browser/src/control/Control.Menubar.ts
+++ b/browser/src/control/Control.Menubar.ts
@@ -415,13 +415,17 @@ class Menubar extends window.L.Control {
{name: _UNO('.uno:RunMacro'), id: 'runmacro', uno: '.uno:RunMacro'}
]},
{name: _UNO('.uno:HelpMenu', 'text'), id: 'help', type: 'menu', menu: [
- {name: _('Forum'), id: 'forum', type: 'action'},
{name: _('Online Help'), id: 'online-help', type: 'action', iosapp: false},
+ {type: 'separator', iosapp: false},
{name: _('Keyboard shortcuts'), id: 'keyboard-shortcuts', type: 'action', iosapp: false},
+ {type: 'separator'},
+ {name: _('Forum'), id: 'forum', type: 'action'},
{name: _('Report an issue'), id: 'report-an-issue', type: 'action', iosapp: false},
{name: _('Latest Updates'), id: 'latestupdates', type: 'action', iosapp: false},
{name: _('Send Feedback'), id: 'feedback', type: 'action', mobileapp: false},
+ {type: 'separator'},
{name: _('Server audit'), id: 'serveraudit', type: 'action', mobileapp: false},
+ {type: 'separator'},
{name: _('About'), id: 'about', type: 'action'}]
},
{name: _('Last modification'), id: 'last-mod', type: 'action', tablet: false}
@@ -600,11 +604,16 @@ class Menubar extends window.L.Control {
]},
{name: _UNO('.uno:HelpMenu', 'presentation'), id: 'help', type: 'menu', menu: [
{name: _('Online Help'), id: 'online-help', type: 'action', iosapp: false},
+ {type: 'separator', iosapp: false},
{name: _('Keyboard shortcuts'), id: 'keyboard-shortcuts', type: 'action', iosapp: false},
+ {type: 'separator'},
+ {name: _('Forum'), id: 'forum', type: 'action'},
{name: _('Report an issue'), id: 'report-an-issue', type: 'action', iosapp: false},
{name: _('Latest Updates'), id: 'latestupdates', type: 'action', iosapp: false},
{name: _('Send Feedback'), id: 'feedback', type: 'action', mobileapp: false},
+ {type: 'separator'},
{name: _('Server audit'), id: 'serveraudit', type: 'action', mobileapp: false},
+ {type: 'separator'},
{name: _('About'), id: 'about', type: 'action'}]
},
{name: _('Last modification'), id: 'last-mod', type: 'action', tablet: false}
@@ -739,11 +748,16 @@ class Menubar extends window.L.Control {
]},
{name: _UNO('.uno:HelpMenu', 'presentation'), id: 'help', type: 'menu', menu: [
{name: _('Online Help'), id: 'online-help', type: 'action', iosapp: false},
+ {type: 'separator', iosapp: false},
{name: _('Keyboard shortcuts'), id: 'keyboard-shortcuts', type: 'action', iosapp: false},
+ {type: 'separator'},
+ {name: _('Forum'), id: 'forum', type: 'action'},
{name: _('Report an issue'), id: 'report-an-issue', type: 'action', iosapp: false},
{name: _('Latest Updates'), id: 'latestupdates', type: 'action', iosapp: false},
{name: _('Send Feedback'), id: 'feedback', type: 'action', mobileapp: false},
+ {type: 'separator'},
{name: _('Server audit'), id: 'serveraudit', type: 'action', mobileapp: false},
+ {type: 'separator'},
{name: _('About'), id: 'about', type: 'action'}]
},
{name: _('Last modification'), id: 'last-mod', type: 'action', tablet: false}
@@ -1037,11 +1051,16 @@ class Menubar extends window.L.Control {
]},
{name: _UNO('.uno:HelpMenu', 'spreadsheet'), id: 'help', type: 'menu', menu: [
{name: _('Online Help'), id: 'online-help', type: 'action', iosapp: false},
+ {type: 'separator', iosapp: false},
{name: _('Keyboard shortcuts'), id: 'keyboard-shortcuts', type: 'action', iosapp: false},
+ {type: 'separator'},
+ {name: _('Forum'), id: 'forum', type: 'action'},
{name: _('Report an issue'), id: 'report-an-issue', type: 'action', iosapp: false},
{name: _('Latest Updates'), id: 'latestupdates', type: 'action', iosapp: false},
{name: _('Send Feedback'), id: 'feedback', type: 'action', mobileapp: false},
+ {type: 'separator'},
{name: _('Server audit'), id: 'serveraudit', type: 'action', mobileapp: false},
+ {type: 'separator'},
{name: _('About'), id: 'about', type: 'action'}]
},
{name: _('Last modification'), id: 'last-mod', type: 'action', tablet: false}
diff --git a/browser/src/control/Control.NotebookbarWriter.js b/browser/src/control/Control.NotebookbarWriter.js
index 5b91d4edab..979e29a635 100644
--- a/browser/src/control/Control.NotebookbarWriter.js
+++ b/browser/src/control/Control.NotebookbarWriter.js
@@ -431,18 +431,6 @@ window.L.Control.NotebookbarWriter = window.L.Control.Notebookbar.extend({
var hasServerAudit = this.getHiddenItems() ? !this.getHiddenItems().includes('server-audit') : true;

var content = [
- {
- 'type': 'toolbox',
- 'children': [
- {
- 'id': 'forum',
- 'type': 'bigtoolitem',
- 'text': _('Forum'),
- 'command': '.uno:ForumHelp',
- 'accessibility': { focusBack: true, combination: 'C', de: null }
- }
- ]
- },
{
'type': 'toolbox',
'children': [
@@ -469,56 +457,18 @@ window.L.Control.NotebookbarWriter = window.L.Control.Notebookbar.extend({
]
},
{ type: 'separator', id: 'help-keyboardshortcuts-break', orientation: 'vertical' },
- hasAccessibilitySupport ?
- {
- 'id':'togglea11ystate',
- 'type': 'bigcustomtoolitem',
- 'text': _('Screen Reading'),
- 'accessibility': { focusBack: true, combination: 'SR', de: null }
- } : {},
- hasAccessibilityCheck ?
- {
- 'id': 'accessibility-check',
- 'class': 'unoAccessibilityCheck',
- 'type': 'bigtoolitem',
- 'text': _UNO('.uno:AccessibilityCheck', 'text', true),
- 'command': '.uno:SidebarDeck.A11yCheckDeck',
- 'accessibility': { focusBack: false, combination: 'A', de: null }
- } : {},
- {
- 'id': 'validatesidebara11y',
- 'type': 'bigcustomtoolitem',
- 'text': _('Validate Sidebar'),
- 'visible': isDebugOn ? 'true' : 'false',
- 'accessibility': { focusBack: true, combination: 'VS', de: null }
- },
- {
- 'id': 'validatedialogsa11y',
- 'type': 'bigcustomtoolitem',
- 'text': _('Validate Dialog'),
- 'visible': isDebugOn ? 'true' : 'false',
- 'accessibility': { focusBack: true, combination: 'VD', de: null }
- },
- hasAccessibilitySupport || hasAccessibilityCheck ?
- {
- 'id': 'help-accessibility-break',
- 'type': 'separator',
- 'orientation': 'vertical'
- } : {},
- hasServerAudit ?
- {
- 'id': 'server-audit',
- 'type': 'bigcustomtoolitem',
- 'text': _('Server audit'),
- 'command': 'serveraudit',
- 'accessibility': { focusBack: false, combination: 'SA', de: null }
- } : {},
- hasServerAudit ?
{
- 'id': 'help-serveraudit-break',
- 'type': 'separator',
- 'orientation': 'vertical'
- } : {},
+ 'type': 'toolbox',
+ 'children': [
+ {
+ 'id': 'forum',
+ 'type': 'bigtoolitem',
+ 'text': _('Forum'),
+ 'command': '.uno:ForumHelp',
+ 'accessibility': { focusBack: true, combination: 'C', de: null }
+ }
+ ]
+ },
{
'type': 'toolbox',
'children': [
@@ -531,7 +481,6 @@ window.L.Control.NotebookbarWriter = window.L.Control.Notebookbar.extend({
},
]
},
- { 'type': 'separator', 'id': 'help-reportissue-break', 'orientation': 'vertical' },
hasLatestUpdates ?
{
'type': 'toolbox',
@@ -559,6 +508,62 @@ window.L.Control.NotebookbarWriter = window.L.Control.Notebookbar.extend({
}
]
} : {},
+ hasServerAudit ?
+ {
+ 'id': 'help-serveraudit-break',
+ 'type': 'separator',
+ 'orientation': 'vertical'
+ } : {},
+ hasServerAudit ?
+ {
+ 'id': 'server-audit',
+ 'type': 'bigcustomtoolitem',
+ 'text': _('Server audit'),
+ 'command': 'serveraudit',
+ 'accessibility': { focusBack: false, combination: 'SA', de: null }
+ } : {},
+ hasAccessibilitySupport || hasAccessibilityCheck ?
+ {
+ 'id': 'help-accessibility-break',
+ 'type': 'separator',
+ 'orientation': 'vertical'
+ } : {},
+ hasAccessibilitySupport ?
+ {
+ 'id':'togglea11ystate',
+ 'type': 'bigcustomtoolitem',
+ 'text': _('Screen Reading'),
+ 'accessibility': { focusBack: true, combination: 'SR', de: null }
+ } : {},
+ hasAccessibilityCheck ?
+ {
+ 'id': 'accessibility-check',
+ 'class': 'unoAccessibilityCheck',
+ 'type': 'bigtoolitem',
+ 'text': _UNO('.uno:AccessibilityCheck', 'text', true),
+ 'command': '.uno:SidebarDeck.A11yCheckDeck',
+ 'accessibility': { focusBack: false, combination: 'A', de: null }
+ } : {},
+ {
+ 'id': 'validatesidebara11y',
+ 'type': 'bigcustomtoolitem',
+ 'text': _('Validate Sidebar'),
+ 'visible': isDebugOn ? 'true' : 'false',
+ 'accessibility': { focusBack: true, combination: 'VS', de: null }
+ },
+ {
+ 'id': 'validatedialogsa11y',
+ 'type': 'bigcustomtoolitem',
+ 'text': _('Validate Dialog'),
+ 'visible': isDebugOn ? 'true' : 'false',
+ 'accessibility': { focusBack: true, combination: 'VD', de: null }
+ },
+ hasAbout ?
+ {
+ 'id': 'help-about-break',
+ 'type': 'separator',
+ 'orientation': 'vertical'
+ } : {},
hasAbout ?
{
'type': 'toolbox',

"Tor Lillqvist (via github)"

unread,
Mar 17, 2026, 8:05:31 AMMar 17
to collaboraon...@googlegroups.com
browser/src/control/Control.NotebookbarBuilder.js | 30 ++++++++--------------
1 file changed, 12 insertions(+), 18 deletions(-)

New commits:
commit b9a397a348c505290319840f2db2ae99aeb8d32f
Author: Tor Lillqvist <t...@collabora.com>
AuthorDate: Tue Mar 17 11:00:28 2026 +0200
Commit: Andras Timar <andras...@collabora.com>
CommitDate: Tue Mar 17 13:04:41 2026 +0100

We do want to be able to export as PDF also in CODA-W

Whatever reason was behind bypassing that for CODA-W does not exist
any more.

Signed-off-by: Tor Lillqvist <t...@collabora.com>
Change-Id: I1df6fc413b89d900a82bdf54782f4dfe5ae305a3

diff --git a/browser/src/control/Control.NotebookbarBuilder.js b/browser/src/control/Control.NotebookbarBuilder.js
index 046c09020f..e6dad2fb0f 100644
--- a/browser/src/control/Control.NotebookbarBuilder.js
+++ b/browser/src/control/Control.NotebookbarBuilder.js
@@ -394,15 +394,13 @@ window.L.Control.NotebookbarBuilder = window.L.Control.JSDialogBuilder.extend({
'action': !window.ThisIsAMobileApp ? 'exportepub' : 'downloadas-epub',
'text': _('EPUB (.epub)'),
'command': !window.ThisIsAMobileApp ? 'exportepub' : 'downloadas-epub'
- }
- ];
- if (!window.ThisIsTheWindowsApp)
- // In CODA-W surely just the PDF save with options should be enough
- submenuOpts.push({
+ },
+ {
'action': !window.ThisIsAMobileApp ? 'exportdirectpdf' : 'downloadas-pdf',
'text': _('PDF Document (.pdf)'),
'command': !window.ThisIsAMobileApp ? 'exportdirectpdf' : 'downloadas-pdf'
- });
+ }
+ ];
submenuOpts.push({
'action': 'downloadas-html',
'text': _('HTML File (.html)')
@@ -434,15 +432,13 @@ window.L.Control.NotebookbarBuilder = window.L.Control.JSDialogBuilder.extend({
{
'action': 'downloadas-html',
'text': _('HTML File (.html)')
- }
- ];
- if (!window.ThisIsTheWindowsApp)
- // As for 'text'
- submenuOpts.push({
+ },
+ {
'action': !window.ThisIsAMobileApp ? 'exportdirectpdf' : 'downloadas-pdf',
'text': _('PDF Document (.pdf)'),
'command': !window.ThisIsAMobileApp ? 'exportdirectpdf' : 'downloadas-pdf'
- });
+ }
+ ];
if (!window.ThisIsTheAndroidApp)
submenuOpts.push({
'action': 'exportpdf' ,
@@ -470,15 +466,13 @@ window.L.Control.NotebookbarBuilder = window.L.Control.JSDialogBuilder.extend({
{
'action': 'downloadas-html',
'text': _('HTML Document (.html)')
- }
- ];
- if (!window.ThisIsTheWindowsApp)
- // As for 'text'
- submenuOpts.push({
+ },
+ {
'action': !window.ThisIsAMobileApp ? 'exportdirectpdf' : 'downloadas-pdf',
'text': _('PDF Document (.pdf)'),
'command': !window.ThisIsAMobileApp ? 'exportdirectpdf' : 'downloadas-pdf',
- });
+ }
+ ];
if (!window.ThisIsTheAndroidApp)
submenuOpts.push({
'action': 'exportpdf',

"Gökay Şatır (via github)"

unread,
Mar 18, 2026, 3:45:18 AMMar 18
to collaboraon...@googlegroups.com
browser/src/app/TextSelectionMiddleware.ts | 3 +++
1 file changed, 3 insertions(+)

New commits:
commit 246db50f6a018abf8f2960f30e1e67c95416c5b6
Author: Gökay Şatır <gokay...@collabora.com>
AuthorDate: Mon Mar 16 12:26:48 2026 +0300
Commit: Szymon Kłos <eszk...@gmail.com>
CommitDate: Wed Mar 18 08:44:19 2026 +0100

Core side normally sends an invalidateCursor message.
But when the cursor is at the end of the text, the invalidation message is not sent for select-all command.
Invalidation message triggers a redraw. Absence of it causes to lack a redraw.
So we trigger a redraw in case of the absence of invalidation message.

This sholdn't effect performance, since this doesn't add a draw request to the queue if there is already one.

Signed-off-by: Gökay Şatır <gokay...@collabora.com>
Change-Id: I8bf9aecaa473c94032cdcdbd4152b59c1f0dfa7b

diff --git a/browser/src/app/TextSelectionMiddleware.ts b/browser/src/app/TextSelectionMiddleware.ts
index fa1a386e19..5bc15901c5 100644
--- a/browser/src/app/TextSelectionMiddleware.ts
+++ b/browser/src/app/TextSelectionMiddleware.ts
@@ -91,6 +91,9 @@ class TextSelections {
public static setEndRectangle(rectangle: cool.SimpleRectangle) {
this.endRectangle = rectangle;
this.updateMarkers();
+
+ // Schedule a redraw if there's not any.
+ app.sectionContainer.requestReDraw();
}

private static updateMarkers() {

"Gökay Şatır (via github)"

unread,
Mar 18, 2026, 4:29:18 AMMar 18
to collaboraon...@googlegroups.com
browser/src/canvas/sections/ShapeHandlesSection.ts | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)

New commits:
commit bb89e2ff7747e52b1e2d8d5851799556e0f2cf7b
Author: Gökay Şatır <gokay...@collabora.com>
AuthorDate: Mon Mar 16 16:25:54 2026 +0300
Commit: Szymon Kłos <eszk...@gmail.com>
CommitDate: Wed Mar 18 09:28:37 2026 +0100

Impress Issue: Flickering after changing text and pressing ESC.

Reason: While setting the visibility of the section, we are also making SVG visible.
SVG can contain stale content at the time.
Showing it shows the user old content for a split second. It feels like flickering.

Solution: We don't need to show the SVG there anyway. It is shown while dragging or resizing, and there is already a call to showSVG for those actions.

Signed-off-by: Gökay Şatır <gokay...@collabora.com>
Change-Id: I37e404cbdac42167c50e3c052b96c82989205d80

diff --git a/browser/src/canvas/sections/ShapeHandlesSection.ts b/browser/src/canvas/sections/ShapeHandlesSection.ts
index f9bf9f11cb..6fa0b2c651 100644
--- a/browser/src/canvas/sections/ShapeHandlesSection.ts
+++ b/browser/src/canvas/sections/ShapeHandlesSection.ts
@@ -565,9 +565,11 @@ class ShapeHandlesSection extends CanvasSectionObject {
for (let i = 0; i < this.sectionProperties.subSections.length; i++)
this.sectionProperties.subSections[i].setShowSection(this.showSection);

- if (this.showSection)
- this.showSVG();
- else
+ // Don't call showSVG() here: the SVG overlay may contain stale
+ // content from a previous render. A new shapeselectioncontent
+ // message will arrive and setSVG() will set the up-to-date SVG.
+ // The overlay is only made visible on demand (e.g. during drag).
+ if (!this.showSection)
this.hideSVG();
}


It is loading more messages.
0 new messages