♻️ refactor(vscode): 看板的悬停提示也改走 data-tooltip,插件网页只剩一条提示的路;lint 禁止原生 title
- 看板剩下的原生 title(顶栏按钮、工单卡片、工单详情、会话、Profile 表、改动文件树和 diff、属性表)全部改成 data-tooltip, 禁用的按钮照样弹;只有图标、原来只靠 title 当名字的(审查勾、生成说明、属性说明的 i 图标、没头像的归属人)补上 aria-label - 提示框按原文换行:摘修复弹框里「迁移」列的几个文件一行一个 - 代码阅读 Markdown 链接的标题写成 data-tooltip - eslint 规则:网页 TSX 的 DOM 元素上写 title 报错,iframe 和组件自己的 title 属性不算
This commit is contained in:
1 parent
1f7bf63e27
commit
c8620e6e1b
15 files changed
+86
-46
No files matched your search
@@ -39,4 +39,9 @@ describe('renderMarkdown', () => {
|
||||
expect(renderMarkdown('[相对](./x.md)')).not.toContain('<a')
|
||||
expect(renderMarkdown('')).not.toContain('<img')
|
||||
})
|
||||
|
||||
it('链接的标题走悬停提示(data-tooltip),不写原生 title', () => {
|
||||
expect(renderMarkdown('[文档](https://example.com "接口说明")')).toBe('<p><a href="https://example.com" data-tooltip="接口说明">文档</a></p>\n')
|
||||
expect(renderMarkdown('[文档](https://example.com "")')).toBe('<p><a href="https://example.com">文档</a></p>\n')
|
||||
})
|
||||
})
|
||||
@@ -8,6 +8,20 @@ import MarkdownIt from 'markdown-it'
|
||||
const md = new MarkdownIt({ html: false, linkify: true }).disable('image')
|
||||
md.validateLink = url => /^(?:https?|mailto):/i.test(url)
|
||||
|
||||
/** 链接的标题(`[文字](网址 "标题")`)写成 data-tooltip,和页面上别的悬停提示走同一个提示框(components/ui/tooltip.tsx),不用原生 title。 */
|
||||
md.core.ruler.push('link_title_tooltip', (state) => {
|
||||
for (const block of state.tokens) {
|
||||
for (const token of block.children ?? []) {
|
||||
const title = token.type === 'link_open' ? token.attrGet('title') : null
|
||||
if (title === null)
|
||||
continue
|
||||
token.attrs = token.attrs!.filter(([name]) => name !== 'title')
|
||||
if (title)
|
||||
token.attrSet('data-tooltip', title)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/** 最近渲染过的文字 → HTML,最多留 CACHE_SIZE 段,最久没用的先丢。 */
|
||||
const CACHE_SIZE = 500
|
||||
const cache = new Map<string, string>()
|
||||
|
||||
@@ -227,7 +227,7 @@ function TabButton({ active, onClick, icon, title, badge }: TabButtonProps) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
title={title}
|
||||
data-tooltip={title}
|
||||
aria-label={title}
|
||||
aria-pressed={active}
|
||||
className={`relative flex h-9 w-full items-center justify-center transition-colors ${
|
||||
|
||||
@@ -209,7 +209,7 @@ export function CherryPickModal({ open, branch, groups, firstAfterReleaseSha, on
|
||||
<span className="flex min-w-0 items-center gap-1.5">
|
||||
{c.migration && (
|
||||
<span
|
||||
title={c.migration.files.join('\n')}
|
||||
data-tooltip={c.migration.files.join('\n')}
|
||||
className="shrink-0 rounded border border-current px-1 text-[10px] leading-4 opacity-80"
|
||||
>
|
||||
迁移
|
||||
|
||||
@@ -35,13 +35,15 @@ function OwnerBadge({ login, avatar }: { login: string, avatar?: string }) {
|
||||
<img
|
||||
src={avatar}
|
||||
alt={login}
|
||||
title={`${login} 的工单(只读)`}
|
||||
data-tooltip={`${login} 的工单(只读)`}
|
||||
className="size-4 shrink-0 rounded-full ring-1 ring-[var(--vscode-panel-border)]"
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<span
|
||||
title={`${login} 的工单(只读)`}
|
||||
data-tooltip={`${login} 的工单(只读)`}
|
||||
role="img"
|
||||
aria-label={`${login} 的工单(只读)`}
|
||||
className="grid size-4 shrink-0 place-items-center rounded-full bg-[var(--vscode-badge-background)] text-[8px] uppercase text-[var(--vscode-badge-foreground)]"
|
||||
>
|
||||
{login.slice(0, 1)}
|
||||
@@ -90,7 +92,7 @@ export const IssueCard = forwardRef<HTMLDivElement, IssueCardProps>(
|
||||
? (
|
||||
<span
|
||||
className="grid place-items-center text-[var(--vscode-disabledForeground)]"
|
||||
title={lockedPrerequisite != null ? `等待 #${lockedPrerequisite} 完成` : '等待前置工单完成'}
|
||||
data-tooltip={lockedPrerequisite != null ? `等待 #${lockedPrerequisite} 完成` : '等待前置工单完成'}
|
||||
aria-label={lockedPrerequisite != null ? `等待 #${lockedPrerequisite} 完成` : '等待前置工单完成'}
|
||||
>
|
||||
<Lock className="size-3.5" />
|
||||
@@ -103,7 +105,7 @@ export const IssueCard = forwardRef<HTMLDivElement, IssueCardProps>(
|
||||
? (
|
||||
<span
|
||||
className="absolute bottom-1 right-1.5 rounded border border-blue-400/50 bg-blue-400/10 px-1 text-[9px] text-blue-400"
|
||||
title={`${issue.handoffFrom ?? '?'} 已移交,待接管`}
|
||||
data-tooltip={`${issue.handoffFrom ?? '?'} 已移交,待接管`}
|
||||
>
|
||||
待接管
|
||||
</span>
|
||||
|
||||
@@ -230,7 +230,7 @@ export function IssueDetailPanel({
|
||||
<button
|
||||
type="button"
|
||||
onClick={onOpenLogs}
|
||||
title="查看扩展日志"
|
||||
data-tooltip="查看扩展日志"
|
||||
aria-label="查看扩展日志"
|
||||
className="grid size-5 place-items-center rounded opacity-60 hover:opacity-100"
|
||||
>
|
||||
@@ -803,7 +803,7 @@ export function IssueDetailPanel({
|
||||
{issue.source === 'youtrack' && (
|
||||
<span
|
||||
className="ml-2 inline-flex items-center rounded border border-purple-400/50 bg-purple-400/10 px-1.5 py-0.5 align-middle text-[10px] font-normal text-purple-400"
|
||||
title="此工单来自 YouTrack"
|
||||
data-tooltip="此工单来自 YouTrack"
|
||||
>
|
||||
YouTrack
|
||||
</span>
|
||||
@@ -814,7 +814,7 @@ export function IssueDetailPanel({
|
||||
? (
|
||||
<span
|
||||
className="ml-2 inline-flex items-center gap-1 rounded border border-[var(--vscode-panel-border)] px-1.5 py-0.5 align-middle text-[10px] font-normal text-[var(--vscode-disabledForeground)]"
|
||||
title={lockTitle}
|
||||
data-tooltip={lockTitle}
|
||||
>
|
||||
等待 #
|
||||
{prerequisiteNumber}
|
||||
@@ -828,7 +828,7 @@ export function IssueDetailPanel({
|
||||
? (
|
||||
<span
|
||||
className="inline-flex shrink-0 items-center gap-1.5 rounded border border-[var(--vscode-panel-border)] px-2 py-1 text-[11px] opacity-80"
|
||||
title={`${ownerLogin} 的工单,只能查看`}
|
||||
data-tooltip={`${ownerLogin} 的工单,只能查看`}
|
||||
>
|
||||
{avatars?.[ownerLogin]
|
||||
? <img src={avatars[ownerLogin]} alt={ownerLogin} className="size-4 rounded-full" />
|
||||
@@ -930,7 +930,7 @@ export function IssueDetailPanel({
|
||||
key={att.url}
|
||||
type="button"
|
||||
onClick={() => onOpenInBrowser(att.url)}
|
||||
title={att.name}
|
||||
data-tooltip={att.name}
|
||||
className="inline-flex max-w-[200px] items-center gap-1 truncate rounded border border-[var(--vscode-panel-border)] px-2 py-1 hover:bg-black/10 dark:hover:bg-white/10"
|
||||
>
|
||||
<ExternalLink className="size-3 shrink-0" />
|
||||
|
||||
@@ -177,7 +177,7 @@ export function ManagedSessionsPanel({
|
||||
<button
|
||||
type="button"
|
||||
onClick={openImport}
|
||||
title="导入已 /rename 的命名会话"
|
||||
data-tooltip="导入已 /rename 的命名会话"
|
||||
className="flex shrink-0 items-center gap-1 rounded border border-[var(--vscode-input-border,var(--vscode-panel-border))] bg-[var(--vscode-button-secondaryBackground,var(--vscode-input-background))] px-2 py-1 text-xs text-[var(--vscode-button-secondaryForeground,var(--vscode-foreground))] hover:bg-[var(--vscode-button-secondaryHoverBackground,var(--vscode-toolbar-hoverBackground))]"
|
||||
>
|
||||
<Download className="size-3.5" />
|
||||
@@ -367,7 +367,7 @@ function SessionRow({ session, profiles, onRename, onResume, onDelete, onCloseTa
|
||||
<button
|
||||
type="button"
|
||||
onClick={resume}
|
||||
title="点击恢复会话(新终端 resume)"
|
||||
data-tooltip="点击恢复会话(新终端 resume)"
|
||||
className="grid size-6 shrink-0 place-items-center rounded text-[var(--vscode-descriptionForeground)] hover:bg-[var(--vscode-toolbar-hoverBackground,var(--vscode-list-hoverBackground))] hover:text-[var(--vscode-foreground)]"
|
||||
aria-label="恢复会话"
|
||||
>
|
||||
@@ -411,7 +411,7 @@ function SessionRow({ session, profiles, onRename, onResume, onDelete, onCloseTa
|
||||
e.stopPropagation()
|
||||
beginEdit()
|
||||
}}
|
||||
title="双击改名"
|
||||
data-tooltip="双击改名"
|
||||
>
|
||||
{session.name || <span className="opacity-40">(未命名)</span>}
|
||||
</span>
|
||||
@@ -462,7 +462,7 @@ function SessionRow({ session, profiles, onRename, onResume, onDelete, onCloseTa
|
||||
e.stopPropagation()
|
||||
onHandoff(session.id)
|
||||
}}
|
||||
title="交接给同事"
|
||||
data-tooltip="交接给同事"
|
||||
aria-label="交接给同事"
|
||||
className="grid size-6 shrink-0 place-items-center rounded text-[var(--vscode-descriptionForeground)] opacity-0 transition-opacity hover:bg-[var(--vscode-toolbar-hoverBackground,var(--vscode-list-hoverBackground))] hover:text-[var(--vscode-foreground)] group-hover:opacity-70"
|
||||
>
|
||||
@@ -476,7 +476,7 @@ function SessionRow({ session, profiles, onRename, onResume, onDelete, onCloseTa
|
||||
e.stopPropagation()
|
||||
onCloseTab(session.id)
|
||||
}}
|
||||
title="关闭会话 tab"
|
||||
data-tooltip="关闭会话 tab"
|
||||
aria-label="关闭会话 tab"
|
||||
className="grid size-6 shrink-0 place-items-center rounded text-[var(--vscode-descriptionForeground)] opacity-70 transition-opacity hover:bg-[var(--vscode-toolbar-hoverBackground,var(--vscode-list-hoverBackground))] hover:text-[var(--vscode-foreground)]"
|
||||
>
|
||||
@@ -490,7 +490,7 @@ function SessionRow({ session, profiles, onRename, onResume, onDelete, onCloseTa
|
||||
e.stopPropagation()
|
||||
onDelete(session.id)
|
||||
}}
|
||||
title="从列表移除(不删 jsonl)"
|
||||
data-tooltip="从列表移除(不删 jsonl)"
|
||||
aria-label="从列表移除"
|
||||
className="grid size-6 shrink-0 place-items-center rounded text-[var(--vscode-descriptionForeground)] opacity-0 transition-opacity hover:bg-[var(--vscode-toolbar-hoverBackground,var(--vscode-list-hoverBackground))] hover:text-state-red group-hover:opacity-70"
|
||||
>
|
||||
|
||||
@@ -139,7 +139,7 @@ export function PanelHeader({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onFilterChange('')}
|
||||
title="清空筛选"
|
||||
data-tooltip="清空筛选"
|
||||
aria-label="清空筛选"
|
||||
className="absolute right-1 flex h-4 w-4 items-center justify-center rounded hover:bg-[var(--vscode-toolbar-hoverBackground)]"
|
||||
>
|
||||
@@ -152,7 +152,7 @@ export function PanelHeader({
|
||||
type="button"
|
||||
onClick={() => setReleaseMenuOpen(open => !open)}
|
||||
disabled={releaseDisabled}
|
||||
title={title}
|
||||
data-tooltip={title}
|
||||
aria-label={title}
|
||||
aria-haspopup="menu"
|
||||
aria-expanded={releaseMenuOpen}
|
||||
@@ -196,7 +196,7 @@ export function PanelHeader({
|
||||
type="button"
|
||||
onClick={onToggleEnvLock}
|
||||
disabled={envLockDisabled}
|
||||
title={envLockTitle}
|
||||
data-tooltip={envLockTitle}
|
||||
aria-label={envLockTitle}
|
||||
className="flex h-6 w-6 items-center justify-center rounded hover:bg-[var(--vscode-toolbar-hoverBackground)] disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
@@ -211,7 +211,7 @@ export function PanelHeader({
|
||||
type="button"
|
||||
onClick={onCommit}
|
||||
disabled={commitRunning}
|
||||
title="提交代码"
|
||||
data-tooltip="提交代码"
|
||||
aria-label="提交代码"
|
||||
className="flex h-6 w-6 items-center justify-center rounded hover:bg-[var(--vscode-toolbar-hoverBackground)] disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
@@ -224,7 +224,7 @@ export function PanelHeader({
|
||||
<button
|
||||
type="button"
|
||||
onClick={onImportYouTrack}
|
||||
title="导入 YouTrack 工单"
|
||||
data-tooltip="导入 YouTrack 工单"
|
||||
aria-label="导入 YouTrack 工单"
|
||||
className="flex h-6 w-6 items-center justify-center rounded hover:bg-[var(--vscode-toolbar-hoverBackground)]"
|
||||
>
|
||||
@@ -234,7 +234,7 @@ export function PanelHeader({
|
||||
<button
|
||||
type="button"
|
||||
onClick={onToggleScope}
|
||||
title={scope === 'mine' ? '当前:只看我的工单,点击切换为全部' : '当前:团队全部工单,点击切换为只看我的'}
|
||||
data-tooltip={scope === 'mine' ? '当前:只看我的工单,点击切换为全部' : '当前:团队全部工单,点击切换为只看我的'}
|
||||
aria-label={scope === 'mine' ? '切换为全部工单' : '切换为只看我的'}
|
||||
className="flex h-6 w-6 items-center justify-center rounded hover:bg-[var(--vscode-toolbar-hoverBackground)]"
|
||||
>
|
||||
@@ -244,7 +244,7 @@ export function PanelHeader({
|
||||
type="button"
|
||||
onClick={onRefresh}
|
||||
disabled={refreshing}
|
||||
title={refreshing ? '正在刷新…' : '刷新'}
|
||||
data-tooltip={refreshing ? '正在刷新…' : '刷新'}
|
||||
aria-label="刷新"
|
||||
aria-busy={refreshing}
|
||||
className="flex h-6 w-6 items-center justify-center rounded hover:bg-[var(--vscode-toolbar-hoverBackground)] disabled:cursor-not-allowed"
|
||||
@@ -254,7 +254,7 @@ export function PanelHeader({
|
||||
<button
|
||||
type="button"
|
||||
onClick={onEditAuth}
|
||||
title="重新配置 Gitea Token"
|
||||
data-tooltip="重新配置 Gitea Token"
|
||||
aria-label="重新配置 Gitea Token"
|
||||
className="flex h-6 w-6 items-center justify-center rounded hover:bg-[var(--vscode-toolbar-hoverBackground)]"
|
||||
>
|
||||
|
||||
@@ -272,7 +272,7 @@ export function ProfileGrid({ data, onSave, onOpen }: ProfileGridProps) {
|
||||
className={`${headerBase} border-t px-2`}
|
||||
onDoubleClick={() => beginEdit({ kind: 'header', col })}
|
||||
onContextMenu={e => openHeaderMenu(e, col)}
|
||||
title="双击改名,右键删除"
|
||||
data-tooltip="双击改名,右键删除"
|
||||
>
|
||||
{editing && editing.kind === 'header' && editing.col === col
|
||||
? (
|
||||
@@ -304,7 +304,7 @@ export function ProfileGrid({ data, onSave, onOpen }: ProfileGridProps) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={addProfile}
|
||||
title="新增 profile 列"
|
||||
data-tooltip="新增 profile 列"
|
||||
aria-label="新增 profile 列"
|
||||
className="grid size-5 place-items-center rounded opacity-60 hover:bg-[var(--vscode-list-hoverBackground)] hover:opacity-100"
|
||||
>
|
||||
@@ -342,7 +342,7 @@ export function ProfileGrid({ data, onSave, onOpen }: ProfileGridProps) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={addKey}
|
||||
title="新增 key 行"
|
||||
data-tooltip="新增 key 行"
|
||||
className="flex items-center gap-1 rounded px-1 py-0.5 text-[var(--vscode-descriptionForeground)] hover:bg-[var(--vscode-list-hoverBackground)] hover:text-[var(--vscode-foreground)]"
|
||||
>
|
||||
<Plus className="size-3.5" />
|
||||
@@ -434,7 +434,7 @@ function RowFragment({
|
||||
className={`${cellBase} ${editableHover} border-l px-2 font-medium`}
|
||||
onDoubleClick={() => beginEdit({ kind: 'key', row: rIdx })}
|
||||
onContextMenu={e => openKeyMenu(e, rIdx)}
|
||||
title="双击编辑 key,右键删除"
|
||||
data-tooltip="双击编辑 key,右键删除"
|
||||
>
|
||||
{editing && editing.kind === 'key' && editing.row === rIdx
|
||||
? (
|
||||
@@ -473,7 +473,7 @@ function RowFragment({
|
||||
key={`r${rIdx}-c${col}`}
|
||||
className={`${cellBase} ${editableHover} group px-2`}
|
||||
onDoubleClick={() => onEditCell(profile)}
|
||||
title="双击编辑(多行 + 图片)"
|
||||
data-tooltip="双击编辑(多行 + 图片)"
|
||||
>
|
||||
<div className="flex w-full min-w-0 items-center gap-1.5">
|
||||
<span className="min-w-0 flex-1 truncate">
|
||||
@@ -492,7 +492,7 @@ function RowFragment({
|
||||
e.stopPropagation()
|
||||
onOpen(value)
|
||||
}}
|
||||
title={kind === 'url' ? '在浏览器打开' : '在系统中打开'}
|
||||
data-tooltip={kind === 'url' ? '在浏览器打开' : '在系统中打开'}
|
||||
aria-label={kind === 'url' ? '在浏览器打开' : '在系统中打开'}
|
||||
className="grid size-5 shrink-0 place-items-center rounded opacity-0 transition-opacity hover:bg-[var(--vscode-toolbar-hoverBackground,var(--vscode-list-hoverBackground))] hover:opacity-100 group-hover:opacity-70"
|
||||
>
|
||||
|
||||
@@ -140,7 +140,7 @@ export function FileDiffPane({
|
||||
<span
|
||||
className="min-w-0 flex-1 truncate font-mono text-xs"
|
||||
style={{ color: confirmed ? 'var(--vscode-descriptionForeground)' : 'var(--vscode-foreground)' }}
|
||||
title={path}
|
||||
data-tooltip={path}
|
||||
>
|
||||
{path}
|
||||
</span>
|
||||
@@ -148,7 +148,8 @@ export function FileDiffPane({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onToggleConfirm(!confirmed)}
|
||||
title={confirmed ? '已审查,点击取消' : '标记为已审查'}
|
||||
data-tooltip={confirmed ? '已审查,点击取消' : '标记为已审查'}
|
||||
aria-label={confirmed ? '已审查,点击取消' : '标记为已审查'}
|
||||
className="shrink-0"
|
||||
style={{
|
||||
color: confirmed
|
||||
@@ -163,7 +164,9 @@ export function FileDiffPane({
|
||||
type="button"
|
||||
onClick={onGenerate}
|
||||
disabled={generating}
|
||||
title={generating ? '生成中…' : '生成本文件改动说明'}
|
||||
data-tooltip={generating ? '生成中…' : '生成本文件改动说明'}
|
||||
aria-label="生成本文件改动说明"
|
||||
aria-busy={generating}
|
||||
className="shrink-0 text-[var(--vscode-descriptionForeground)] hover:text-[var(--vscode-foreground)] disabled:opacity-60"
|
||||
>
|
||||
{generating ? <Loader2 className="size-3.5 animate-spin" /> : <Sparkles className="size-3.5" />}
|
||||
|
||||
@@ -70,7 +70,8 @@ export function FileTree({
|
||||
e.stopPropagation()
|
||||
onToggleDirConfirm(row.descendantFiles, !isConfirmed)
|
||||
}}
|
||||
title={isConfirmed ? '已查看,点击取消' : '标记为已查看'}
|
||||
data-tooltip={isConfirmed ? '已查看,点击取消' : '标记为已查看'}
|
||||
aria-label={isConfirmed ? '已查看,点击取消' : '标记为已查看'}
|
||||
className="shrink-0 transition-opacity"
|
||||
style={{
|
||||
opacity: isConfirmed || hoveredPath === row.path ? 1 : 0,
|
||||
@@ -87,13 +88,13 @@ export function FileTree({
|
||||
const isConfirmed = confirmed.has(row.path)
|
||||
const isSelected = selectedPath === row.path
|
||||
return (
|
||||
// 文件行:点击选中 → 容器据此拉取并渲染右栏 diff;title 用完整路径。
|
||||
// 文件行:点击选中 → 容器据此拉取并渲染右栏 diff;悬停提示是状态和完整路径。
|
||||
<li
|
||||
key={row.path}
|
||||
onMouseEnter={() => setHoveredPath(row.path)}
|
||||
onMouseLeave={() => setHoveredPath(p => (p === row.path ? null : p))}
|
||||
onClick={() => onSelectFile(row.path)}
|
||||
title={`${row.status} · ${row.path}`}
|
||||
data-tooltip={`${row.status} · ${row.path}`}
|
||||
className={`group flex cursor-pointer items-center gap-2 py-0.5 pr-2 ${
|
||||
isSelected
|
||||
? 'bg-[var(--vscode-list-activeSelectionBackground)] text-[var(--vscode-list-activeSelectionForeground)]'
|
||||
@@ -114,7 +115,8 @@ export function FileTree({
|
||||
e.stopPropagation()
|
||||
onToggleFileConfirm(row.path, !isConfirmed)
|
||||
}}
|
||||
title={isConfirmed ? '已查看,点击取消' : '标记为已查看'}
|
||||
data-tooltip={isConfirmed ? '已查看,点击取消' : '标记为已查看'}
|
||||
aria-label={isConfirmed ? '已查看,点击取消' : '标记为已查看'}
|
||||
className="shrink-0 transition-opacity"
|
||||
style={{
|
||||
opacity: isConfirmed || hoveredPath === row.path ? 1 : 0,
|
||||
|
||||
@@ -97,7 +97,7 @@ function RowActionButtons({ actions, className }: { actions: RowAction[] | undef
|
||||
action.onClick()
|
||||
}}
|
||||
disabled={action.disabled}
|
||||
title={action.title}
|
||||
data-tooltip={action.title}
|
||||
aria-label={action.title ?? '次要操作'}
|
||||
className={`grid size-5 shrink-0 place-items-center ${className ?? ''} ${
|
||||
action.disabled ? 'cursor-not-allowed opacity-30' : 'opacity-60 hover:opacity-100'
|
||||
@@ -380,7 +380,7 @@ function PropertyRow({
|
||||
type="button"
|
||||
data-property-action="primary"
|
||||
onDoubleClick={() => firePrimaryAction(() => propDef.onOpen?.(value as string))}
|
||||
title={value as string}
|
||||
data-tooltip={value as string}
|
||||
className="flex min-w-0 select-none items-center gap-1.5 text-left text-xs font-mono text-[var(--vscode-textLink-foreground)] underline-offset-2 hover:text-[var(--vscode-textLink-activeForeground)] hover:underline"
|
||||
>
|
||||
{actionPending && <Loader2 className="size-3.5 shrink-0 animate-spin" />}
|
||||
@@ -468,12 +468,14 @@ function PropertyRow({
|
||||
<div className="group flex min-h-[36px] border-b border-[var(--vscode-panel-border)] transition-colors last:border-b-0">
|
||||
{/* 标签列 */}
|
||||
<div className="flex w-[38%] flex-shrink-0 items-center gap-1.5 border-r border-[var(--vscode-panel-border)] px-3 py-1">
|
||||
<span className="truncate text-xs font-medium" title={propDef.label}>
|
||||
<span className="truncate text-xs font-medium" data-tooltip={propDef.label}>
|
||||
{propDef.label}
|
||||
</span>
|
||||
{propDef.description && (
|
||||
<span
|
||||
title={propDef.description}
|
||||
data-tooltip={propDef.description}
|
||||
role="img"
|
||||
aria-label={propDef.description}
|
||||
className="grid size-3 cursor-help place-items-center opacity-60"
|
||||
>
|
||||
<Info className="size-3" />
|
||||
|
||||
@@ -100,7 +100,8 @@ function isOwnName(anchor: Element, text: string): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* 整个 webview 唯一的悬停提示:页面根上挂一次,元素只写 data-tooltip="提示"(名字照旧写在 aria-label 里)。
|
||||
* 整个 webview 唯一的悬停提示:页面根上挂一次,元素只写 data-tooltip="提示"(名字照旧写在 aria-label 里),提示里的换行照样换行。
|
||||
* 网页里的原生 title 属性由 eslint.config.mjs 的规则拦下,只有 iframe 这类必须带 title 的元素例外。
|
||||
* 不用原生 title:macOS 上 VS Code 的 Electron 只在鼠标压着元素自身边缘时才弹原生提示,停在里面的图标、文字上不弹
|
||||
* (electron/electron#49843),样式也是系统的。这里画成 VS Code 悬停框的样子(--vscode-editorHoverWidget-*),
|
||||
* portal 到 body 不被 overflow 容器裁掉;键盘聚焦(:focus-visible)立刻弹,并用 aria-describedby 挂到元素上。
|
||||
@@ -173,7 +174,7 @@ export function TooltipHost(): ReactElement | null {
|
||||
id={id}
|
||||
role="tooltip"
|
||||
style={{ top: 0, left: 0, visibility: 'hidden' }}
|
||||
className="pointer-events-none fixed z-50 max-w-[22rem] rounded border border-[var(--vscode-editorHoverWidget-border)] bg-[var(--vscode-editorHoverWidget-background)] px-2 py-1 text-[11px] leading-tight break-words text-[var(--vscode-editorHoverWidget-foreground)] shadow-[0_2px_8px_var(--vscode-widget-shadow)]"
|
||||
className="pointer-events-none fixed z-50 max-w-[22rem] rounded border border-[var(--vscode-editorHoverWidget-border)] bg-[var(--vscode-editorHoverWidget-background)] px-2 py-1 text-[11px] leading-tight break-words whitespace-pre-line text-[var(--vscode-editorHoverWidget-foreground)] shadow-[0_2px_8px_var(--vscode-widget-shadow)]"
|
||||
>
|
||||
{tip.text}
|
||||
</div>,
|
||||
|
||||
Reference in new issue
Block a user