✨ feat(vscode): 看板默认显示团队工单,同事的卡只读并贴头像
看板范围默认从「只看我的」改为「团队全部」,同事移交或推进的工单不用 手动切开关就能看见。同事的卡片按 Gitea 归属(assignee 优先,无 assignee 退回创建者)判定为只读:禁止拖列、开会话、实施、合并分支、删 worktree、 删改工单,卡片右上角贴归属人头像,详情面板属性表整体消毒并显示只读徽章。 看 PR 改动、勾已查看、生成文件摘要属于本机审查动作,照常放行。 扩展侧在消息分发前统一拦截需要归属人身份的消息,兜住前端快照过期或工单 刚被重新指派的竞态;webhook 单卡上板时同步归属,避免增量卡漏进门禁。 头像由扩展侧带 token 抓回转 data URI 下发,绕开 webview CSP 对外链图片 的限制,按 URL 进程内缓存。 Claude-Session: https://claude.ai/code/session_01KSgcQwf1mWrb8qeTKFvJbV
This commit is contained in:
1 parent
e8963ac2a2
commit
9167b86708
26 files changed
+550
-34
No files matched your search
@@ -20,12 +20,39 @@ interface IssueCardProps {
|
||||
locked?: boolean
|
||||
/** 锁定时关联的前置工单号,用于 tooltip 文案 "等待 #N 完成"。 */
|
||||
lockedPrerequisite?: number
|
||||
/** 只读态:同事的工单。禁止拖拽,并在右上角贴归属人头像。 */
|
||||
readOnly?: boolean
|
||||
/** 归属人 Gitea login(只读卡片才显示)。 */
|
||||
ownerLogin?: string
|
||||
/** 归属人头像 data URI;缺失时回退首字母圆点。 */
|
||||
ownerAvatar?: string
|
||||
}
|
||||
|
||||
/** 同事工单的归属人标记:有头像用头像,没有就用 login 首字母。 */
|
||||
function OwnerBadge({ login, avatar }: { login: string, avatar?: string }) {
|
||||
return avatar
|
||||
? (
|
||||
<img
|
||||
src={avatar}
|
||||
alt={login}
|
||||
title={`${login} 的工单(只读)`}
|
||||
className="size-4 shrink-0 rounded-full ring-1 ring-[var(--vscode-panel-border)]"
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<span
|
||||
title={`${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)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export const IssueCard = forwardRef<HTMLDivElement, IssueCardProps>(
|
||||
({ issue, selected, onSelect, depth = 0, locked, lockedPrerequisite }, ref) => {
|
||||
({ issue, selected, onSelect, depth = 0, locked, lockedPrerequisite, readOnly, ownerLogin, ownerAvatar }, ref) => {
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging }
|
||||
= useSortable({ id: issue.id, data: { column: issue.column } })
|
||||
= useSortable({ id: issue.id, data: { column: issue.column }, disabled: readOnly })
|
||||
|
||||
const style: React.CSSProperties = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
@@ -52,23 +79,26 @@ export const IssueCard = forwardRef<HTMLDivElement, IssueCardProps>(
|
||||
onClick={() => onSelect?.(issue.id)}
|
||||
className={cn(
|
||||
'relative select-none px-3 py-2 text-xs',
|
||||
'cursor-grab active:cursor-grabbing',
|
||||
readOnly ? 'cursor-default' : 'cursor-grab active:cursor-grabbing',
|
||||
locked && 'opacity-75',
|
||||
selected
|
||||
&& 'ring-2 ring-[var(--vscode-focusBorder)] ring-offset-1 ring-offset-[var(--vscode-sideBar-background)]',
|
||||
)}
|
||||
>
|
||||
{locked
|
||||
? (
|
||||
<span
|
||||
className="absolute right-1.5 top-1.5 grid place-items-center text-[var(--vscode-disabledForeground)]"
|
||||
title={lockedPrerequisite != null ? `等待 #${lockedPrerequisite} 完成` : '等待前置工单完成'}
|
||||
aria-label={lockedPrerequisite != null ? `等待 #${lockedPrerequisite} 完成` : '等待前置工单完成'}
|
||||
>
|
||||
<Lock className="size-3.5" />
|
||||
</span>
|
||||
)
|
||||
: null}
|
||||
<div className="absolute right-1.5 top-1.5 flex items-center gap-1">
|
||||
{locked
|
||||
? (
|
||||
<span
|
||||
className="grid place-items-center text-[var(--vscode-disabledForeground)]"
|
||||
title={lockedPrerequisite != null ? `等待 #${lockedPrerequisite} 完成` : '等待前置工单完成'}
|
||||
aria-label={lockedPrerequisite != null ? `等待 #${lockedPrerequisite} 完成` : '等待前置工单完成'}
|
||||
>
|
||||
<Lock className="size-3.5" />
|
||||
</span>
|
||||
)
|
||||
: null}
|
||||
{readOnly && ownerLogin ? <OwnerBadge login={ownerLogin} avatar={ownerAvatar} /> : null}
|
||||
</div>
|
||||
{issue.handoffAttachmentId
|
||||
? (
|
||||
<span
|
||||
@@ -79,7 +109,7 @@ export const IssueCard = forwardRef<HTMLDivElement, IssueCardProps>(
|
||||
</span>
|
||||
)
|
||||
: null}
|
||||
<div className={cn('truncate', locked && 'pr-5')}>
|
||||
<div className={cn('truncate', (locked || (readOnly && ownerLogin)) && 'pr-5', locked && readOnly && ownerLogin && 'pr-10')}>
|
||||
<span className="font-mono opacity-60">
|
||||
{issue.source === 'youtrack' ? issue.externalId : `#${issue.number}`}
|
||||
</span>
|
||||
|
||||
Reference in new issue
Block a user