Skip to content

Add OnlyMath plugin - #629

Open
MrMathiasen wants to merge 6 commits into
ONLYOFFICE:masterfrom
MrMathiasen:add-onlymath-plugin
Open

Add OnlyMath plugin#629
MrMathiasen wants to merge 6 commits into
ONLYOFFICE:masterfrom
MrMathiasen:add-onlymath-plugin

Conversation

@MrMathiasen

Copy link
Copy Markdown

OnlyMath

OnlyMath adds an in-document computer-algebra tool to the Document editor.
Insert a math field with Alt+M and evaluate it in place:

  • Calculate arithmetic and symbolic expressions
  • Solve equations
  • Differentiate and integrate
  • Precision control in a right-side Settings panel, including a symbolic
    "Mathematical" mode that keeps π, √2, e exact
  • Danish high-school conventions (decimal comma, integration constant k)

How it works

Math is evaluated by Giac/Xcas, compiled to WebAssembly and run in a Web
Worker. The engine is bundled with the plugin, so OnlyMath works fully
offline
— no external services and no API keys.

What this PR adds

  • sdkjs-plugins/content/onlymath/ — the complete plugin (config.json v1.0.0,
    index.html + panel.html, built assets, the Giac engine under cas/,
    light/dark toolbar icons, store card icons + 4 screenshots, CHANGELOG.md,
    README.md, LICENSE).
  • Registered the plugin in store/config.json (discussion left empty).

Notes

  • Offered: OpenMath
  • Editors: Word (Document)
  • Categories: work, specAbilities
  • License: GPL-3.0-or-later, because the bundled Giac/Xcas engine is GPL
    (its license is included at cas/giac/LICENSE).

OnlyMath brings in-document computer algebra to the Document editor: insert a
math field (Alt+M) and calculate, solve equations, differentiate and integrate
using a bundled Giac/Xcas engine compiled to WebAssembly. Runs fully client-side
and offline; no external services or API keys.

- config.json v1.0.0 with store block, icons2 scale maps (light/dark),
  screenshots and categories (work, specAbilities)
- Registered in store/config.json

Co-authored-by: Cursor <cursoragent@cursor.com>
@CLAassistant

CLAassistant commented Jul 22, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@drape3a

drape3a commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Hi @MrMathiasen
Thanks for submitting OnlyMath — really like the offline CAS approach. While reviewing it for the marketplace I ran into a few things that are worth fixing before it goes further, mostly around frame/origin handling that will become more strict soon.

Cross-frame window.top/window.parent usage will break under iframe sandboxing — please refactor

This plugin relies on direct DOM access to ancestor frames in a few places, all rooted in shared-*.js and the background script:

  • Hotkeys — walks up window.parent and attaches a capturing keydown listener on each ancestor document to catch Alt+M/Alt+B/Alt+L.

  • Init-once guard / op lock — reads/writes properties directly on window.top, with no try/catch, so it throws synchronously whenever the plugin frame isn't same-origin with the top window.

  • Background ↔ panel sync — a set of CustomEvents dispatched on window.top, used as a shared bus between the two plugin variations.

Today this only fails when the plugin happens to be cross-origin with the top window. A future editor version will sandbox plugin iframes (allow-scripts without allow-same-origin), which makes every window.top/window.parent access cross-origin unconditionally — so items 1–3 will stop working for every user, not just in edge-case deployments.

Suggested fixes, following the pattern already used by the official ai plugin:

  • Hotkeys → use Asc.plugin.attachEditorEvent instead of walking window.parent. It's delivered via postMessage from the editor core, so it doesn't touch other frames at all.

    window.Asc.plugin.attachEditorEvent("onKeyDown", function(e) {
       console.log(e); // e.ctrlKey || e.metaKey || e.altKey || e.keyCode 
    });
  • Init-once guard / lock → these only need to survive within a single frame's own script instance — replace with a plain local variable, no cross-frame state needed.

  • Background ↔ panel sync → replace the window.top CustomEvent bus with Asc.plugin.executeMethod to send and Asc.plugin.attachEditorEvent to receive. Same host-mediated channel, sandbox-agnostic.

    // sending (from any plugin variation frame)
    Asc.plugin.executeMethod("SendEvent", [eventName, data]);
    // receive (in another variation frame of the same plugin)
    Asc.plugin.attachEditorEvent(eventName, function(data) { ... });
  • Persisted settings (e.g. precision) → move off window.top.__om_precision to localStorage or the plugin options mechanism, rather than a shared-window property.

Separate issue: relative paths to plugins.js / plugins-ui.js / plugins.css

index.html and panel.html reference these as ./../v1/plugins.js etc. — relative paths that assume a fixed directory layout relative to the plugin's own location. This happens to resolve correctly in the desktop editor's local file layout, but isn't guaranteed to hold wherever else the plugin gets hosted/served from. These should be absolute paths so the plugin works consistently regardless of where it's deployed, not just in the desktop editor.

Let me know if anything above is unclear — happy to help however's useful. Would like these addressed before the sandbox change lands, since it'll otherwise silently stop working for every install.

Address marketplace review: hotkeys via attachEditorEvent(onKeyDown),
background↔panel via SendEvent, local op-lock/init, localStorage
precision/status, and absolute onlyoffice.github.io sdkjs-plugins/v1 URLs.
@MrMathiasen

Copy link
Copy Markdown
Author

Hi @drape3a — thanks for the careful review and the concrete guidance.

Addressed in the latest commit on this branch:

  1. Hotkeys — dropped parent-frame keydown walks; shortcuts now use Asc.plugin.attachEditorEvent("onKeyDown", …).
  2. Init-once / op-lock — module-local variables in each frame (no window.top properties).
  3. Background ↔ panel sync — replaced the window.top CustomEvent bus with executeMethod("SendEvent", …) / attachEditorEvent(…).
  4. Precision (and last status) — moved to localStorage.
  5. Asset pathsindex.html / panel.html now load plugins.js / plugins-ui.js / plugins.css from absolute https://onlyoffice.github.io/sdkjs-plugins/v1/… URLs.

One note: panel-window.ts still has a few best-effort same-origin DOM hooks (hide panel close-X, open Settings via the background-plugin “…” button). They’re try/catch-guarded and no-op under sandbox; opening the panel via context menu / ShowWindow does not depend on them. Happy to strip those entirely if you’d prefer zero ancestor access.

Happy to iterate further if anything else comes up in re-review.

@drape3a

drape3a commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Thanks for the quick turnaround — the window.top/window.parent removal, local init-guard/lock, localStorage for settings, and the onKeyDown hotkey migration all look correct now.

One concern with the background↔panel sync fix: it now uses Asc.plugin.executeMethod("SendEvent", [eventName, data]) to send and attachEditorEvent(eventName, cb) to receive. SendEvent broadcasts to other running plugins by guid, but explicitly skips the sender's own guid (onPluginEvent2 in the SDK core checks guid === currentPluginEvent and continues) — and background/panel share the same guid. So this likely never delivers events between the two variations at all.

Please use sendToPlugin/Asc.PluginWindow (attachEvent/command) instead, as suggested earlier — that channel is routed by windowID, not guid, so it doesn't hit this exclusion. Worth manually testing: open the panel, run Solve on an equation with multiple variables, and confirm the variable picker actually appears — that'll tell you quickly if the current wiring is silently dead.

// from any plugin variation frame
const customWindow = new window.Asc.PluginWindow();
customWindow.attachEvent("eventName", function(data) {});
customWindow.command("eventName2", data2);

// in another variation frame of the same plugin
window.Asc.plugin.sendToPlugin("eventName", data);
window.Asc.plugin.attachEvent("eventName2", function(data2) {});

…ndbox purity

Follow-up to reviewer feedback: panel-window no longer patches asc_pluginRun,
intercepts the '...' dropdown, or hides the panel close-X via ancestor-frame
DOM. The panel opens purely through Asc.PluginWindow / ShowWindow, so the
plugin touches no ancestor frame at all. Rebuilt full/bundled package.
@MrMathiasen

Copy link
Copy Markdown
Author

Follow-up on my earlier note about panel-window.ts: I went ahead and removed the remaining same-origin ancestor hooks entirely for zero ancestor access, as offered.

Dropped from the plugin:

  • the asc_pluginRun patch on the editor frame (settings-menu → panel routing),
  • the capture-phase click interception on the "…" dropdown, and
  • the close-X hide + its MutationObserver on the editor document.

The panel now opens purely via Asc.PluginWindow / ShowWindow (from the context-menu "OnlyMath Settings" item), so the plugin no longer reads or writes any ancestor frame. Rebuilt the full/bundled package and pushed to this branch (70bf29b).

The only same-origin access left anywhere is doc-ops reaching the editor's asc_AddMath for a native equation-field insert, and that already falls back to the Builder API when the instance isn't reachable — so it degrades gracefully under sandbox too. Happy to revisit that as well if you'd prefer. Thanks again!

@MrMathiasen

MrMathiasen commented Jul 22, 2026 via email

Copy link
Copy Markdown
Author

Address drape3a's review: replace SendEvent (same-guid dead channel)
with Asc.PluginWindow command/sendToPlugin (windowID-routed). Panel
isActivated=false so background owns the window. Remap Calculate from
Alt+B to Alt+C to avoid Firefox Bookmarks menu collision.
@MrMathiasen

Copy link
Copy Markdown
Author

Hi @drape3a — follow-up on your SendEvent note (and the earlier Alt+B issue I was debugging).

Background ↔ panel messaging — switched to the windowID-routed custom-window channel you described:

  • background owns the panel as Asc.PluginWindow; sends with command(), receives with attachEvent()
  • panel sends with Asc.plugin.sendToPlugin(), receives with Asc.plugin.attachEvent()
  • panel variation is now isActivated: false so the editor never spawns an unowned panel; it opens via context menu → PluginWindow
  • panel emits panelReady on mount; background replays status + registry

Manually verified: multi-variable Solve shows the variable picker; panel buttons drive the document.

Calculate shortcut — remapped Alt+B → Alt+C. On Danish Firefox (Linux), Alt+B opens Bogmærker before the editor can preventDefault via attachEditorEvent("onKeyDown"). Alt+C avoids that collision.

Latest package is on this branch. Happy to iterate further if anything else comes up.

@MrMathiasen

MrMathiasen commented Jul 22, 2026 via email

Copy link
Copy Markdown
Author

@drape3a

drape3a commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Hi! Sandbox check passed, all clean now. One bug: the Settings window doesn't open from the toolbar.

@MrMathiasen

MrMathiasen commented Jul 23, 2026 via email

Copy link
Copy Markdown
Author

@drape3a

drape3a commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

...I've updated it (removed the dots) ...

I don't see your latest changes reflected in the PR — could you push them?

- config.json: drop the panelRight variation so the non-functional "…" menu no longer renders on the OnlyMath row in the background-plugins list.
- Add a dedicated "Settings" toolbar button that opens the panel via Asc.PluginWindow (sandbox-safe; no ancestor-frame access).
- Refresh built bundle (index) and README/CHANGELOG to match.

Co-authored-by: Cursor <cursoragent@cursor.com>
@MrMathiasen

MrMathiasen commented Jul 23, 2026 via email

Copy link
Copy Markdown
Author

@drape3a

drape3a commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Hi @MrMathiasen!
Settings still doesn't open from the toolbar (Insert tab).

@MrMathiasen

MrMathiasen commented Jul 24, 2026 via email

Copy link
Copy Markdown
Author

- Subscribe to onToolbarMenuClick in config.json events so the editor actually delivers toolbar clicks (the missing subscription is why the Insert-tab icon did nothing).
- Single OnlyMath toolbar icon with a dropdown: Insert (Alt+M), Calculate (Alt+C), Solve (Alt+L), OnlyMath Settings. Tooltip: "CAS Calculator - OnlyMath".
- Sandbox-safe: editor-mediated AddToolbarMenuItem/attachToolbarMenuClickEvent only; no ancestor-frame access.
- Refresh built bundles.

Co-authored-by: Cursor <cursoragent@cursor.com>
@MrMathiasen

MrMathiasen commented Jul 24, 2026 via email

Copy link
Copy Markdown
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants