Repository navigation
feat(desktop): consume @muyajs/core in util files (markdownToHtml/pdf/dompurify/printService/sourceCode/icon) - #4402
Conversation
Begin migrating the desktop renderer off the legacy `@marktext/muyajs` engine and onto the TypeScript rewrite published as `@muyajs/core` (packages/muya). This first step only adds the dependency and the type plumbing; the `muya` alias (→ packages/muyajs) is left intact so editor.vue keeps working while the migration proceeds file by file. - Add `@muyajs/core: workspace:*` to packages/desktop dependencies. - `@muyajs/core` ships no built `lib/types`; its package `exports` map points `.` at `./src/index.ts`, so vue-tsc would otherwise descend into the entire muya source tree and report errors that only exist because muya's own `src/types/global.d.ts` globals aren't loaded under the desktop program. Add a hand-written `src/types/muya-core.d.ts` declaring just the surface the desktop consumes, and a `paths` entry redirecting type resolution to it. Runtime resolution still goes through the package `exports` map via Vite/electron-vite (verified by build:unpack and a vitest import smoke test). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Swap the directly-equivalent muyajs imports for their `@muyajs/core`
counterparts:
- util/markdownToHtml.ts: `new ExportHtml(md).renderHtml()` →
`new MarkdownToHtml(md).renderHtml()`. The new `renderHtml()` already
wraps its output in `<article class="markdown-body">…</article>`
(muyajs did not), so the desktop no longer adds the wrapper — output
is byte-identical.
- util/pdf.ts: `escapeHTML`/`unescapeHTML` now come from `@muyajs/core`
(identical implementations). The `Slugger` import is intentionally
left on muyajs: the TOC anchors built here must match the heading
`id`s emitted by the muyajs export renderer (`Muya#exportStyledHTML`,
still used by editor.vue), which use the SAME Slugger. Swapping to
@muyajs/core's `generateGithubSlug` (a different algorithm, no
dedup/unicode downcoding) would break in-document TOC links; it
migrates together with the editor.vue swap.
- components/editorWithTabs/sourceCode.vue: `wordCount` now comes from
`@muyajs/core`. Same `{ word, paragraph, character, all }` return
shape; the token-split regex differs only cosmetically (`/\s+/` vs
`/[\s\n]+/`, equivalent since `\s` covers `\n`).
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Replace the `muya/lib/utils/dompurify` import (whose default export was simply `DOMPurify.sanitize`) with `dompurify` directly — already a direct desktop dependency. `@muyajs/core`'s exported `sanitize(html, opts, disableHtml)` has a different signature (it escapes HTML internally), whereas the desktop needs the RAW sanitizer that does not escape (pdf.ts escapes/unescapes around it). Behavior is identical to the legacy path. Cast through `unknown` to bridge DOMPurify's `string | TrustedHTML` overload union; both configs set `RETURN_TRUSTED_TYPE: false`, so the result is always a string. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Drop the `muya/lib/ui/fileIcons` import in favor of the underlying `@marktext/file-icons` package (already a direct desktop dependency). The muyajs wrapper only added a `getClassByName(name)` helper around the package's `matchName(name)?.getClass(0, false)`; inline that helper in the component and import the package's CSS (`build/index.css`) directly, as the wrapper did. Add an ambient module declaration for `@marktext/file-icons` (it ships no types) to shims.d.ts. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
services/printService.ts calls `getImageInfo(rawSrc: string).src` to
normalise an <img>'s src attribute into a displayable URL before
printing/PDF export (GH#678). `@muyajs/core` has no behaviour-equivalent
string helper for this:
- its `getImageInfo(image: HTMLElement)` takes a DOM element, not a
string;
- its `getImageSrc(src: string)` double-prefixes already-resolved
`file://` URLs (`file://file://…`) and blanks `data:` URLs — and the
muyajs export renderer already emits absolute `file://` srcs into the
HTML this consumes, so it would regress every image.
Keep the muyajs import for now and document why; it migrates once the
export render path (editor.vue / Muya#exportStyledHTML) moves to
@muyajs/core. No behavioral change.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
Website preview ready: https://pr-4402-marktext-website.ransixi.workers.dev Built from |
There was a problem hiding this comment.
Pull request overview
This PR is an incremental step in migrating the desktop renderer’s non-editor utility/component code from the legacy @marktext/muyajs engine to the TypeScript rewrite published as @muyajs/core, while explicitly keeping legacy-only helpers on muyajs to avoid behavior regressions during the staged migration.
Changes:
- Add
@muyajs/coreas apackages/desktopdependency and introduce a tsconfigpathsredirect to a minimal hand-writtenmuya-core.d.tssurface to keepvue-tscfrom typechecking the full muya source tree. - Switch desktop utilities/components to consume
MarkdownToHtml,escapeHTML/unescapeHTML, andwordCountfrom@muyajs/core, while deliberately retaining legacySluggerandgetImageInfoimports onmuyajs(documented inline) to preserve export/TOC/image behavior. - Vendor
dompurifydirectly (matching the legacy wrapper behavior) and consume@marktext/file-iconsdirectly in the sidebar icon component.
Reviewed changes
Copilot reviewed 8 out of 11 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-lock.yaml | Adds workspace link for @muyajs/core under the desktop importer. |
| packages/desktop/package.json | Adds @muyajs/core: workspace:* dependency. |
| packages/desktop/tsconfig.base.json | Redirects @muyajs/core type resolution to a local d.ts surface. |
| packages/desktop/src/types/shims.d.ts | Adds an ambient module declaration for @marktext/file-icons (package ships no types). |
| packages/desktop/src/types/muya-core.d.ts | Adds a minimal declaration surface for the subset of @muyajs/core used by desktop. |
| packages/desktop/src/renderer/src/util/pdf.ts | Uses escapeHTML/unescapeHTML from @muyajs/core while intentionally keeping legacy Slugger for TOC anchor compatibility. |
| packages/desktop/src/renderer/src/util/markdownToHtml.ts | Switches to @muyajs/core’s MarkdownToHtml and removes the now-redundant outer <article> wrapper. |
| packages/desktop/src/renderer/src/util/dompurify.ts | Replaces legacy wrapper import with direct dompurify usage while preserving raw-sanitize semantics. |
| packages/desktop/src/renderer/src/services/printService.ts | Documents why getImageInfo remains on legacy muyajs for now. |
| packages/desktop/src/renderer/src/components/sideBar/icon.vue | Switches from the legacy wrapper to direct @marktext/file-icons usage and inlines the wrapper helper logic. |
| packages/desktop/src/renderer/src/components/editorWithTabs/sourceCode.vue | Switches wordCount import to @muyajs/core. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Build artifacts for PR #4402: Run: https://github.com/marktext/marktext/actions/runs/27127729045
|
Export the TableChessboard UI plugin from the muya core entrypoint so the desktop editor host can register it (the quick-insert table picker). The @muyajs/core workspace dependency and the tsconfig.base.json @muyajs/core paths entry it consumes already landed on develop via #4402. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* build(muya): export TableChessboard from @muyajs/core entrypoint Export the TableChessboard UI plugin from the muya core entrypoint so the desktop editor host can register it (the quick-insert table picker). The @muyajs/core workspace dependency and the tsconfig.base.json @muyajs/core paths entry it consumes already landed on develop via #4402. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * feat(desktop): exportStyledHTML wrapper + @muyajs/core types shim @muyajs/core exposes MarkdownToHtml(md, muya).generate() but has no equivalent of the legacy muyajs exportStyledHTML (which injects a TOC at the [TOC] marker and wraps the article in a header/footer page table for PDF/print). Add a desktop-side exportStyledHTML wrapper that reproduces that behaviour on top of the engine output so export stays equivalent. Add a hand-written @muyajs/core declaration shim (referenced by the tsconfig paths entry) so vue-tsc resolves the import at the package boundary instead of type-checking the entire muya source tree under the desktop program. Delete it once @muyajs/core ships built type declarations. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * feat(desktop): migrate editor.vue to the @muyajs/core engine Rewire the editor host onto @muyajs/core: - Construction: build the engine with markRaw(new Muya(el, options)) + explicit init(); register the new UI plugin set (TableChessboard, ParagraphQuickInsertMenu, CodeBlockLanguageSelector, EmojiSelector, ImageEditTool, ImageResizeBar/ToolBar, InlineFormatToolbar, ParagraphFront*, PreviewToolBar, LinkTools, FootnoteTool, Table*). - Options/i18n: map the desktop language preference to the engine's bundled locale objects and pass locale on construct + on language-changed (editor.locale(...)). - Events: derive the content-change payload (markdown/wordCount/cursor/ history/TOC/blocks) from the engine's low-level json-change; adapt the new selection-change shape for the application-menu state builder and drive format state off the formats it now carries; listen for scroll on the container directly (engine has no scroll event). - Methods: setContent (replacing setMarkdown), getScrollContainer via domNode, viewport-relative caret coords for typewriter/keep-visible scrolling, copy/paste method remap (copyAsRich -> copyAsHtml), per-tab engine-history stash for in-session tab switches with a synthetic desktop-shaped history for save/dirty tracking. - Search circular-ref fix: muya's search/replace/find return the live Search instance (circular: Search -> muya -> ... -> ScrollPage) and each match carries a live block ref. The store deep-clones (JSON) its payload, which crashed; toSearchMatches() strips it to the plain { index, matches, value } the search UI needs before dispatching. - Blur the editor when the command palette opens to avoid a focus-trap vs engine-selection deadlock that froze the renderer. Documented gaps (engine does not yet surface these; editor stays fully functional): heading-copy-link hover affordance, selection affiliation chain for block-context menu toggles, and selectedTableCells in select-all. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * test(desktop): update e2e selectors + recipes for @muyajs/core mu- DOM Migrate the desktop e2e suite to the @muyajs/core renderer: - Swap ag-* DOM selectors for the engine's mu-* equivalents across the fixture-render, crash, issue-regression, paragraph-blocks and strong-cjk specs, and update helpers.ts (paragraph content lives in span.mu-paragraph-content; selection is committed via a synthetic keyup so the engine updates its active block). - crash-update-paragraph #2099: the quick-insert menu now triggers on '/' (not the legacy '@'); type '/', wait for the float to be shown (it is always attached but parked off-screen until positioned), then pick Header 1. - plantuml: the engine encodes via plantuml-encoder (no '~1' deflate prefix), so assert the bare plantuml-alphabet base64 src. - strong-cjk: mark the CJK + **"x"** flanking case as fixme — a documented @muyajs/core parser gap (#4307, tracked as it.fails in packages/muya/src/state/__tests__/strongCjkFlanking.spec.ts); the CJK + **plain** regression still passes. Full desktop e2e: 87 passed, 3 skipped (all documented gaps), 0 failed. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Summary
First step of migrating the desktop renderer off the legacy
@marktext/muyajsengine and onto the TypeScript rewrite published as@muyajs/core(packages/muya).This PR only touches the non-
editor.vueutility/component files.editor.vue(the large engine swap) is handled separately, so themuyaalias (→packages/muyajs) is left fully intact and the legacy engine still drives the editor during the migration.Wiring
@muyajs/core: workspace:*topackages/desktopdependencies.@muyajs/coreships no builtlib/types; itsexportsmap points.at./src/index.ts. Letting vue-tsc resolve to that source pulls the entire muya tree into the desktop program, where muya's ownsrc/types/global.d.tsglobals (e.g.Element.__MUYA_BLOCK__) aren't loaded → spurious errors. Added a hand-writtensrc/types/muya-core.d.ts(declaring only the surface the desktop consumes) plus apathsentry redirecting type resolution to it. Runtime resolution still flows through the packageexportsmap via Vite/electron-vite (verified bybuild:unpack+ a vitest import smoke test).Per-file old → new mapping
util/markdownToHtml.tsnew ExportHtml(md).renderHtml()new MarkdownToHtml(md).renderHtml()from@muyajs/coreMarkdownToHtml#renderHtmlnow wraps in<article class="markdown-body">…</article>itself, so the desktop no longer adds the wrapper — output is identical (no double-wrap).util/pdf.tsescapeHTML/unescapeHTMLfrommuya/lib/utils@muyajs/coreutil/pdf.tsSluggerfrommuya/lib/parser/marked/sluggerutil/dompurify.tsrunSanitizefrommuya/lib/utils/dompurifyDOMPurify.sanitize(vendordompurifydirectly)DOMPurify.sanitize.@muyajs/core'ssanitize(html, opts, disableHtml)escapes HTML internally — wrong for the desktop's raw-sanitize use (pdf.ts escapes/unescapes around it). Behavior preserved.services/printService.tsgetImageInfofrommuya/lib/utilscomponents/editorWithTabs/sourceCode.vuewordCountfrommuya/lib/utils@muyajs/core{ word, paragraph, character, all }shape; split regex differs only cosmetically (/\s+/vs/[\s\n]+/).components/sideBar/icon.vuefileIconsfrommuya/lib/ui/fileIcons@marktext/file-iconsdirectly (+build/index.css)getClassByNamearoundmatchName(name)?.getClass(0, false); inlined in the component. Ambient decl added toshims.d.ts(package ships no types).Signature adaptations / documented blockers
Two functions have no behaviour-equivalent in
@muyajs/coreand were deliberately left on muyajs (each with an inline comment) to avoid regressions. They migrate together with theeditor.vue/Muya#exportStyledHTMLswap:pdf.tsSlugger—getHtmlTocbuilds TOC anchors (#${slugger.slug(content)}) that must match the headingids emitted by the muyajs export renderer (Muya#exportStyledHTML, still used byeditor.vue), which use the sameSlugger.@muyajs/coreonly offersgenerateGithubSlug— a different algorithm (no dedup, no unicode downcoding), which would break in-document TOC links while the export path is still muyajs.printService.tsgetImageInfo(rawSrc: string)— normalises an<img>src string into a displayable URL (GH#678).@muyajs/core'sgetImageInfo(image: HTMLElement)takes a DOM element (different), andgetImageSrc(src: string)double-prefixes already-resolvedfile://URLs (file://file://…) and blanksdata:URLs — and the muyajs export renderer already emits absolutefile://srcs here, so it would regress every image.Verification
pnpm -C packages/desktop run typecheck— clean ✅pnpm run lint— 0 errors (77 pre-existing warnings, identical todevelop) ✅pnpm run build:unpack— builds successfully ✅pnpm -C packages/desktop exec vitest run— 560/560 pass ✅ (plus a throwaway smoke test confirming@muyajs/coreresolves & returns correct values in vitest)🤖 Generated with Claude Code