Conversation
Adds two editor plugins for Overleaf CE, both fully self-contained: no iframes, no CDN, no new server-side dependencies. Diagram editor (modules/drawio) - .drawio documents hold a plain-text mxGraphModel XML (human-readable, diffable, compatible with the free Draw.io desktop app; maxGraph registers mxGraphModel/mxCell/mxPoint codec aliases for that). - Canvas editor renders on @maxgraph/core (Apache-2.0, the mxGraph successor that powers draw.io), bundled into Overleaf's own JS. Toolbar: rectangle/ellipse/text insert, two-click arrows, freehand pencil, delete, undo/redo, zoom/fit, live stroke/fill/width styling; keyboard: Delete, Ctrl+Z / Ctrl+Shift+Z / Ctrl+Y. - Save writes the model back into the document (sync + version history) and re-creates companion diagram.png / diagram.pdf (vector, via svg2pdf.js + jsPDF in the browser; .svg fallback if PDF fails). - Edit again / export again works by design: the source is the model file itself. Image editor (modules/toast-image) - Raster image editing via tui-image-editor (npm-bundled), with a self-hosted icon sheet (theme 'common.bi.image' override) so nothing is fetched from uicdn.toast.com (blocked by the IDE CSP; the stock fetch crashed editor initialization). - Save uploads the edited image and refreshes the file tree. Shared wiring - settings: 'drawio' in textExtensions; createFileModes / sourceEditorComponents for the .drawio editor; new fileViewButtons module-import slot consumed by file-view-header (the Edit button, shared with toast-image); 'toast-image' + 'drawio' in moduleImportSequence. - i18n keys in locales/en.json and frontend/extracted-translations.json (kept in sync, alphabetical). - package.json: @maxgraph/core, tui-image-editor, jspdf, svg2pdf.js. Tests: vitest unit tests for the pure logic (model normalisation, companion file names, file-tree helpers, image save/CSRF flow, upload payloads) — 30 passing.
…rash Diagram editor (modules/drawio) - Rebuild the UI on the classic mxGraph grapheditor example layout (jgraph/mxgraph javascript/examples/grapheditor www): top toolbar, left shape palette (General: rectangle/ellipse/rhombus/cylinder/ triangle/cloud/actor/text; Connectors: straight/arrow/elbow/ orthogonal — click, then click start and end element), canvas with A4-landscape page sheet, grid, status bar (zoom %, cursor position). - Palette items support click-to-add and drag-onto-canvas placement; new shapes take the current stroke/fill colour and line width. - Toolbar: New (clear, undoable), Undo/Redo, zoom -/+/Fit, Select/Freehand tools, Delete, grid toggle, colour/width controls, Save. Keyboard: Delete / Ctrl+Z / Ctrl+Shift+Z / Ctrl+Y. - Shape styles mirror the grapheditor default stylesheet (white fill, black stroke, fontSize 12). Toast image editor (modules/toast-image) - Fix 'n is not a constructor' init crash: TUI must be constructed with loadButton:false and downloadButton:false (the proven CE+ working configuration); with the built-in buttons enabled the bundled build crashes at construction. Our toolbar (Save/Close) drives the UI. i18n: refresh the drawio_* key set in locales/en.json and frontend/extracted-translations.json (kept in sync).
…mage: fix real TUI init crash Diagram editor (modules/drawio -> modules/diagram): - Replace the maxGraph palette UI with the @svgedit/svgcanvas 7.4.2 canvas (MIT, zero dependencies, bundled into Overleaf's own JS; no iframe/CDN). - Native modes: select, rectangle, ellipse, line, circle, pencil (freehand); add-text, recolour (fill/stroke/width), to front/back, delete, undo/redo, zoom, clear. - Document format is now the SVG source itself (editable, round-trippable); Save writes the SVG back into the document and re-creates companion name.png and vector name.pdf (svg2pdf.js + jsPDF) for \\includegraphics. - Wiring: settings.defaults createFileModes/sourceEditorComponents -> modules/diagram; textExtensions += svg (keep drawio for legacy files); moduleImportSequence 'drawio' -> 'diagram'. - Deps: + @svgedit/svgcanvas ^7.4.2, - @maxgraph/core. - i18n: drawio_* -> diagram_* (en.json + extracted-translations, both files). - Unit tests updated for the SVG model helpers (33 pass). - Plan/evidence: references/svg-diagram-svgcanvas-plan.md. toast-image (TUI crash): - Root cause proved in tui-image-editor 3.15.3 dist: menu contained 'guide' (not in the v3 SUB_UI_COMPONENT registry -> new undefined = 'n is not a constructor'), and the code called editor.loadImage() which does not exist in v3 (public API: loadImageFromFile/FromURL only). - Restore the old working plugin's exact init: includeUI.loadImage + valid menu [crop,flip,rotate,draw,shape,icon,text,mask,filter] + initMenu 'filter' + theme + usageStatistics:false, no explicit load call. - Readiness/save gating: ready only when 'executeCommand' === 'Load' fires (ui.initializeImgUrl set) via ui.on invoker events + bounded poll; never uploads a blank canvas over an existing project file. - Retry hygiene: previous instance destroyed + container cleared.
…t/branding
Diagram editor (modules/diagram):
- Register as Overleaf visual-editor provider (visualEditorProviders) so the
canvas renders in the editor pane instead of under a full-height CodeMirror
editor; SVG opens in Visual by default, Code/Visual toggle available.
- Create the initial layer (SVG-Edit app does this): the canvas core's
mouseDown bails when #svgcontent has no <g>, so fresh canvases were
undrawable and shape tools never received events.
- Heal canvas geometry (NaN zoom/dimensions) before import, drawing and
export so saved SVGs keep valid width/height and re-imports render.
- Add Text now uses the outline (ink) colour instead of the shape fill, so
text is visible on white backgrounds; selects the new element.
- svgToPdfBlob: use the svg2pdf.js v2 API svg2pdf(element, pdf, {x,y,width,
height}) and fit the diagram on A4 preserving aspect ratio; PDF export now
produces vector PDFs (was 'svg2pdf.js is not available' before).
- Defensive capture-phase wheel-zoom on the canvas (the core's own wheel
handler assumes a zoom component that is absent in this build).
Image editor (modules/toast-image):
- fetchImpl: bind window.fetch (detached reference threw Illegal invocation).
- Keep TUI's fabric hidden textarea inside the modal so OL's focus trap no
longer swallows text-tool keystrokes.
- Add explicit Zoom -/+ and Reset (1:1) buttons driving the TUI zoom
component's real zoom() API (built-in icons only toggled wheel-zoom mode).
- Hide TUI's canvas header (brand logo + Load/Download) by structure.
i18n: add reset_zoom (zoom_in/zoom_out already existed).
…(iframe + bridge)
- Vendor svgedit 7.4.2 (dist/editor) as a same-origin static subtree at
services/web/public/static/svgedit/ (Editor.js, svgedit.css, images,
components, extensions, LICENSE); assets + runtime extension imports
resolve against /static/svgedit/.
- Host page (embed.html+css/js): full SVG-Edit UI; ext-storage excluded
(no prompt/cookie/auto-load/beforeunload), no_save_warning off,
branding neutralised (home item removed, main menu -> 'Diagram',
'Created with' comment stripped from serialised output).
- Bridge: window.__olSvgEmbed { ready, load(svg), getSvg(), onChanged }
(same-origin, debounced); parent keeps the SVG document in CodeMirror
(OL sync/history unchanged) and drives the Save (PNG + vector PDF
companions via svg2pdf.js/jsPDF).
- .svg opens in Visual mode by default (showVisualForFile: provider-
claimed files default to visual when no preference stored).
- Drop @svgedit/svgcanvas dependency (no longer imported by web code);
svgDimensions() helper + tests; i18n keys; README rewritten.
- toast-image: silence TUI __hostObserver type error (teardown of the
text-editing fix that ships in this build).
…expose TUI fabric hook for E2E SVG-Edit's context menus (se-cmenu_canvas-dialog, se-cmenu-layers) are positioned at cursor page coordinates clamped by screen.width/screen.height (the host OS screen — standalone svg-edit runs full-window). Embedded in the Overleaf editor pane that clamp is wrong: the menu lands far from the cursor or past the iframe's right/bottom edge where it is clipped and its items become unreachable. Fix in the host page (embed.js, no vendored-bundle edits): a document-level contextmenu/click listener (bubble phase) runs after the bundle has written its inline top/left; it then re-anchors each visible menu at the cursor, pinning flush to the iframe viewport edge when it would overflow. toast-image: expose window.__olTuiFabric() so E2E can verify keystroke capture on the real fabric canvas (read-only debug hook, removable).
TUI inverts the selected tool item to a white background
(.tui-image-editor-item.active { background-color:#fff }); the theme had
set menu.activeIcon.color to #fff, so the active icon was white on white
and invisible.
- theme: menu.activeIcon.color #fff -> #1e1e1e (matches TUI's own intent:
standard theme uses a dark active icon on the white active background)
- CSS belt in toast-image-editor.css: force dark icon (svg use fill/stroke)
and dark label on active menu items, so a future theme regression cannot
hide the selection again
…r-fs-access) Every extension module statically imports ../_virtual/_vite/preload-helper.js which was absent from the vendored tree, so all extensions except self-contained ext-markers 404'd on load and the panning/shapes/ polygon-star/eyedropper/connector/layer-view/grid tools were missing. ext-opensave additionally needed its node_modules browser-fs-access bundle. Both files restored byte-identical from the official 7.4.2 dist; verified via headless A/B test that the full reference toolbar set now loads.
…tch reference ext-eyedropper inserts at a fixed left-bar index while ext-connector appends, so their relative order is an async race. The reference build shows connect-then-eyedropper; the rebrand step now enforces that order after the extensions land.
…olygon/connect/eyedropper) The extension inits resolve asynchronously and each inserts by fixed index or append, so the tail order is a race. Broaden the rebrand normalisation from the last two buttons to the whole contested tail (tool_text < tool_shapelib < tool_image < tools_polygon < tool_connect < tool_eyedropper); verified 14/14 identical to svgedit.netlify.app.
…te inserts An extension can insert (or re-insert by fixed index) its button after a rebrand run and undo the normalisation, leaving the tail order to the race. A MutationObserver on the left bar's direct children re-runs the normalisation 120ms after the last change; the pass is a no-op once the order is stable, so it converges. 3/3 boot runs now match the reference sequence exactly.
- prune the opensave menu's disk-backed ops (New/Open/Save/Save as): New would sync a cleared canvas into the document, Open would overwrite it with a local file, Save/Save-as are browser downloads that masquerade as project saves. Import (data-URI/inline SVG embed) and drag-and-drop are kept. - the removed items' bare N/S shortcuts keep firing through document-level listeners that survive detachment, so a capture-phase keydown blocker replaces them (verified: no dialogs, no orphan errors, T still works). - a MutationObserver on the editor menu re-prunes if ext-opensave attaches its items after the last rebrand timestamp. - replace the core Image tool (typed URL/path prompt -> broken <image href> references) with a local-file-dialog tool (user decision, option a): SVG inlines, raster embeds as data URI — portable content in the project document.
…erbox) The companion PDF was always A4 with the diagram fitted inside, which changed the apparent size of the document (a 400x300 px diagram came out on a 595x842 pt page) and distorted \\includegraphics sizing. Now the page is exactly the SVG's own size (px -> pt at 96 dpi, zero offset), with the A4 margin-fit kept only as a fallback for SVGs without usable dimensions. Pure helpers (pxToPt, pdfPageSize, fitContent) extracted and unit-tested (31/31 module tests green).
diagram: record the Overleaf-owned-document hardening (pruned file ops, key blocker, image-tool file-dialog decision with option b as a future note, left-bar order normalisation + verification against the live reference) and the new PDF page-size rule. toast-image: document the TUI text-input focus-trap fix (fabric hidden-textarea container + reparenter), the visible selected-tool button theme fix, and the __olTuiFabric debug hook.
…t 96-dpi px) The live canvas of the A4-shaped test diagram is 722.75x510.73 units and the user expected the companion PDF at that document size; the px->pt (0.75) mapping shrank it to 542x383 (about 60% of the A4 reference). SVG-Edit's coordinate space is points (standard A4 canvas = 842x595), so the page now maps 1 user unit to 1 pt and the 96-dpi conversion is gone. 31/31 module tests green.
…pages otherwise) jsPDF 4.x defaults to portrait and normalises a custom [width, height] pair into portrait — a 722.75x510.73 (landscape) canvas came out as a 510.73x722.75 page, i.e. the PDF was transposed relative to the diagram. The export now sets orientation explicitly (w>=h → landscape, else portrait); both branches verified against jsPDF 4.2.1 behaviour, with a regression test pinning the swap quirk. 32/32 module tests green.
…target size) Bug hunt: the PNG was created at 2× canvas size but the SVG raster was drawn at its natural 1× size (ctx.drawImage(img, 0, 0)), leaving the diagram in the top-left quadrant with empty space around it — while the PDF companion renders the same content 1:1 (verified: canvas content bbox 17.6..639.5 x -5..250.4 units = 622x255.6; PDF shows it at 14..640 x 0..252 pt = 87%x49% fill; the PNG showed only 43%x25%). Drawing with an explicit destination (img, 0, 0, width, height) makes the PNG 1:1 the diagram canvas — no additional space around it, same content placement as the PDF.
…er on the canvas The wired Image-tool file-dialog handler called finish(100,100) on 'error', adding a 100x100 <image> with no href. Now the error path reports the failure without mutating the canvas.
The core add path (addSVGElementsFromJson) appends the element but never touches the undo stack, so a PNG imported via the Image tool stayed on the canvas through Undo/Redo. Register the finished element in the editor's own history (BatchCommand + InsertElementCommand + addCommandToHistory) — the same mechanism the core SVG-file import (importSvgString) uses — and notify the changed listeners like the core flow, so the parent document stays in sync. Best-effort: if the API shape ever changes, the import still lands.
This was referenced Aug 27, 2026
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Integration of image modification options
https://ui.toast.com/tui-image-editor
and vector graphics options
https://github.com/SVG-Edit/svgedit/tree/master
into overleaf
TUI:

SVG-Edit:
Related issues / Pull Requests
Contributor Agreement