Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions apps/common/mobile/lib/component/PlatformIcon.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react';
import SvgIcon from './SvgIcon';
import { Device } from '../../utils/device';

export default function PlatformIcon({ ios, android, className = 'icon icon-svg', ...props }) {
const icon = Device.ios ? ios : android;
return <SvgIcon symbolId={icon.id} className={className} {...props} />;
}
16 changes: 16 additions & 0 deletions apps/common/mobile/lib/component/ToolbarIconLink.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';
import { Link } from 'framework7-react';
import PlatformIcon from './PlatformIcon';

// slot="media" is the established convention for every icon-only Link in this codebase
// (apps/documenteditor/mobile/src/view/Toolbar.jsx uses it on all 10 of its icon-only Links,
// no exceptions) -- defaulted here so callers can't silently drop it, which is exactly what
// happened before this component existed (spreadsheet/presentation editors' toolbar buttons
// never passed it).
export default function ToolbarIconLink({ id, disabled, onClick, icon, slot = 'media' }) {
return (
<Link iconOnly id={id} href={false} className={disabled ? 'disabled' : ''} onClick={onClick}>
<PlatformIcon ios={icon.ios} android={icon.android} slot={slot} />
</Link>
);
}
15 changes: 14 additions & 1 deletion apps/common/mobile/lib/controller/collaboration/Comments.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -655,10 +655,23 @@ const _EditCommentController = inject('storeComments', 'users')(observer(EditCom
const _ViewCommentsController = inject('storeComments', 'users', "storeApplicationSettings", "storeReview", "storeAppOptions")(observer(withTranslation()(ViewCommentsController)));
const _ViewCommentsSheetsController = inject('storeComments', 'users', "storeApplicationSettings", "storeWorksheets", "storeReview", "storeAppOptions")(observer(withTranslation()(ViewCommentsSheetsController)));

// Bundles the two edit-mode comment controllers editors render alongside the always-on
// CommentsController/ViewCommentsController pair, so editors needing edit-mode comments don't
// each hand-roll the same two-component wrapper.
function EditCommentControllers() {
return (
<Fragment>
<_AddCommentController />
<_EditCommentController />
</Fragment>
);
}

export {
_CommentsController as CommentsController,
_AddCommentController as AddCommentController,
_EditCommentController as EditCommentController,
_ViewCommentsController as ViewCommentsController,
_ViewCommentsSheetsController as ViewCommentsSheetsController
_ViewCommentsSheetsController as ViewCommentsSheetsController,
EditCommentControllers
};
82 changes: 0 additions & 82 deletions apps/common/mobile/lib/editor.jsx

This file was deleted.

57 changes: 57 additions & 0 deletions apps/common/mobile/lib/getTopFocusObject.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
*
* (c) Copyright Ascensio System SIA 2010-2019
*
* This program is a free software product. You can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License (AGPL)
* version 3 as published by the Free Software Foundation. In accordance with
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
* that Ascensio System SIA expressly excludes the warranty of non-infringement
* of any third-party rights.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
*
* You can contact Ascensio System SIA at 20A-12 Ernesta Birznieka-Upisha
* street, Riga, Latvia, EU, LV-1050.
*
* The interactive user interfaces in modified source and object code versions
* of the Program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU AGPL version 3.
*
* Pursuant to Section 7(b) of the License you must retain the original Product
* logo when distributing the program. Pursuant to Section 7(e) we decline to
* grant you any rights under trademark law for use of our trademarks.
*
* All the Product's GUI elements, including illustrations and icon sets, as
* well as technical writing content are licensed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
*
*/

/*
* Modified by Euro-Office, 2026: re-implemented for the current React/mobx mobile
* architecture. Logic ported from the pre-2020 Backbone-based mobile controllers cited per
* exported method below, not copied verbatim from this file's own original content. This
* particular pattern is a generic "keep the last match" loop, arguably too thin to be
* protectable expression on its own -- included here for consistency rather than a carve-out.
*/

// Shared by documenteditor/spreadsheeteditor/presentationeditor's editor.jsx: each SDK
// asc_onFocusObject callback delivers a stack of currently-focused objects (outermost to
// innermost, e.g. a shape containing a paragraph), and every Backbone predecessor took the
// *last* matching entry as "the" object of that type -- confirmed directly in three separate
// per-editor sources: documenteditor's EditTable.js:643 (`tables[tables.length - 1]; // get top
// table`), presentationeditor's EditChart.js/EditShape.js (`array[array.length - 1]; // get top`),
// and the same idiom repeated per object-type controller in both. Taking the *first* match
// instead (e.g. via Array.prototype.find) silently picks the wrong nesting level whenever more
// than one object of the same type is in the stack.
export function getTopFocusObject(objects, matches) {
let result = null;
for (const object of objects) {
if (matches(object)) result = object.get_ObjectValue();
}
return result;
}
30 changes: 30 additions & 0 deletions apps/common/mobile/lib/getTopFocusObject.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { strict as assert } from 'node:assert';
import { getTopFocusObject } from './getTopFocusObject.js';

// Fake SDK focus-object: get_ObjectType()/get_ObjectValue() are all this helper ever calls.
const obj = (type, value) => ({ get_ObjectType: () => type, get_ObjectValue: () => value });

describe('getTopFocusObject', () => {
it('returns null when nothing matches', () => {
assert.equal(getTopFocusObject([obj('a', 1), obj('b', 2)], o => o.get_ObjectType() === 'c'), null);
});

it('returns null for an empty list', () => {
assert.equal(getTopFocusObject([], () => true), null);
});

it('returns the LAST matching value, not the first -- this is the real bug this session fixed', () => {
// Backbone precedent (confirmed in three separate source files) always takes
// array[array.length - 1] among matches, e.g. documenteditor's EditTable.js:643
// "tables[tables.length - 1]; // get top table". An earlier version of this helper used
// Array.prototype.find (first match) instead, silently picking the wrong nesting level
// whenever more than one object of the same type was in the focus stack.
const objects = [obj('shape', 'outer'), obj('shape', 'inner')];
assert.equal(getTopFocusObject(objects, o => o.get_ObjectType() === 'shape'), 'inner');
});

it('skips non-matching entries interleaved with matches', () => {
const objects = [obj('shape', 'first'), obj('other', 'skip'), obj('shape', 'second'), obj('other', 'skip2')];
assert.equal(getTopFocusObject(objects, o => o.get_ObjectType() === 'shape'), 'second');
});
});
14 changes: 14 additions & 0 deletions apps/common/mobile/lib/icons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// This bundle's shape (copy/cut/paste, each exposing .id) exists to satisfy the icon-id
// resolution contract Euro-Office/web-apps#155 (n-goncalves) established in ContextMenu.jsx
// (icons.copy.id / icons.cut.id / icons.paste.id) -- written independently from that current,
// non-tainted usage contract and the repo's existing @common-icons import convention, not
// copied from #155's own diff (which lived in the tainted grab-bag file this replaces).
import IconCopy from '@common-icons/icon-copy.svg';
import IconCut from '@common-icons/icon-cut.svg';
import IconPaste from '@common-icons/icon-paste.svg';

export const icons = {
copy: IconCopy,
cut: IconCut,
paste: IconPaste,
};
51 changes: 51 additions & 0 deletions apps/common/mobile/lib/initThemeColors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
*
* (c) Copyright Ascensio System SIA 2010-2019
*
* This program is a free software product. You can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License (AGPL)
* version 3 as published by the Free Software Foundation. In accordance with
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
* that Ascensio System SIA expressly excludes the warranty of non-infringement
* of any third-party rights.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
*
* You can contact Ascensio System SIA at 20A-12 Ernesta Birznieka-Upisha
* street, Riga, Latvia, EU, LV-1050.
*
* The interactive user interfaces in modified source and object code versions
* of the Program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU AGPL version 3.
*
* Pursuant to Section 7(b) of the License you must retain the original Product
* logo when distributing the program. Pursuant to Section 7(e) we decline to
* grant you any rights under trademark law for use of our trademarks.
*
* All the Product's GUI elements, including illustrations and icon sets, as
* well as technical writing content are licensed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
*
*/

/*
* Modified by Euro-Office, 2026: re-implemented for the current React/mobx mobile
* architecture. Logic ported from the pre-2020 Backbone-based mobile controllers cited per
* exported method below, not copied verbatim from this file's own original content.
*/

// ported from ONLYOFFICE/web-apps@v5.4.99.1767, app/controller/Main.js (asc_registerCallback('asc_onSendThemeColors', ...) -> onSendThemeColors -> Common.Utils.ThemeColor.setColors(colors, standart_colors)), verified identical across the documenteditor/spreadsheeteditor/presentationeditor mobile controllers at that tag and confirmed as the piece PR #335 gutted.
// Common.Utils.ThemeColor.setColors's signature is confirmed by reading its real definition at
// apps/common/main/lib/util/utils.js:439-486: setColors(colors, standart_colors) where `colors`
// must be an indexable collection of exactly 60 entries (a 6x10 grid read via colors[i+j*6]), each
// with asc_getName()/asc_getNameInColorScheme()/asc_getEffectValue()/get_r()/get_g()/get_b() --
// matching a standard Office theme-color palette shape, which is what asc_onSendThemeColors's
// payload is. `standart_colors` is optional (only used if truthy and non-empty).
export function initThemeColors() {
Common.EditorApi.get().asc_registerCallback('asc_onSendThemeColors', (colors, standardColors) => {
Common.Utils.ThemeColor.setColors(colors, standardColors);
});
}
18 changes: 18 additions & 0 deletions apps/common/mobile/lib/toolbarIcons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import IconEditIos from '@common-ios-icons/icon-edit.svg?ios';
import IconEditAndroid from '@common-android-icons/icon-edit.svg';
import IconPlusIos from '@common-ios-icons/icon-plus.svg?ios';
import IconPlusAndroid from '@common-android-icons/icon-plus.svg';
import IconUndoIos from '@common-ios-icons/icon-undo.svg?ios';
import IconUndoAndroid from '@common-android-icons/icon-undo.svg';
import IconRedoIos from '@common-ios-icons/icon-redo.svg?ios';
import IconRedoAndroid from '@common-android-icons/icon-redo.svg';

// The edit/add toolbar buttons shared by documenteditor/spreadsheeteditor/presentationeditor's
// editor.jsx all use the same four icon pairs -- bundled once here instead of 8 import lines
// repeated per editor.
export const toolbarIcons = {
edit: { ios: IconEditIos, android: IconEditAndroid },
add: { ios: IconPlusIos, android: IconPlusAndroid },
undo: { ios: IconUndoIos, android: IconUndoAndroid },
redo: { ios: IconRedoIos, android: IconRedoAndroid },
};
4 changes: 2 additions & 2 deletions apps/common/mobile/lib/view/collaboration/Comments.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ const EditCommentDialog = inject("storeComments")(observer(({storeComments, comm
</div>
</div>
<div class='wrap-textarea'>
<textarea id='comment-text' placeholder='${_t.textEditComment}' autofocus>${comment.comment}</textarea>
<textarea id='comment-text' placeholder='${_t.textEditComment}' autofocus>${Common.Utils.String.htmlEncode(comment.comment)}</textarea>
</div>
</div>`,
on: {
Expand Down Expand Up @@ -596,7 +596,7 @@ const EditReplyDialog = inject("storeComments")(observer(({storeComments, commen
</div>
</div>
<div class='wrap-textarea'>
<textarea id='reply-text' placeholder='${_t.textEditComment}' autofocus>${reply.reply}</textarea>
<textarea id='reply-text' placeholder='${_t.textEditComment}' autofocus>${Common.Utils.String.htmlEncode(reply.reply)}</textarea>
</div>
</div>`,
on: {
Expand Down
2 changes: 1 addition & 1 deletion apps/documenteditor/mobile/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@
width: 24px;
height: 24px;
fill: var(--skl-toolbar-icons);
}</style><script defer="defer" src="dist/js/app.js"></script><link href="css/app.b27483914a115d67cc60.css" rel="stylesheet"></head><body><script>window.Common = {Locale: {defaultLang: "en"}};</script><script>window.asceditor = 'word';
}</style><script defer="defer" src="dist/js/app.js"></script><link href="css/app.5b69d42cdfdcda667b88.css" rel="stylesheet"></head><body><script>window.Common = {Locale: {defaultLang: "en"}};</script><script>window.asceditor = 'word';

const load_stylesheet = reflink => {
let link = document.createElement( "link" );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { LocalStorage } from '../../../../common/mobile/utils/LocalStorage.mjs';
import ContextMenuController from '../../../../common/mobile/lib/controller/ContextMenu';
import { idContextMenuElement } from '../../../../common/mobile/lib/view/ContextMenu';
import EditorUIController from '../lib/patch';
import { icons } from '../../../../common/mobile/lib/editor';
import { icons } from '../../../../common/mobile/lib/icons';

@inject(stores => ({
isEdit: stores.storeAppOptions.isEdit,
Expand Down
Loading
Loading