Skip to content

Commit 000ad6f

Browse files
committed
fix(rich-editor): address review feedback on modal field
- RichMarkdownField reports the original value when the doc matches its canonical form, so a non-canonical input never reads as a false unsaved change (skill + version description modals) - Add sim: mention link navigation (Cmd/Ctrl-click) to the modal field - versions: keep the v{n} fallback as the rename guard/seed so re-submitting the displayed token is a no-op (no redundant "v3 · v3"); document the clear-name no-op - Clarify the lazy query-gating comment in useMarkdownMentions
1 parent ae8c0b5 commit 000ad6f

3 files changed

Lines changed: 32 additions & 6 deletions

File tree

apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/use-markdown-mentions.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ export function useMarkdownMentions(
2020
options: { enabled: boolean }
2121
): MentionItem[] {
2222
const active = options.enabled && Boolean(workspaceId)
23-
// Pass through only when active; each hook self-gates on a falsy workspaceId.
23+
// When inactive, `ws` is undefined and `wsStr` is '' so every query stays disabled until the first
24+
// `@`: the hooks that expose an `enabled` option get it explicitly; the rest (which take no options)
25+
// self-gate internally on the falsy workspaceId — both empty string and undefined read as disabled.
2426
const ws = active ? workspaceId : undefined
2527
const wsStr = ws ?? ''
2628

apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/rich-markdown-field.tsx

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useEffect, useRef, useState } from 'react'
44
import type { JSONContent } from '@tiptap/core'
55
import { EditorContent, useEditor } from '@tiptap/react'
6+
import { useRouter } from 'next/navigation'
67
import { chipFieldSurfaceClass } from '@/components/emcn'
78
import { cn } from '@/lib/core/utils/cn'
89
import { createMarkdownEditorExtensions } from './extensions'
@@ -12,9 +13,10 @@ import {
1213
splitFrontmatter,
1314
} from './markdown-fidelity'
1415
import { parseMarkdownToDoc } from './markdown-parse'
15-
import { useEditorMentions } from './mention'
16+
import { parseSimHref, simLinkPath, useEditorMentions } from './mention'
1617
import { EditorBubbleMenu } from './menus/bubble-menu'
1718
import { LinkHoverCard } from './menus/link-hover-card'
19+
import { normalizeMarkdownContent } from './normalize-content'
1820
import '@/components/emcn/components/code/code.css'
1921
import './rich-markdown-editor.css'
2022

@@ -64,6 +66,9 @@ export function RichMarkdownField({
6466
onPasteText,
6567
}: RichMarkdownFieldProps) {
6668
const containerRef = useRef<HTMLDivElement>(null)
69+
const router = useRouter()
70+
const routerRef = useRef(router)
71+
routerRef.current = router
6772

6873
// Frontmatter is held out-of-band and re-attached on serialize, exactly like the file editor.
6974
// Split once at mount — the refs and the seed doc all derive from the initial value.
@@ -76,6 +81,12 @@ export function RichMarkdownField({
7681
const onPasteTextRef = useRef(onPasteText)
7782
onPasteTextRef.current = onPasteText
7883

84+
// The original value verbatim, plus its canonical serialization. The editor only ever emits canonical
85+
// markdown, so an already-non-canonical input would re-serialize on mount and read as an unsaved edit;
86+
// reporting the original when the doc matches its canonical form keeps the field clean until a real edit.
87+
const initialValueRef = useRef(value)
88+
const [canonicalSeed] = useState(() => normalizeMarkdownContent(value))
89+
7990
// TipTap extensions are stateful — build them once per mount so each field gets its own placeholder.
8091
const [extensions] = useState(() => createMarkdownEditorExtensions({ placeholder }))
8192
const [initialContent] = useState<JSONContent>(() => parseMarkdownToDoc(initialSplit.body))
@@ -96,11 +107,23 @@ export function RichMarkdownField({
96107
if (!text) return false
97108
return handler(text)
98109
},
110+
handleClick: (view, _pos, event) => {
111+
// Cmd/Ctrl-click an `@`-mention link to navigate to the resource (a plain click places the caret).
112+
const href = (event.target as HTMLElement | null)?.closest('a')?.getAttribute('href')
113+
if (!href?.startsWith('sim:') || !workspaceId) return false
114+
if (view.editable && !(event.metaKey || event.ctrlKey)) return false
115+
const parsed = parseSimHref(href)
116+
const path = parsed && simLinkPath(workspaceId, parsed.kind, parsed.id)
117+
if (!path) return false
118+
routerRef.current.push(path)
119+
return true
120+
},
99121
},
100122
onUpdate: ({ editor }) => {
101123
const md = postProcessSerializedMarkdown(editor.getMarkdown())
102124
lastSyncedBodyRef.current = md
103-
onChangeRef.current(applyFrontmatter(frontmatterRef.current, md))
125+
const serialized = applyFrontmatter(frontmatterRef.current, md)
126+
onChangeRef.current(serialized === canonicalSeed ? initialValueRef.current : serialized)
104127
},
105128
})
106129

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/general/components/versions.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,18 +73,20 @@ export function Versions({
7373
const handleStartRename = (version: number, currentName: string | null | undefined) => {
7474
setOpenDropdown(null)
7575
setEditingVersion(version)
76-
setEditValue(currentName ?? '')
76+
setEditValue(currentName || `v${version}`)
7777
}
7878

7979
const handleSaveRename = (version: number) => {
8080
if (renameMutation.isPending) return
81+
// Clearing the name is a no-op — the version number is always the canonical reference.
8182
if (!workflowId || !editValue.trim()) {
8283
setEditingVersion(null)
8384
return
8485
}
8586

8687
const currentVersion = versions.find((v) => v.version === version)
87-
const currentName = currentVersion?.name ?? ''
88+
// Compare against the `v{n}` fallback so re-submitting the displayed token saves no redundant name.
89+
const currentName = currentVersion?.name || `v${version}`
8890

8991
if (editValue.trim() === currentName) {
9092
setEditingVersion(null)
@@ -251,7 +253,6 @@ export function Versions({
251253
}}
252254
onClick={(e) => e.stopPropagation()}
253255
onBlur={() => handleSaveRename(v.version)}
254-
placeholder={`v${v.version}`}
255256
className={cn(
256257
'h-auto w-full border-0 bg-transparent p-0 font-medium text-[var(--text-primary)] text-caption leading-5 shadow-none outline-none focus:outline-none focus-visible:ring-0'
257258
)}

0 commit comments

Comments
 (0)