diff --git a/apps/common/mobile/lib/component/PlatformIcon.jsx b/apps/common/mobile/lib/component/PlatformIcon.jsx new file mode 100644 index 0000000000..12de71bd51 --- /dev/null +++ b/apps/common/mobile/lib/component/PlatformIcon.jsx @@ -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 ; +} diff --git a/apps/common/mobile/lib/component/ToolbarIconLink.jsx b/apps/common/mobile/lib/component/ToolbarIconLink.jsx new file mode 100644 index 0000000000..b443a3a198 --- /dev/null +++ b/apps/common/mobile/lib/component/ToolbarIconLink.jsx @@ -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 ( + + + + ); +} diff --git a/apps/common/mobile/lib/controller/collaboration/Comments.jsx b/apps/common/mobile/lib/controller/collaboration/Comments.jsx index c29311b7be..1aa8c83005 100644 --- a/apps/common/mobile/lib/controller/collaboration/Comments.jsx +++ b/apps/common/mobile/lib/controller/collaboration/Comments.jsx @@ -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 ( + + <_AddCommentController /> + <_EditCommentController /> + + ); +} + export { _CommentsController as CommentsController, _AddCommentController as AddCommentController, _EditCommentController as EditCommentController, _ViewCommentsController as ViewCommentsController, - _ViewCommentsSheetsController as ViewCommentsSheetsController + _ViewCommentsSheetsController as ViewCommentsSheetsController, + EditCommentControllers }; \ No newline at end of file diff --git a/apps/common/mobile/lib/editor.jsx b/apps/common/mobile/lib/editor.jsx deleted file mode 100644 index 9fd93df9d5..0000000000 --- a/apps/common/mobile/lib/editor.jsx +++ /dev/null @@ -1,82 +0,0 @@ -import React from 'react'; -import { Device } from '../utils/device'; -import SvgIcon from './component/SvgIcon'; -import IconEditSettingsIos from '@common-ios-icons/icon-edit-settings.svg?ios'; -import IconEditSettingsAndroid from '@common-android-icons/icon-edit-settings.svg'; -import IconAddOtherIos from '@common-ios-icons/icon-add-other.svg?ios'; -import IconAddOtherAndroid from '@common-android-icons/icon-add-other.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'; -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 = { - edit: { ios: IconEditSettingsIos, android: IconEditSettingsAndroid }, - add: { ios: IconAddOtherIos, android: IconAddOtherAndroid }, - undo: { ios: IconUndoIos, android: IconUndoAndroid }, - redo: { ios: IconRedoIos, android: IconRedoAndroid }, - copy: IconCopy, - cut: IconCut, - paste: IconPaste, -}; - -/** - * Platform-aware icon component that renders iOS or Android icon based on device - * @param {object} props - * @param {object} props.ios - iOS icon module (with .id property) - * @param {object} props.android - Android icon module (with .id property) - * @param {string} [props.className] - CSS class name - */ -export const PlatformIcon = ({ ios, android, className = 'icon icon-svg' }) => ( - -); - -/** - * Factory to create object getter functions for focus objects - * @param {object} focusObjects - The storeFocusObjects instance - * @param {number} type - The Asc.c_oAscTypeSelectElement type to filter by - * @param {function} [extraCheck] - Optional additional filter function - * @returns {function} Getter function that returns the last matching object's value - */ -export const createObjectGetter = (focusObjects, type, extraCheck) => () => { - const match = focusObjects._focusObjects - .filter(obj => { - if (obj.get_ObjectType() !== type) return false; - return !extraCheck || extraCheck(obj); - }) - .pop(); - return match?.get_ObjectValue(); -}; - -/** - * Builds standard object getters for a storeFocusObjects instance - * @param {object} storeFocusObjects - The store to add getters to - * @param {object} types - Map of getter names to {type, check?} configs - */ -export const buildFocusObjectGetters = (storeFocusObjects, types) => { - storeFocusObjects.intf = storeFocusObjects.intf || {}; - - for (const [name, config] of Object.entries(types)) { - storeFocusObjects.intf[name] = createObjectGetter( - storeFocusObjects, - config.type, - config.check - ); - } -}; - -/** - * Common theme colors initialization - identical across all editors - */ -export const initThemeColors = () => { - Common.EditorApi.get().asc_registerCallback('asc_onSendThemeColors', (colors, standartColors) => { - Common.Utils.ThemeColor.setColors(colors, standartColors); - }); -}; diff --git a/apps/common/mobile/lib/getTopFocusObject.js b/apps/common/mobile/lib/getTopFocusObject.js new file mode 100644 index 0000000000..8f22f9ed0b --- /dev/null +++ b/apps/common/mobile/lib/getTopFocusObject.js @@ -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; +} diff --git a/apps/common/mobile/lib/getTopFocusObject.test.js b/apps/common/mobile/lib/getTopFocusObject.test.js new file mode 100644 index 0000000000..7579cd21bb --- /dev/null +++ b/apps/common/mobile/lib/getTopFocusObject.test.js @@ -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'); + }); +}); diff --git a/apps/common/mobile/lib/icons.js b/apps/common/mobile/lib/icons.js new file mode 100644 index 0000000000..909a8a37bd --- /dev/null +++ b/apps/common/mobile/lib/icons.js @@ -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, +}; diff --git a/apps/common/mobile/lib/initThemeColors.js b/apps/common/mobile/lib/initThemeColors.js new file mode 100644 index 0000000000..7b8b976a1f --- /dev/null +++ b/apps/common/mobile/lib/initThemeColors.js @@ -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); + }); +} diff --git a/apps/common/mobile/lib/toolbarIcons.js b/apps/common/mobile/lib/toolbarIcons.js new file mode 100644 index 0000000000..d6255b067e --- /dev/null +++ b/apps/common/mobile/lib/toolbarIcons.js @@ -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 }, +}; diff --git a/apps/common/mobile/lib/view/collaboration/Comments.jsx b/apps/common/mobile/lib/view/collaboration/Comments.jsx index ba043e6974..739e807636 100644 --- a/apps/common/mobile/lib/view/collaboration/Comments.jsx +++ b/apps/common/mobile/lib/view/collaboration/Comments.jsx @@ -310,7 +310,7 @@ const EditCommentDialog = inject("storeComments")(observer(({storeComments, comm
- +
`, on: { @@ -596,7 +596,7 @@ const EditReplyDialog = inject("storeComments")(observer(({storeComments, commen
- +
`, on: { diff --git a/apps/documenteditor/mobile/index.html b/apps/documenteditor/mobile/index.html index 6e4b83eaf2..682c481c12 100644 --- a/apps/documenteditor/mobile/index.html +++ b/apps/documenteditor/mobile/index.html @@ -218,7 +218,7 @@ width: 24px; height: 24px; fill: var(--skl-toolbar-icons); -}