diff --git a/package.json b/package.json index 6f5400d484e..4c3f301b25a 100644 --- a/package.json +++ b/package.json @@ -177,6 +177,7 @@ "lint-staged": "^16.4.0", "lowlight": "^3.3.0", "lucide-react": "^0.577.0", + "marked": "^17.0.1", "mermaid": "^11.15.0", "monaco-editor": "^0.55.1", "oxfmt": "^0.52.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b48ad9b4f29..076f5d41808 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -263,6 +263,9 @@ importers: lucide-react: specifier: ^0.577.0 version: 0.577.0(react@19.2.5) + marked: + specifier: ^17.0.1 + version: 17.0.6 mermaid: specifier: ^11.15.0 version: 11.15.0 @@ -3457,6 +3460,7 @@ packages: boolean@3.2.0: resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. brace-expansion@1.1.14: resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} @@ -4446,6 +4450,7 @@ packages: glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me global-agent@3.0.0: resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} @@ -4642,6 +4647,7 @@ packages: inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -4967,6 +4973,7 @@ packages: lodash.isequal@4.5.0: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. lodash@4.18.1: resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} @@ -5330,6 +5337,7 @@ packages: node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead node-fetch@3.3.2: resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} diff --git a/src/renderer/src/components/editor/MarkdownPreview.tsx b/src/renderer/src/components/editor/MarkdownPreview.tsx index 55282b91b5f..38d146c1312 100644 --- a/src/renderer/src/components/editor/MarkdownPreview.tsx +++ b/src/renderer/src/components/editor/MarkdownPreview.tsx @@ -1390,9 +1390,6 @@ export default function MarkdownPreview({ if (target.protocol !== 'file:') { return } - if (sourceOwner.kind === 'unknown') { - return - } const classified = resolveMarkdownLinkTarget(href, filePath, worktreeRoot) const classifiedFileTarget = @@ -1406,11 +1403,19 @@ export default function MarkdownPreview({ ? { line: classifiedFileTarget.line, column: classifiedFileTarget.column } : parseLineTarget(target.hash) + // Why: same-file anchors need no ownership/filesystem resolution (e.g. + // `./README.md#heading` when this file is README.md). Run before the + // unknown-ownership guard so ambiguous folder-workspace ownership still + // scrolls within the open document. if (absolutePath === filePath && target.hash && !lineTarget) { void scrollToAnchor(target.hash.slice(1)) return } + if (sourceOwner.kind === 'unknown') { + return + } + const targetWorktree = findMarkdownPreviewTargetWorktree( worktreesByRepo, absolutePath, diff --git a/src/renderer/src/components/editor/RichMarkdownEditor.tsx b/src/renderer/src/components/editor/RichMarkdownEditor.tsx index ae7a346c0af..1c0cb2734d2 100644 --- a/src/renderer/src/components/editor/RichMarkdownEditor.tsx +++ b/src/renderer/src/components/editor/RichMarkdownEditor.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import type { Editor } from '@tiptap/react' +import { useEditorState, type Editor } from '@tiptap/react' import type { DiffComment, MarkdownDocument } from '../../../../shared/types' import { useAppStore } from '@/store' import { selectWorktreeDiffComments } from '@/store/worktree-diff-comments-selector' @@ -248,10 +248,13 @@ export default function RichMarkdownEditor({ setSlashMenu: menu.setSlashMenu, setDocLinkMenu: menu.setDocLinkMenu }) - const selectedCitationStatus = getSelectedHtmlSuperscriptLinkStatus( + // Why: useEditor defaults shouldRerenderOnTransaction to false, so selection-only + // citation NodeSelections would leave aria status stale without useEditorState. + const selectedCitationStatus = useEditorState({ editor, - htmlSuperscriptLinkContext - ) + selector: (snapshot) => + getSelectedHtmlSuperscriptLinkStatus(snapshot.editor, htmlSuperscriptLinkContext) + }) useRichMarkdownSpellcheckAttribute(editor, richMarkdownSpellcheckEnabled) // Why: use useLayoutEffect (synchronous cleanup) so the pending serialization diff --git a/src/renderer/src/components/editor/html-character-reference-decoder.ts b/src/renderer/src/components/editor/html-character-reference-decoder.ts index 14d5ccb796a..e8bd9d1689d 100644 --- a/src/renderer/src/components/editor/html-character-reference-decoder.ts +++ b/src/renderer/src/components/editor/html-character-reference-decoder.ts @@ -1,6 +1,8 @@ export type HtmlAttributeQuote = '"' | "'" | null export function decodeHtmlTextCharacterReferences(value: string): string { + // Why: a