Skip to content

[pull] develop from marktext:develop#116

Merged
pull[bot] merged 5 commits into
code:developfrom
marktext:develop
Jul 5, 2026
Merged

[pull] develop from marktext:develop#116
pull[bot] merged 5 commits into
code:developfrom
marktext:develop

Conversation

@pull

@pull pull Bot commented Jul 5, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

Jocs and others added 5 commits July 5, 2026 11:45
…shields.io badges) (#4857)

* fix(muya): load extensionless remote images sent with a charset (#3837)

checkImageContentType() gates extensionless remote images (shields.io
badges, dynamic CDN images) on a HEAD request's Content-Type, but matched
it with `/^image\/(?:jpeg|png|gif|svg\+xml|webp)$/` — an exact full-string
match. shields.io serves `image/svg+xml;charset=utf-8`, whose `;charset`
parameter fails the anchored regex, so the badge is treated as "not an
image" and never rendered.

Strip any Content-Type parameter before matching so the MIME type alone is
tested.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(muya): load remote images when the content-type HEAD is CSP-blocked (#3837)

The charset fix alone wasn't enough: for an extensionless remote image
(shields.io badge) checkImageContentType does a HEAD fetch, but the renderer
CSP has no connect-src and falls back to default-src 'self', so the
cross-origin HEAD is refused and the fetch throws. It then returned false,
so loadImage rejected the image as "not an image" and the badge never
rendered — even though img-src is permissive enough to load it.

Have checkImageContentType return null ("couldn't determine") on a blocked/
failed HEAD or a non-200/typeless response, and only reject in loadImage on a
positive non-image type. A null now falls through to the actual <img> load,
which img-src permits. Verified in a real Electron build: the shields.io
badge renders (naturalWidth > 0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(muya): skip the content-type HEAD for cross-origin images (#3837)

The HEAD content-type probe can never work from the renderer for a
cross-origin URL: CORS strips the response headers, and the app CSP (no
connect-src) refuses the request outright — so it only ever fired a
guaranteed-to-fail fetch that logged a CSP console error, then fell through
to the <img> load anyway. Since the extensionless remote URLs this runs on
are always cross-origin, the probe was effectively dead weight there.

Skip the HEAD unless the URL is same-origin (the only case whose Content-Type
the renderer can actually read). Cross-origin images load via the permissive
img-src exactly as before, now without the wasted request or console noise;
the probe still runs for same-origin URLs. Keeps everything in muya — no
main-process/IPC dependency. Verified in a real Electron build: the shields.io
badge renders with zero CSP console errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(muya): open the link when modifier-clicking a linked image (#3835)

A linked image `[![alt](src)](href)` has two independent click listeners
on the editor node: ImageSelection (which, on Ctrl/Cmd-click of an image,
emits a 'image' format-click that pops the image viewer) and
linkMouseEvents (which emits a 'link' format-click that opens the URL).
Both fire on the same modifier-click, so the image viewer opened on top of
the navigation and the link appeared not to work.

Skip ImageSelection's image-preview emission when the image lives inside a
link (`span.mu-link`) on a modifier-click, letting the link handler open
the URL. Plain (non-linked) images still preview as before.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(muya): reuse shared LINK_SELECTOR for the linked-image guard (#3835)

Follow-up cleanup: the linked-image preview guard hand-rolled
`.closest('.mu-link')`, which only matches plain markdown links and misses
reference links, autolinks, and raw-HTML anchors that can also wrap an
image. Export linkMouseEvents' LINK_SELECTOR (the exact set the link
handler treats as navigable) and reuse it, so the guard suppresses the
image preview for every link variant the link handler will act on. Also
only walk the DOM on a modifier-click.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… raw-HTML linked images (#4865) (#4866)

* fix(muya): tokenize reference links whose text is an image (#4865)

A full reference link whose text is an image — `[![alt](img)][ref]`, the
standard README-badge pattern — fragmented into a bare image plus a separate
empty reference link, so the image was never nested inside the link. The
reference_link anchor group used `[^\]]+?`, which stops at the image's inner
`]`. Mirror the inline `link` rule's nesting-capable anchor group so the
image is captured as the link's child.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(muya): render image hover icons as spans so linked images survive inside anchors (#4865)

An inline image's hover controls (success/fail/close icons) were `<a>` elements
carrying no href. When an image sits inside a real anchor — a reference-linked
image `[![alt](img)][ref]` (a.mu-reference-link) or a raw-HTML linked image
`<a href><img></a>` (a.mu-raw-html) — the nested `<a>`-inside-`<a>` is invalid
HTML, so the parser closes the outer anchor early and hoists the image out of
the link. The image then renders outside the anchor: it is not recognised as a
linked image, so Ctrl/Cmd-click pops the image preview instead of following the
link.

Render the icons as `<span>` (they are decorative controls, not links) and drop
the `a` qualifier from their CSS selectors. The image now stays inside the
anchor, so the #3835 modifier-click guard applies and the link is followed.

The raw-HTML regression test runs under jsdom, not happy-dom: happy-dom's
DOMPurify strips the `<a>` (rendering it as a span, masking the nesting), while
jsdom keeps it, matching the real Electron app.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…3791) (#4860)

The outline/TOC panel keyed el-tree nodes by the heading's `slug` — a
per-render object id (getTOC's stableSlug, a WeakMap keyed by the live
block object). Switching tabs calls Editor.setContent -> ScrollPage
rebuilds every heading as a brand-new object, so each heading gets a fresh
slug, the collapsedKeys lookup misses, and the outline re-expands
everything on return. In-place edits (the #3028 case) kept working only
because they mutate the existing block objects.

Key the tree by the content-derived `githubSlug` instead (already computed
by getTOC), which is stable across both content edits and document
reloads/tab switches; `slug` is still carried for the click-to-scroll
anchor. The keying logic is extracted into a pure `deriveKeyedToc` helper so
it can be unit-tested, and listToTree now threads `githubSlug` through.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ke) (#4859)

* fix(desktop): flush pending edits before saving so the last keystroke isn't lost (#3803)

The engine commits edits to the store's currentFile.markdown on a deferred
requestAnimationFrame (json-change). FILE_SAVE / FILE_SAVE_AS snapshotted
currentFile.markdown and shipped it to the main process without flushing
first, so a keystroke typed in the same frame as Cmd+S (or Save As) — a
window that widens under main-thread load on large docs — was dropped from
the written file, with no dirty indicator. Tab switching already guards
against this by emitting 'flush-active-editor' (the #2938 fix), but the save
paths did not.

Emit 'flush-active-editor' at the start of FILE_SAVE and FILE_SAVE_AS; the
editor flushes the queued op into currentFile synchronously before its
markdown is read.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(desktop): extract flushActiveEditor() and cover all persist paths (#3803)

Follow-up cleanup: the flush-before-read was a copy-pasted bus.emit at two
call sites (and a third pre-existing copy in UPDATE_CURRENT_FILE). Extract a
single flushActiveEditor() action and route all of them through it. Also
apply it to MOVE_FILE_TO and RESPONSE_FOR_RENAME, which read
currentFile.markdown and persist it via the exact same pattern as FILE_SAVE
— so "move to" / renaming an untitled file no longer drops a same-frame
keystroke either.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(desktop): assert the flushed markdown reaches the save payload (#3803 review)

The prior spec only asserted flush-emit < IPC-send ordering, but the #3803 bug
lives at the `const { …, markdown } = this.currentFile` read that sits between
them — a flush moved after that read would still pass. Wire a real
`flush-active-editor` listener that commits the pending keystroke (mirroring
editor.vue) and assert the SENT PAYLOAD carries it, so a regression past the
read boundary is caught. Also: cover RESPONSE_FOR_RENAME (both the untitled
payload path and the existing-file rename-emit path), filter the exact IPC
channel in the MOVE_FILE_TO case, and anchor ordering by event name instead of
the fragile invocationCallOrder[0].

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@pull pull Bot locked and limited conversation to collaborators Jul 5, 2026
@pull pull Bot added the ⤵️ pull label Jul 5, 2026
@pull pull Bot merged commit c907b29 into code:develop Jul 5, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant