fix(editor): 图片超链接弹窗带出图片地址(docs/31 更正)

上一笔把图片「超链接」做成预填包裹链接,用户指出「没有带出来
这张图片的 url」——要的是图片自己的地址出现在弹窗里。改版
ImageLinkModal:主字段「图片地址」打开即带出该图 src(本地相对
路径或网络地址),可查看可改;包裹链接降为第二字段「点击时打开
的链接(可空)」。提交时一条事务同时处理 src 与 link 标记,
没改动则不发事务。新增弹窗预填与 src patch 测试。
This commit is contained in:
Muse
2026-11-15 10:06:47 +08:00
parent 0c09d94501
commit 1c0dd00088
6 changed files with 120 additions and 14 deletions

View File

@@ -18,7 +18,7 @@ const SourceEditor = lazy(() => import('./components/SourceEditor'))
import { offsetInMarkdown, type CursorAnchor } from './editor/cursorSync'
import { insertPlainText, insertLinkText } from './editor/paste'
import { isImeComposing } from './utils/ime'
import { ConfirmSaveModal, AboutModal, ShortcutsModal, LinkModal, ErrorModal , EncodingConvertModal, ImageSizeModal } from './components/Modals'
import { ConfirmSaveModal, AboutModal, ShortcutsModal, LinkModal, ErrorModal , EncodingConvertModal, ImageSizeModal, ImageLinkModal } from './components/Modals'
import { tabsReducer, createTab, activeTab, isDirty, type DocTab, type TabsState } from './stores/tabs'
import { countWords, countChars, extractOutline, baseName, isPathInside, type OutlineItem } from './utils/text'
import { callUndo, callRedo, focusEditor, type CursorInfo } from './editor/editor'
@@ -1088,7 +1088,7 @@ export default function App() {
// ——— 编辑区自绘右键菜单 ———
const [editorMenu, setEditorMenu] = useState<{ x: number; y: number; hasSelection: boolean; imageSrc: string | null; imageEl: HTMLElement | null; tabId: string | null } | null>(null)
/** 右键图片的「超链接」目标(图片模式链接弹窗)与「调整大小」目标(docs/31) */
const [imageLinkTarget, setImageLinkTarget] = useState<{ tabId: string; el: HTMLElement; href: string } | null>(null)
const [imageLinkTarget, setImageLinkTarget] = useState<{ tabId: string; el: HTMLElement; src: string; href: string } | null>(null)
const [imageSizeTarget, setImageSizeTarget] = useState<{ tabId: string; el: HTMLElement; width: string | null } | null>(null)
const openEditorMenu = (e: React.MouseEvent) => {
const target = e.target as HTMLElement
@@ -1135,7 +1135,7 @@ export default function App() {
const tid = editorMenu?.tabId
if (el && tid) {
const info = paneApisRef.current.get(tid)?.imageInfoForElement(el)
setImageLinkTarget({ tabId: tid, el, href: info?.linkHref ?? '' })
setImageLinkTarget({ tabId: tid, el, src: info?.src ?? '', href: info?.linkHref ?? '' })
} else setModal('link')
break
}
@@ -2068,16 +2068,19 @@ export default function App() {
/>
)}
{imageLinkTarget && (
<LinkModal
heading="图片链接"
hint="给图片加链接,点开图片时跳转到这个地址;清空则移除链接。"
initialHref={imageLinkTarget.href}
<ImageLinkModal
src={imageLinkTarget.src}
linkHref={imageLinkTarget.href}
onCancel={() => setImageLinkTarget(null)}
onSubmit={(href) => {
onSubmit={({ src, linkHref }) => {
const t = imageLinkTarget
setImageLinkTarget(null)
const ok = paneApisRef.current.get(t.tabId)?.updateImageForElement(t.el, { linkHref: href || null })
if (ok) flash(href ? '图片链接已更新' : '图片链接已移除')
const patch: { src?: string; linkHref?: string | null } = {}
if (src !== t.src) patch.src = src
if (linkHref !== t.href) patch.linkHref = linkHref || null
if (Object.keys(patch).length === 0) return
const ok = paneApisRef.current.get(t.tabId)?.updateImageForElement(t.el, patch)
if (ok) flash('图片链接已更新')
else setError('图片已不在文档中,链接未更新。')
}}
/>

View File

@@ -217,6 +217,49 @@ export function ImageSizeModal({ current, onSubmit, onCancel }: {
)
}
/**
* 图片链接弹窗(docs/31 更正):右键图片 →「超链接…」。
* 主字段是图片自己的地址(src)——打开就带出来,可查看也可改;
* 第二字段是可选的「点击图片时打开的链接」(包裹链接,可空)。
*/
export function ImageLinkModal({ src, linkHref, onSubmit, onCancel }: {
src: string
linkHref: string
onSubmit: (next: { src: string; linkHref: string }) => void
onCancel: () => void
}) {
const [addr, setAddr] = useState(src)
const [link, setLink] = useState(linkHref)
const valid = addr.trim() !== ''
const submit = () => { if (valid) onSubmit({ src: addr.trim(), linkHref: link.trim() }) }
return (
<Shell onCancel={onCancel}>
<h3>图片链接</h3>
<label className="modal-field-label">图片地址</label>
<input
className="modal-input"
value={addr}
autoFocus
onChange={(e) => setAddr(e.target.value)}
onKeyDown={(e) => { if (e.key === 'Enter') submit() }}
placeholder="图片地址(本地相对路径或 https://…)"
/>
<label className="modal-field-label">点击时打开的链接(可空)</label>
<input
className="modal-input"
value={link}
onChange={(e) => setLink(e.target.value)}
onKeyDown={(e) => { if (e.key === 'Enter') submit() }}
placeholder="可空:填了之后点击图片会跳转到这个地址"
/>
<div className="modal-actions">
<button onClick={onCancel}>取消</button>
<button className="primary" disabled={!valid} onClick={submit}>确定</button>
</div>
</Shell>
)
}
export function ErrorModal({ message, onClose }: { message: string; onClose: () => void }) {
return (
<Shell onCancel={onClose}>

View File

@@ -161,6 +161,8 @@ button { font: inherit; color: inherit; }
.modal-actions button { border: 1px solid var(--border); background: var(--bg); border-radius: 7px; padding: 6px 14px; cursor: pointer; }
.modal-actions button.primary { background: var(--accent); color: #fff; border-color: transparent; }
.modal-input { width: 100%; padding: 8px 10px; border: 1px solid var(--border); border-radius: 7px; background: var(--bg); color: var(--text); }
.modal-field-label { display: block; font-size: 12.5px; color: var(--text-dim); margin: 10px 0 4px; }
.modal-field-label:first-of-type { margin-top: 0; }
.modal-field-row { display: flex; gap: 8px; align-items: stretch; }
.modal-field-row .modal-input { flex: 1; }
.seg-control { display: flex; border: 1px solid var(--border); border-radius: 7px; overflow: hidden; }