@wowyuarm/dsh-agent-team 0.1.11 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/README.zh.md +4 -4
- package/package.json +3 -2
- package/packages/agent-team/README.md +2 -2
- package/packages/agent-team/README.zh.md +2 -2
- package/packages/agent-team/lib/context-projection.js +5 -1
- package/packages/agent-team/lib/index.js +11 -44
- package/packages/agent-team/lib/ledger.js +251 -142
- package/packages/agent-team/lib/member-runtime.js +47 -9
- package/packages/agent-team/lib/mentions.js +56 -0
- package/packages/agent-team/lib/spec.js +1 -0
- package/packages/agent-team/lib/typert.host.js +80 -31
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +71 -26
- package/packages/agent-team/lib/types/context-projection.d.ts.map +1 -1
- package/packages/agent-team/lib/types/index.d.ts +1 -7
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +75 -17
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-runtime.d.ts +2 -1
- package/packages/agent-team/lib/types/member-runtime.d.ts.map +1 -1
- package/packages/agent-team/lib/types/mentions.d.ts +34 -0
- package/packages/agent-team/lib/types/mentions.d.ts.map +1 -0
- package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/entities.d.ts +0 -18
- package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/requests-results.d.ts +57 -15
- package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
- package/packages/agent-team/preset/team-member/agent.cordis.yml +48 -8
- package/packages/client-agent-team/lib/client.js +1366 -909
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +3 -3
- package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts +18 -0
- package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamAvatarStack.js +22 -0
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +3 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +125 -13
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +10 -3
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.js +11 -17
- package/packages/client-agent-team/lib/types/client/TeamConversation.js +1 -1
- package/packages/client-agent-team/lib/types/client/TeamCountBadge.d.ts +20 -0
- package/packages/client-agent-team/lib/types/client/TeamCountBadge.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamCountBadge.js +25 -0
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts +9 -5
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.js +82 -14
- package/packages/client-agent-team/lib/types/client/TeamMemberRow.d.ts +43 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberRow.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberRow.js +27 -0
- package/packages/client-agent-team/lib/types/client/TeamMembersAction.d.ts +2 -2
- package/packages/client-agent-team/lib/types/client/TeamMembersAction.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMembersAction.js +2 -2
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +6 -2
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +28 -10
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +3 -4
- package/packages/client-agent-team/lib/types/client/locales.d.ts +36 -16
- package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/locales.js +36 -16
- package/packages/client-agent-team/lib/types/client/slots.d.ts +0 -1
- package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-changes.d.ts +7 -1
- package/packages/client-agent-team/lib/types/client/team-changes.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-changes.js +62 -31
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +38 -5
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-formatters.js +54 -7
- package/packages/tool-agent-team/README.md +2 -2
- package/packages/tool-agent-team/README.zh.md +2 -2
- package/packages/tool-agent-team/lib/index.js +29 -16
- package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/progress-nudge.js +0 -328
- package/packages/agent-team/lib/types/progress-nudge.d.ts +0 -136
- package/packages/agent-team/lib/types/progress-nudge.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/TeamSettings.d.ts +0 -3
- package/packages/client-agent-team/lib/types/client/TeamSettings.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/TeamSettings.js +0 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamAgentsPanel.d.ts","sourceRoot":"","sources":["../../../src/client/TeamAgentsPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EACzB,2BAA2B,EAE5B,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kDAAkD,CAAA;AAEnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAalD,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IACrD,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,kBAAkB,CAAC,CAAA;IAC/D,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAA;IACjD,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAA;IACvD,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAA;IACnD,8EAA8E;IAC9E,QAAQ,CAAC,eAAe,CAAC,EAAE,2BAA2B,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,CAAA;IAC7E,QAAQ,CAAC,iBAAiB,EAAE,gBAAgB,CAAC,mBAAmB,CAAC,CAAA;IACjE,QAAQ,CAAC,gBAAgB,EAAE,CAAC,OAAO,EAAE,yBAAyB,EAAE,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAA;IAC1F,QAAQ,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAA;CAClC;AAED,wBAAgB,eAAe,CAAC,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,CAAC,EAAE,EAAE,oBAAoB,+
|
|
1
|
+
{"version":3,"file":"TeamAgentsPanel.d.ts","sourceRoot":"","sources":["../../../src/client/TeamAgentsPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EACzB,2BAA2B,EAE5B,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kDAAkD,CAAA;AAEnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAalD,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IACrD,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,kBAAkB,CAAC,CAAA;IAC/D,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAA;IACjD,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAA;IACvD,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAA;IACnD,8EAA8E;IAC9E,QAAQ,CAAC,eAAe,CAAC,EAAE,2BAA2B,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,CAAA;IAC7E,QAAQ,CAAC,iBAAiB,EAAE,gBAAgB,CAAC,mBAAmB,CAAC,CAAA;IACjE,QAAQ,CAAC,gBAAgB,EAAE,CAAC,OAAO,EAAE,yBAAyB,EAAE,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAA;IAC1F,QAAQ,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAA;CAClC;AAED,wBAAgB,eAAe,CAAC,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,CAAC,EAAE,EAAE,oBAAoB,+BA0O/N"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { Button, IconArchiveOutline20, IconEditOutline16, IconPlayOutline16, IconPlusOutline16, IconRefreshOutline16, Input, Modal, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
|
-
import {
|
|
4
|
+
import { TeamMemberIdentity } from "./TeamMemberRow.js";
|
|
5
5
|
import { SortableRow, useSidebarRowDrag } from "./sidebar-drag.js";
|
|
6
6
|
import { moveSidebarItem, useSidebarOrder } from "./sidebar-order.js";
|
|
7
7
|
import { useSidebarSectionOpen, setSidebarSectionOpen } from "./sidebar-sections.js";
|
|
@@ -195,7 +195,7 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, ad
|
|
|
195
195
|
...(model === undefined ? {} : { model }),
|
|
196
196
|
});
|
|
197
197
|
};
|
|
198
|
-
return (_jsxs("div", { className: css.panel, children: [_jsx(Modal, { open: formOpen, onClose: closeForm, title: t('addAgent'), closeLabel: t('close'), contentClassName: createCss.dialogContent, footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: creating, onClick: closeForm, children: t('cancel') }), _jsx(Button, { type: "submit", form: "team-agent-create-form", variant: "primary", disabled: creating || handle.trim().length === 0, children: creating ? t('creatingAgent') : t('createAgent') })] }), children: _jsxs("form", { id: "team-agent-create-form", className: createCss.form, onSubmit: submit, children: [_jsxs("label", { className: createCss.field, children: [_jsx("span", { children: t('agentName') }), _jsx(Input, { className: createCss.input, value: handle, onChange: event => { setHandle(event.target.value); setRetryRequest(undefined); }, disabled: creating, autoFocus: true })] }), _jsxs("label", { className: createCss.field, children: [_jsxs("span", { children: [t('agentDescription'), t('optionalSuffix')] }), _jsx(Input, { className: createCss.input, value: description, placeholder: t('agentDescriptionPlaceholder'), onChange: event => { setDescription(event.target.value); setRetryRequest(undefined); }, disabled: creating })] }), _jsx(ModelPickerField, { model: model, onModelChange: choice => { setModel(choice); setRetryRequest(undefined); }, loadModels: loadModels, disabled: creating, t: t }), formOpen && error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] }) }), _jsxs(TeamSidebarSection, { title: t('agents'), open: sectionOpen, onToggle: open => { setSidebarSectionOpen(workspaceId, 'agents', open); }, actions: (_jsx(Tooltip, { label: t('addAgent'), delayMs: 500, children: _jsx("button", { ref: triggerRef, type: "button", className: css.iconButton, "aria-label": t('addAgent'), onClick: () => { setError(undefined); setFormOpen(true); }, children: _jsx(IconPlusOutline16, { size: 14 }) }) })), children: [loading && members.length === 0 && _jsx("p", { className: css.emptyState, children: t('loadingAgents') }), !loading && members.length === 0 && _jsx("p", { className: css.emptyState, children: t('emptyAgents') }), _jsx("div", { className: css.agentList, children: orderedMembers.map(status => (_jsx(SortableRow, { drag: drag, orderKey: status.member.memberId, children: _jsx(AgentRow, { status: status, ...(memberSessionId === undefined ? {} : { current: status.member.sessionId === memberSessionId }), updateMember: updateMember, recoverMember: recoverMember, archiveMember: archiveMember, loadModels: loadModels, openMemberSession: openMemberSession, onUpdated: () => { void refresh(); }, t: t }) }, status.member.memberId))) })] }), !formOpen && error !== undefined && (_jsxs("div", { className: css.retryError, role: "alert", children: [_jsx("span", { children: error }), retryRequest !== undefined && _jsx("button", { type: "button", className: css.textButton, disabled: creating, onClick: () => { setFormOpen(true); }, children: t('retry') })] }))] }));
|
|
198
|
+
return (_jsxs("div", { className: css.panel, children: [_jsx(Modal, { open: formOpen, onClose: closeForm, title: t('addAgent'), closeLabel: t('close'), contentClassName: createCss.dialogContent, footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: creating, onClick: closeForm, children: t('cancel') }), _jsx(Button, { type: "submit", form: "team-agent-create-form", variant: "primary", disabled: creating || handle.trim().length === 0, children: creating ? t('creatingAgent') : t('createAgent') })] }), children: _jsxs("form", { id: "team-agent-create-form", className: createCss.form, onSubmit: submit, children: [_jsxs("label", { className: createCss.field, children: [_jsx("span", { children: t('agentName') }), _jsx(Input, { className: createCss.input, value: handle, onChange: event => { setHandle(event.target.value); setRetryRequest(undefined); }, disabled: creating, autoFocus: true })] }), _jsxs("label", { className: createCss.field, children: [_jsxs("span", { children: [t('agentDescription'), t('optionalSuffix')] }), _jsx(Input, { className: createCss.input, value: description, placeholder: t('agentDescriptionPlaceholder'), onChange: event => { setDescription(event.target.value); setRetryRequest(undefined); }, disabled: creating })] }), _jsx(ModelPickerField, { model: model, onModelChange: choice => { setModel(choice); setRetryRequest(undefined); }, loadModels: loadModels, disabled: creating, t: t }), formOpen && error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] }) }), _jsxs(TeamSidebarSection, { title: t('agents'), open: sectionOpen, onToggle: open => { setSidebarSectionOpen(workspaceId, 'agents', open); }, actions: (_jsx(Tooltip, { label: t('addAgent'), delayMs: 500, children: _jsx("button", { ref: triggerRef, type: "button", className: css.iconButton, "aria-label": t('addAgent'), onClick: () => { setError(undefined); setFormOpen(true); }, children: _jsx(IconPlusOutline16, { size: 14 }) }) })), children: [loading && members.length === 0 && _jsx("p", { className: css.emptyState, children: t('loadingAgents') }), !loading && error === undefined && members.length === 0 && _jsx("p", { className: css.emptyState, children: t('emptyAgents') }), _jsx("div", { className: css.agentList, children: orderedMembers.map(status => (_jsx(SortableRow, { drag: drag, orderKey: status.member.memberId, children: _jsx(AgentRow, { status: status, ...(memberSessionId === undefined ? {} : { current: status.member.sessionId === memberSessionId }), updateMember: updateMember, recoverMember: recoverMember, archiveMember: archiveMember, loadModels: loadModels, openMemberSession: openMemberSession, onUpdated: () => { void refresh(); }, t: t }) }, status.member.memberId))) })] }), !formOpen && error !== undefined && (_jsxs("div", { className: css.retryError, role: "alert", children: [_jsx("span", { children: error }), retryRequest !== undefined && _jsx("button", { type: "button", className: css.textButton, disabled: creating, onClick: () => { setFormOpen(true); }, children: t('retry') })] }))] }));
|
|
199
199
|
}
|
|
200
200
|
/**
|
|
201
201
|
* One sidebar Agent row: the select button opens the Member's own Session
|
|
@@ -255,7 +255,7 @@ function AgentRow({ status, current, updateMember, recoverMember, archiveMember,
|
|
|
255
255
|
setRowAlert(t('archiveAgentFailed', { message: cause instanceof Error ? cause.message : String(cause) }));
|
|
256
256
|
}
|
|
257
257
|
};
|
|
258
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: css.agentRow, "data-menu-open": menuOpen || undefined, children: [
|
|
258
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: css.agentRow, "data-agent-row": true, "data-menu-open": menuOpen || undefined, children: [_jsx("button", { type: "button", className: css.agentSelect, "aria-label": t('openAgentSession', { name: status.member.handle }), "aria-current": current ? 'page' : undefined, disabled: status.availability !== 'active', onClick: () => { openMemberSession(status.member.sessionId); }, children: _jsx(TeamMemberIdentity, { status: status, name: status.member.handle.replace(/^@/, ''), className: css.agentCopy, t: t }) }), _jsx("span", { className: css.rowMenu, children: _jsx(TeamRowMenu, { label: t('actionsAgent', { name: status.member.handle }), items: [
|
|
259
259
|
{ id: 'edit', label: t('editAgent'), icon: _jsx(IconEditOutline16, {}) },
|
|
260
260
|
...(status.presence === 'error' ? [{ id: 'resume', label: t('resumeAgent'), icon: _jsx(IconPlayOutline16, {}) }] : []),
|
|
261
261
|
...(status.availability === 'unavailable' && restartOffered(status) ? [{ id: 'restart', label: t('restartAgent'), icon: _jsx(IconRefreshOutline16, {}) }] : []),
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AgentTeamMemberId } from '@wowyuarm/dsh-agent-team/types';
|
|
2
|
+
/** One owner as the stack draws it: the id carries the hue, the name the initial. */
|
|
3
|
+
export interface TeamAvatarOwner {
|
|
4
|
+
readonly memberId: AgentTeamMemberId;
|
|
5
|
+
/** Public handle, or the raw Member id when the roster no longer names them. */
|
|
6
|
+
readonly name: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The compact "who is on this work" stack: overlapping 18px Member circles in
|
|
10
|
+
* the shared identity language, capped at three plus a `+N` chip. The circles
|
|
11
|
+
* are presentational, so the stack is one `role="img"` whose label carries the
|
|
12
|
+
* whole roster — three anonymous initials would read as noise.
|
|
13
|
+
*/
|
|
14
|
+
export declare function TeamAvatarStack({ owners, label }: {
|
|
15
|
+
readonly owners: readonly TeamAvatarOwner[];
|
|
16
|
+
readonly label: string;
|
|
17
|
+
}): import("react").JSX.Element | null;
|
|
18
|
+
//# sourceMappingURL=TeamAvatarStack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TeamAvatarStack.d.ts","sourceRoot":"","sources":["../../../src/client/TeamAvatarStack.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAOvE,qFAAqF;AACrF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAA;IACpC,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE;IACjD,QAAQ,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,CAAA;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB,sCAQA"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { memberHue } from "./team-formatters.js";
|
|
3
|
+
import css from './avatar-stack.module.css';
|
|
4
|
+
/** Distinct owners past this count collapse into one `+N` chip. */
|
|
5
|
+
const MAX_VISIBLE = 3;
|
|
6
|
+
/**
|
|
7
|
+
* The compact "who is on this work" stack: overlapping 18px Member circles in
|
|
8
|
+
* the shared identity language, capped at three plus a `+N` chip. The circles
|
|
9
|
+
* are presentational, so the stack is one `role="img"` whose label carries the
|
|
10
|
+
* whole roster — three anonymous initials would read as noise.
|
|
11
|
+
*/
|
|
12
|
+
export function TeamAvatarStack({ owners, label }) {
|
|
13
|
+
if (owners.length === 0)
|
|
14
|
+
return null;
|
|
15
|
+
const shown = owners.slice(0, MAX_VISIBLE);
|
|
16
|
+
const overflow = owners.length - shown.length;
|
|
17
|
+
return _jsxs("span", { className: css.stack, role: "img", "aria-label": label, children: [shown.map(owner => _jsx("span", { className: css.avatar, style: { '--team-avatar-hue': memberHue(owner.memberId) }, children: initial(owner.name) }, owner.memberId)), overflow > 0 && _jsx("span", { className: css.overflow, children: `+${overflow}` })] });
|
|
18
|
+
}
|
|
19
|
+
/** First visible character of a handle — and of a raw Member id when that is all there is. */
|
|
20
|
+
function initial(name) {
|
|
21
|
+
return name.replace(/^@/, '').replace(/^member:/, '').slice(0, 1).toUpperCase();
|
|
22
|
+
}
|
|
@@ -8,6 +8,8 @@ interface TeamChannelPageProps {
|
|
|
8
8
|
readonly loadChannels: TeamConversationProps['loadChannels'];
|
|
9
9
|
readonly subscribeChanges: TeamConversationProps['subscribeChanges'];
|
|
10
10
|
readonly loadMembers: TeamConversationProps['loadMembers'];
|
|
11
|
+
/** The Human's own unread per Thread: the Host's three-class judgement, never a Client guess. */
|
|
12
|
+
readonly loadInbox: TeamConversationProps['loadInbox'];
|
|
11
13
|
readonly drafts: TeamDraftStore;
|
|
12
14
|
readonly getAttachment: TeamConversationProps['getAttachment'];
|
|
13
15
|
readonly putAttachment: TeamConversationProps['putAttachment'];
|
|
@@ -20,6 +22,6 @@ interface TeamChannelPageProps {
|
|
|
20
22
|
readonly backToChannels: TeamConversationProps['backToChannels'];
|
|
21
23
|
readonly t: TeamConversationProps['t'];
|
|
22
24
|
}
|
|
23
|
-
export declare function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscribeChanges, loadMembers, drafts, putAttachment, getAttachment, sendMessage, joinChannel, removeChannelMember, selectThread, selectChannel, backToChannels, resolveTaskRefs, t }: TeamChannelPageProps): import("react").JSX.Element;
|
|
25
|
+
export declare function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscribeChanges, loadMembers, loadInbox, drafts, putAttachment, getAttachment, sendMessage, joinChannel, removeChannelMember, selectThread, selectChannel, backToChannels, resolveTaskRefs, t }: TeamChannelPageProps): import("react").JSX.Element;
|
|
24
26
|
export {};
|
|
25
27
|
//# sourceMappingURL=TeamChannelPage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamChannelPage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamChannelPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAA+B,mBAAmB,EAE7D,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kDAAkD,CAAA;AAEnF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAEvD,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"TeamChannelPage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamChannelPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAA+B,mBAAmB,EAE7D,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kDAAkD,CAAA;AAEnF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAEvD,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,aAAa,CAAA;AAiB/D,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,UAAU,EAAE,mBAAmB,CAAA;IACxC,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,gBAAgB,EAAE,qBAAqB,CAAC,kBAAkB,CAAC,CAAA;IACpE,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAA;IAC1D,iGAAiG;IACjG,QAAQ,CAAC,SAAS,EAAE,qBAAqB,CAAC,WAAW,CAAC,CAAA;IACtD,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAA;IAC/B,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAA;IAC1D,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAA;IAC1D,QAAQ,CAAC,mBAAmB,EAAE,qBAAqB,CAAC,qBAAqB,CAAC,CAAA;IAC1E,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,eAAe,EAAE,qBAAqB,CAAC,iBAAiB,CAAC,CAAA;IAClE,QAAQ,CAAC,cAAc,EAAE,qBAAqB,CAAC,gBAAgB,CAAC,CAAA;IAChE,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC;AA0DD,wBAAgB,eAAe,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,mBAAmB,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,eAAe,EAAE,CAAC,EAAE,EAAE,oBAAoB,+BAkX9R"}
|
|
@@ -3,10 +3,12 @@ import { Fragment, useCallback, useEffect, useRef, useState } from 'react';
|
|
|
3
3
|
import { Button, IconChevronLeftOutline14, IconChevronRightOutline14, Modal } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
4
|
import { mintRequestId, uploadComposerFiles } from "./requests.js";
|
|
5
5
|
import { TeamComposer } from "./TeamComposer.js";
|
|
6
|
-
import {
|
|
6
|
+
import { TeamMemberRow } from "./TeamMemberRow.js";
|
|
7
7
|
import { TeamMessage } from "./TeamMessage.js";
|
|
8
|
+
import { TeamAvatarStack } from "./TeamAvatarStack.js";
|
|
9
|
+
import { TeamCountBadge } from "./TeamCountBadge.js";
|
|
8
10
|
import { TeamRunDivider } from "./TeamRunDivider.js";
|
|
9
|
-
import { formatTaskStatus, taskStatusDot, mentionNamesOf } from "./team-formatters.js";
|
|
11
|
+
import { formatAbsoluteTime, formatInboxTime, formatTaskStatus, taskStatusDot, mentionNamesOf } from "./team-formatters.js";
|
|
10
12
|
import { TeamStateDot } from "./TeamStateDot.js";
|
|
11
13
|
import { useChannelMembership } from "./team-membership.js";
|
|
12
14
|
import { useTimelineScroll } from "./timeline-scroll.js";
|
|
@@ -16,12 +18,14 @@ import channelCss from './channel.module.css';
|
|
|
16
18
|
import css from './conversation.module.css';
|
|
17
19
|
import threadCss from './thread.module.css';
|
|
18
20
|
/**
|
|
19
|
-
* Merge the freshest top-level window over what the reader already has
|
|
20
|
-
*
|
|
21
|
+
* Merge the freshest top-level window over what the reader already has. The
|
|
22
|
+
* fresh window is authoritative for every Message it covers — a change wake
|
|
23
|
+
* must move that row's live Task state, newest instant, and unread with it —
|
|
24
|
+
* while older Messages loaded earlier are retained instead of discarded.
|
|
21
25
|
*/
|
|
22
26
|
function mergeChannelView(current, fresh) {
|
|
23
|
-
const
|
|
24
|
-
const items = [...
|
|
27
|
+
const freshRefs = new Set(fresh.items.map(item => item.message.messageRef));
|
|
28
|
+
const items = [...current.items.filter(item => !freshRefs.has(item.message.messageRef)), ...fresh.items]
|
|
25
29
|
.sort((left, right) => left.message.sequence - right.message.sequence);
|
|
26
30
|
return {
|
|
27
31
|
...fresh,
|
|
@@ -31,9 +35,47 @@ function mergeChannelView(current, fresh) {
|
|
|
31
35
|
hasMore: fresh.hasMore || current.cursor < fresh.cursor,
|
|
32
36
|
};
|
|
33
37
|
}
|
|
34
|
-
|
|
38
|
+
/**
|
|
39
|
+
* The people the Channel feed shows on one Task: owners of its live Claims, in
|
|
40
|
+
* claim order, deduped. Claim state is a Host fact, not decoration — a released
|
|
41
|
+
* Claim is no longer work — and only a Task still in progress or review has
|
|
42
|
+
* any, because a done or closed Task keeps its Claims as history that the state
|
|
43
|
+
* word already tells the reader.
|
|
44
|
+
*/
|
|
45
|
+
function liveOwnersByTask(items, claims) {
|
|
46
|
+
const live = new Set(items
|
|
47
|
+
.flatMap(item => item.task === undefined ? [] : [item.task])
|
|
48
|
+
.filter(task => task.status === 'in_progress' || task.status === 'in_review')
|
|
49
|
+
.map(task => task.taskRef));
|
|
50
|
+
const owners = new Map();
|
|
51
|
+
for (const claim of claims) {
|
|
52
|
+
if (!live.has(claim.taskRef) || claim.state === 'released')
|
|
53
|
+
continue;
|
|
54
|
+
const known = owners.get(claim.taskRef);
|
|
55
|
+
if (known === undefined)
|
|
56
|
+
owners.set(claim.taskRef, [claim.owner]);
|
|
57
|
+
else if (!known.includes(claim.owner))
|
|
58
|
+
known.push(claim.owner);
|
|
59
|
+
}
|
|
60
|
+
return owners;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The newest fact instant, or nothing when the entry's own Message is still the
|
|
64
|
+
* newest fact on its Thread. That difference is exactly what "has follow-up
|
|
65
|
+
* activity" means, and the Host projects both instants from the ledger.
|
|
66
|
+
*/
|
|
67
|
+
function followUpAt(item) {
|
|
68
|
+
const lastActivityAt = item.lastActivityAt;
|
|
69
|
+
return lastActivityAt === undefined || lastActivityAt === item.message.occurredAt ? undefined : lastActivityAt;
|
|
70
|
+
}
|
|
71
|
+
/** Host unread per Thread, keyed for the feed's rows: zero unread is the absence of a badge, not a row. */
|
|
72
|
+
function unreadCounts(inbox) {
|
|
73
|
+
return new Map(inbox.items.filter(item => item.unreadCount > 0).map(item => [item.thread.threadRef, item.unreadCount]));
|
|
74
|
+
}
|
|
75
|
+
export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscribeChanges, loadMembers, loadInbox, drafts, putAttachment, getAttachment, sendMessage, joinChannel, removeChannelMember, selectThread, selectChannel, backToChannels, resolveTaskRefs, t }) {
|
|
35
76
|
const [view, setView] = useState();
|
|
36
77
|
const [members, setMembers] = useState([]);
|
|
78
|
+
const [unreadByThread, setUnreadByThread] = useState(new Map());
|
|
37
79
|
const [error, setError] = useState();
|
|
38
80
|
const [pendingFiles, setPendingFiles] = useState([]);
|
|
39
81
|
const [statusMessage, setStatusMessage] = useState();
|
|
@@ -103,7 +145,11 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
|
|
|
103
145
|
// Presence counts ride the header meta line; error and unavailable do not count as online.
|
|
104
146
|
const onlineCount = channelMembers.filter(status => status.presence === 'available' || status.presence === 'working').length;
|
|
105
147
|
const messageSender = (item) => item.message.sender;
|
|
106
|
-
const
|
|
148
|
+
const handleByMember = new Map(members.map(status => [status.member.memberId, status.member.handle.replace(/^@/, '')]));
|
|
149
|
+
// Claim owners ride the same Host projection as everything else; the map is
|
|
150
|
+
// rebuilt per render because the window is small and identity must never lag
|
|
151
|
+
// a change wake.
|
|
152
|
+
const ownersByTask = liveOwnersByTask(view?.items ?? [], view?.claims ?? []);
|
|
107
153
|
const refresh = async (clearError = false) => {
|
|
108
154
|
if (!mountedRef.current)
|
|
109
155
|
return false;
|
|
@@ -118,9 +164,14 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
|
|
|
118
164
|
setStatusMessage(undefined);
|
|
119
165
|
}
|
|
120
166
|
try {
|
|
121
|
-
const [loaded, loadedMembers] = await Promise.all([
|
|
167
|
+
const [loaded, loadedMembers, loadedInbox] = await Promise.all([
|
|
122
168
|
loadChannels({ workspaceId, channelRef, direction: 'before', topLevelOnly: true, includeActivities: false, limit: 20 }),
|
|
123
169
|
loadMembers({ workspaceId }),
|
|
170
|
+
// The non-direct slice of the Host's own Inbox is the authority for
|
|
171
|
+
// "what needs me on this Thread" — following activity, mentions, and my
|
|
172
|
+
// Task/Claim changes alike. Nothing here re-derives unread from message
|
|
173
|
+
// mentions.
|
|
174
|
+
loadInbox({ workspaceId, limit: 100 }),
|
|
124
175
|
]);
|
|
125
176
|
if (!mountedRef.current || sequence !== refreshSequenceRef.current)
|
|
126
177
|
return false;
|
|
@@ -134,7 +185,15 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
|
|
|
134
185
|
setMembers(loadedMembers.value);
|
|
135
186
|
else
|
|
136
187
|
setError(loadedMembers.error.message);
|
|
137
|
-
|
|
188
|
+
// A failed unread read drops the badges instead of leaving counts the
|
|
189
|
+
// reader can no longer trust; the failure surfaces like any other read.
|
|
190
|
+
if (loadedInbox.ok)
|
|
191
|
+
setUnreadByThread(unreadCounts(loadedInbox.value));
|
|
192
|
+
else {
|
|
193
|
+
setUnreadByThread(new Map());
|
|
194
|
+
setError(loadedInbox.error.message);
|
|
195
|
+
}
|
|
196
|
+
return loaded.ok && loadedMembers.ok && loadedInbox.ok;
|
|
138
197
|
}
|
|
139
198
|
catch (cause) {
|
|
140
199
|
if (mountedRef.current && sequence === refreshSequenceRef.current)
|
|
@@ -314,9 +373,16 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
|
|
|
314
373
|
return _jsxs("main", { className: css.surface, "data-team-channel": channelRef, children: [_jsxs("div", { className: css.surfaceHeader, children: [_jsx("div", { className: css.backRow, children: _jsx(Button, { size: "sm", icon: _jsx(IconChevronLeftOutline14, {}), onClick: backToChannels, children: t('backToChannels') }) }), _jsxs("header", { className: css.headerRow, children: [_jsxs("div", { className: css.headerCopy, children: [_jsx("h1", { children: channel === undefined ? '# …' : `# ${channel.name}` }), channel !== undefined && _jsx("p", { children: channel.description }), channel !== undefined && _jsxs("div", { className: channelCss.headerMeta, children: [_jsx("span", { children: t('memberCount', { count: channelMembers.length }) }), _jsx("span", { children: t('onlineCount', { count: onlineCount }) })] })] }), channel !== undefined && _jsx("span", { ref: manageTriggerRef, children: _jsx(Button, { size: "sm", variant: "outline", "aria-haspopup": "dialog", onClick: () => { setManagingMembers(true); }, children: t('manageMembers') }) })] })] }), _jsx(Modal, { open: managingMembers, onClose: closeMembers, title: t('channelMembers'), ...(channel === undefined ? {} : { description: `# ${channel.name} · ${t('memberCount', { count: channelMembers.length })}` }), closeLabel: t('close'), contentClassName: channelCss.modalBody, children: _jsx("div", { ref: memberListRef, className: channelCss.memberList, children: members.filter(status => status.member.state !== 'inactive' && status.member.state !== 'archived').map(status => {
|
|
315
374
|
const joined = channelMemberIds.has(status.member.memberId);
|
|
316
375
|
const rowPending = membership.pending.has(status.member.memberId);
|
|
317
|
-
|
|
376
|
+
// Joining needs an active Member — the Host refuses any other
|
|
377
|
+
// availability. Leaving only needs the Membership fact, so removal
|
|
378
|
+
// stays offered while a joined Member is down.
|
|
379
|
+
const disabled = rowPending || (!joined && status.availability !== 'active');
|
|
318
380
|
const rowError = membership.errors.get(status.member.memberId);
|
|
319
|
-
return
|
|
381
|
+
return _jsx(TeamMemberRow, { status: status, action: {
|
|
382
|
+
label: rowPending ? t('membershipUpdating') : joined ? t('removeFromChannel') : t('addToChannel'),
|
|
383
|
+
disabled,
|
|
384
|
+
onSelect: () => { void membership.change({ workspaceId, channelRef, memberId: status.member.memberId, joined }); },
|
|
385
|
+
}, ...(rowError === undefined ? {} : { error: rowError }), t: t }, status.member.memberId);
|
|
320
386
|
}) }) }), _jsx("section", { ref: timeline.ref, onScroll: timeline.onScroll, className: css.timeline, "aria-label": t('timelineLabel'), children: _jsxs("div", { className: css.timelineContent, children: [loading && channel === undefined && error === undefined && _jsx("div", { className: css.emptySurface, children: _jsxs("p", { className: css.loadingState, children: [_jsx("span", { className: css.loadingMark, "aria-hidden": "true" }), t('loadingChannels')] }) }), !loading && channel === undefined && error === undefined && _jsx("div", { className: css.emptySurface, children: _jsx("p", { className: css.emptyState, children: t('emptyChannels') }) }), !loading && channel === undefined && error !== undefined && _jsxs("div", { className: css.errorState, role: "alert", children: [_jsx("span", { children: error }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => { void refresh(true); }, children: t('retry') })] }), view?.hasMore && _jsx("div", { className: css.timelineAction, children: _jsx(Button, { size: "sm", disabled: loadingOlder, onClick: () => { void loadOlder(); }, children: t('loadOlder') }) }), channel !== undefined && view?.items.length === 0 && _jsx("div", { className: css.emptySurface, children: _jsxs("div", { className: css.emptyState, children: [_jsx("strong", { children: t('emptyMessages') }), _jsx("span", { children: t('emptyMessagesHint') })] }) }), (view?.items.length ?? 0) > 0 && chunkRunsWithDays(view.items, messageSender, item => item.message.occurredAt).map((block, blockIndex) => block.kind === 'day'
|
|
321
387
|
? _jsx("p", { className: threadCss.daySeparator, children: _jsx("span", { children: block.label }) }, `day-${blockIndex}-${block.label}`)
|
|
322
388
|
: _jsx("div", { className: css.messageRun, children: block.items.map((item, index) => {
|
|
@@ -324,7 +390,53 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
|
|
|
324
390
|
const human = item.message.sender === view.humanMemberId;
|
|
325
391
|
const sender = human ? t('human') : senderStatus?.member.handle ?? item.message.sender;
|
|
326
392
|
const turnGap = isRunGap(index > 0 ? block.items[index - 1].message.occurredAt : undefined, item.message.occurredAt);
|
|
393
|
+
const task = item.task;
|
|
394
|
+
// The entry's own line is the gate under the body, and its state
|
|
395
|
+
// leads that line: ownership, status, and unread answer the reader
|
|
396
|
+
// where they are already reading. A reply inside a Thread has none
|
|
397
|
+
// of it — the Thread page owns that surface.
|
|
398
|
+
const owners = task === undefined ? [] : ownersByTask.get(task.taskRef) ?? [];
|
|
399
|
+
const unread = unreadByThread.get(item.thread.threadRef) ?? 0;
|
|
327
400
|
// A turn divider already labels the gapped entry; its row stays chrome-free.
|
|
328
|
-
return _jsxs(Fragment, { children: [turnGap && _jsx(TeamRunDivider, { occurredAt: item.message.occurredAt }),
|
|
401
|
+
return _jsxs(Fragment, { children: [turnGap && _jsx(TeamRunDivider, { occurredAt: item.message.occurredAt }), _jsx(TeamMessage, { senderName: sender, memberId: item.message.sender, human: human, body: item.message.body, attachments: item.message.attachments, loadAttachment: getAttachment, t: t, occurredAt: item.message.occurredAt, mentionNames: mentionNamesOf(item.mentions, handleByMember), onOpenRef: openRef, onResolveTaskRefs: lookupTaskRefs, grouped: index > 0, showGroupedTime: item.message.topLevel === true && !turnGap, ...(senderStatus === undefined ? {} : { senderTitle: senderStatus.member.description }), children: item.message.topLevel && _jsx(ThreadEntryRow, { item: item, owners: owners.map(memberId => ({ memberId, name: handleByMember.get(memberId) ?? memberId })), unread: unread, t: t, onOpen: () => { selectThread(item.thread.threadRef, channelRef, task?.taskRef, item.taskNumber); } }) })] }, item.message.messageRef);
|
|
329
402
|
}) }, `run-${block.items[0].message.messageRef}`))] }) }), channel !== undefined ? _jsx(TeamComposer, { members: channelMembers, drafts: drafts, draftKey: draftKey, pending: pending, ...(statusMessage === undefined ? {} : { confirmation: statusMessage }), ...(error === undefined ? {} : { error }), onEdit: clearSendState, onSubmit: () => { void send(); }, pendingFiles: pendingFiles, onFilesChange: setPendingFiles, asTask: asTask, onAsTaskChange: setAsTask, t: t }, draftKey) : _jsx("div", {})] });
|
|
330
403
|
}
|
|
404
|
+
/**
|
|
405
|
+
* One entry's state: who is on the work, where it stands, and how much of it
|
|
406
|
+
* needs the reader. It leads the entry's own line rather than trailing the
|
|
407
|
+
* identity line, so a reader meets it where they are already reading instead of
|
|
408
|
+
* crossing the column for it — and the same placement holds whether this
|
|
409
|
+
* Message opened its run or continued one, where an identity line would have
|
|
410
|
+
* carried nothing else. The unread capsule is the only member a taskless
|
|
411
|
+
* discussion can carry: it needs no Task.
|
|
412
|
+
*/
|
|
413
|
+
function ThreadStateCluster({ task, owners, unread, t }) {
|
|
414
|
+
if (task === undefined && unread === 0)
|
|
415
|
+
return null;
|
|
416
|
+
return _jsxs("span", { className: css.stateCluster, children: [task !== undefined && _jsx(TeamAvatarStack, { owners: owners, label: t('claimers', { names: owners.map(owner => `@${owner.name}`).join(', ') }) }), task !== undefined && _jsx(TeamStateDot, { size: 8, state: taskStatusDot(task.status) }), task !== undefined && _jsx("span", { className: css.statusWord, children: formatTaskStatus(task.status, t) }), _jsx(TeamCountBadge, { count: unread })] });
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* The gate under one top-level Message: the single row that opens its Thread,
|
|
420
|
+
* and the one line that says what stands there. State leads it, then what the
|
|
421
|
+
* entry is: a Task entry keeps its number so the status word never floats free
|
|
422
|
+
* of the Task it describes, follow-up activity adds when the work last moved,
|
|
423
|
+
* and an unanswered Thread says only 回复. The instant uses the Inbox's own
|
|
424
|
+
* 今天/昨天 form with the precise local time on the control's title, and an
|
|
425
|
+
* unread Thread says so in the control's label rather than only in pixels.
|
|
426
|
+
*/
|
|
427
|
+
function ThreadEntryRow({ item, owners, unread, t, onOpen }) {
|
|
428
|
+
const taskNumber = item.taskNumber;
|
|
429
|
+
const followUp = followUpAt(item);
|
|
430
|
+
const label = taskNumber !== undefined
|
|
431
|
+
? t('taskLabel', { number: taskNumber })
|
|
432
|
+
: followUp === undefined ? t('replyAction') : t('threadLabel');
|
|
433
|
+
const text = followUp === undefined ? label : `${label} · ${t('recentActivity', { time: formatInboxTime(followUp, t) })}`;
|
|
434
|
+
const openLabel = taskNumber === undefined
|
|
435
|
+
? unread > 0 ? t('openThreadUnread', { count: unread }) : t('openThread')
|
|
436
|
+
: unread > 0 ? t('openTaskUnread', { number: taskNumber, count: unread }) : t('openTask', { number: taskNumber });
|
|
437
|
+
// The state is a label, not a control: it stays outside the button so the
|
|
438
|
+
// owner stack keeps its own accessible name — a labeled button prunes its
|
|
439
|
+
// descendants from the accessibility tree — and so the control's name says
|
|
440
|
+
// exactly what clicking it does.
|
|
441
|
+
return _jsxs("span", { className: css.entryLine, "data-thread-entry": "", children: [_jsx(ThreadStateCluster, { task: item.task, owners: owners, unread: unread, t: t }), _jsxs("button", { type: "button", className: css.entryRow, "aria-label": openLabel, ...(followUp === undefined ? {} : { title: formatAbsoluteTime(followUp) }), onClick: onOpen, children: [_jsx("span", { children: text }), _jsx("span", { className: css.entryArrow, "aria-hidden": "true", children: _jsx(IconChevronRightOutline14, { size: 12 }) })] })] });
|
|
442
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamChannelsPanel.d.ts","sourceRoot":"","sources":["../../../src/client/TeamChannelsPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EAEzB,mBAAmB,EAMpB,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kDAAkD,CAAA;AAEnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"TeamChannelsPanel.d.ts","sourceRoot":"","sources":["../../../src/client/TeamChannelsPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EAEzB,mBAAmB,EAMpB,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kDAAkD,CAAA;AAEnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAelD,UAAU,sBAAsB;IAC9B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IACrD,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAA;IACvD,wGAAwG;IACxG,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,kBAAkB,CAAC,CAAA;IAC/D,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,cAAc,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,CAAA;IAC3D,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IACrD,QAAQ,CAAC,mBAAmB,EAAE,gBAAgB,CAAC,qBAAqB,CAAC,CAAA;IACrE,QAAQ,CAAC,cAAc,EAAE,SAAS,yBAAyB,EAAE,CAAA;IAC7D,QAAQ,CAAC,kBAAkB,CAAC,EAAE,mBAAmB,CAAA;IACjD,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAA;CAClC;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,+BAsM9D"}
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { Button, IconArchiveOutline20, IconEditOutline16, IconPlusOutline16, Input, Modal, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
4
|
import { TeamPresenceDot } from "./TeamPresenceDot.js";
|
|
5
|
+
import { TeamMemberRow } from "./TeamMemberRow.js";
|
|
5
6
|
import { MultiMenuField } from "./multi-menu-field.js";
|
|
6
7
|
import { SortableRow, useSidebarRowDrag } from "./sidebar-drag.js";
|
|
7
8
|
import { moveSidebarItem, useSidebarOrder } from "./sidebar-order.js";
|
|
@@ -152,7 +153,7 @@ export function TeamChannelsPanel(props) {
|
|
|
152
153
|
return next;
|
|
153
154
|
});
|
|
154
155
|
setPendingCreate(undefined);
|
|
155
|
-
}, triggerEmptyLabel: t('membersPickerEmpty'), formatCount: count => t('membersPickerCount', { count }) }), error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] }) }), _jsxs(TeamSidebarSection, { title: t('channels'), open: sectionOpen, onToggle: open => { setSidebarSectionOpen(workspaceId, 'channels', open); }, actions: (_jsx(Tooltip, { label: t('addChannel'), delayMs: 500, children: _jsx("button", { ref: triggerRef, type: "button", className: css.iconButton, "aria-label": t('addChannel'), onClick: () => { setError(undefined); setFormOpen(true); }, children: _jsx(IconPlusOutline16, { size: 14 }) }) })), children: [loading && view === undefined && _jsx("p", { className: css.emptyState, children: t('loadingChannels') }), !loading &&
|
|
156
|
+
}, triggerEmptyLabel: t('membersPickerEmpty'), formatCount: count => t('membersPickerCount', { count }) }), error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] }) }), _jsxs(TeamSidebarSection, { title: t('channels'), open: sectionOpen, onToggle: open => { setSidebarSectionOpen(workspaceId, 'channels', open); }, actions: (_jsx(Tooltip, { label: t('addChannel'), delayMs: 500, children: _jsx("button", { ref: triggerRef, type: "button", className: css.iconButton, "aria-label": t('addChannel'), onClick: () => { setError(undefined); setFormOpen(true); }, children: _jsx(IconPlusOutline16, { size: 14 }) }) })), children: [loading && view === undefined && _jsx("p", { className: css.emptyState, children: t('loadingChannels') }), !loading && error === undefined && view !== undefined && view.channels.length === 0 && _jsx("p", { className: css.emptyState, children: t('emptyChannels') }), _jsx("div", { className: css.channelList, children: orderedChannels.map(channel => {
|
|
156
157
|
const joined = membership.get(channel.channelRef) ?? new Set();
|
|
157
158
|
return (_jsx(SortableRow, { drag: drag, orderKey: channel.channelRef, children: _jsx(ChannelRow, { channel: channel, members: members, joinedIds: joined, selected: selectedChannelRef === channel.channelRef, updateChannel: updateChannel, archiveChannel: props.archiveChannel, joinChannel: props.joinChannel, removeChannelMember: props.removeChannelMember, onCommitted: () => { void refresh(); }, selectChannel: selectChannel, t: t }) }, channel.channelRef));
|
|
158
159
|
}) })] }), !formOpen && error !== undefined && _jsx("p", { className: css.error, role: "alert", children: error })] }));
|
|
@@ -226,8 +227,14 @@ function ChannelEditorDialog({ channel, members, joinedIds, updateChannel, joinC
|
|
|
226
227
|
return (_jsx(Modal, { open: true, onClose: onClose, title: t('editChannel'), description: `# ${channel.name}`, closeLabel: t('close'), contentClassName: createCss.dialogContent, footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: saving, onClick: onClose, children: t('cancel') }), _jsx(Button, { type: "submit", form: "team-channel-edit-form", variant: "primary", disabled: saving || !dirty || name.trim() === '', children: saving ? t('editSaving') : t('editSave') })] }), children: _jsxs("form", { id: "team-channel-edit-form", className: createCss.form, onSubmit: event => { void submit(event); }, children: [_jsxs("label", { className: createCss.field, children: [_jsx("span", { children: t('channelName') }), _jsx(Input, { className: createCss.input, value: name, onChange: event => { setName(event.target.value); pendingRequest.current = undefined; }, disabled: saving, autoFocus: true })] }), _jsxs("label", { className: createCss.field, children: [_jsxs("span", { children: [t('channelDescription'), t('optionalSuffix')] }), _jsx(Input, { className: createCss.input, value: description, placeholder: t('agentDescriptionPlaceholder'), onChange: event => { setDescription(event.target.value); pendingRequest.current = undefined; }, disabled: saving })] }), _jsxs("fieldset", { className: createCss.memberPicker, disabled: saving, children: [_jsx("legend", { children: t('channelMembersSection') }), members.map(status => {
|
|
227
228
|
const joined = joinedIds.has(status.member.memberId);
|
|
228
229
|
const rowPending = membership.pending.has(status.member.memberId);
|
|
229
|
-
|
|
230
|
+
// Same membership law as the Channel page roster: joining needs an
|
|
231
|
+
// active Member, leaving only needs the fact.
|
|
232
|
+
const disabled = rowPending || (!joined && status.availability !== 'active');
|
|
230
233
|
const rowError = membership.errors.get(status.member.memberId);
|
|
231
|
-
return
|
|
234
|
+
return _jsx(TeamMemberRow, { status: status, action: {
|
|
235
|
+
label: rowPending ? t('membershipUpdating') : joined ? t('removeFromChannel') : t('addToChannel'),
|
|
236
|
+
disabled,
|
|
237
|
+
onSelect: () => { void membership.change({ workspaceId: channel.workspaceId, channelRef: channel.channelRef, memberId: status.member.memberId, joined }); },
|
|
238
|
+
}, ...(rowError === undefined ? {} : { error: rowError }), t: t }, status.member.memberId);
|
|
232
239
|
})] }), error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] }) }));
|
|
233
240
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamComposer.d.ts","sourceRoot":"","sources":["../../../src/client/TeamComposer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,2BAA2B,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAEpG,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"TeamComposer.d.ts","sourceRoot":"","sources":["../../../src/client/TeamComposer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,2BAA2B,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAEpG,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAqD/D,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,EAAE,EAAE;IAClM,QAAQ,CAAC,OAAO,EAAE,SAAS,2BAA2B,EAAE,CAAA;IACxD,oGAAoG;IACpG,QAAQ,CAAC,iBAAiB,CAAC,EAAE,WAAW,CAAC,iBAAiB,CAAC,CAAA;IAC3D;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAA;IAC/B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAA;IAC/B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,kFAAkF;IAClF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,gGAAgG;IAChG,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,IAAI,EAAE,CAAA;IACvC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,KAAK,IAAI,CAAA;IACzD,gFAAgF;IAChF,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAA;IACnD,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC,+BAiUA"}
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from 'react';
|
|
3
3
|
import { IconChecklistOutline14, IconPaperclipOutline16, IconSendOutline16, Tooltip, useAnchoredMaxHeight, useDismissOnOutsidePointer } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
4
|
import { TeamPresenceDot } from "./TeamPresenceDot.js";
|
|
5
|
+
import { allMentionMembers, containsAllMention, containsMention, mentionedMemberIds } from "./team-formatters.js";
|
|
5
6
|
import css from './composer.module.css';
|
|
6
7
|
import { formatByteSize } from "./attachment-preview.js";
|
|
7
8
|
function findMention(draft, caret) {
|
|
@@ -18,12 +19,6 @@ function findMention(draft, caret) {
|
|
|
18
19
|
return undefined;
|
|
19
20
|
return { start: at, end: caret, query: beforeCaret.slice(at + 1) };
|
|
20
21
|
}
|
|
21
|
-
function escapeRegExp(value) {
|
|
22
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
23
|
-
}
|
|
24
|
-
function containsMention(draft, handle) {
|
|
25
|
-
return new RegExp(`(?:^|[^\\p{L}\\p{N}_])@${escapeRegExp(handle)}(?=$|[^\\p{L}\\p{N}_])`, 'u').test(draft);
|
|
26
|
-
}
|
|
27
22
|
function mentionCandidates(members, query) {
|
|
28
23
|
const normalized = query.toLocaleLowerCase();
|
|
29
24
|
return members.filter(status => status.presence !== 'unavailable'
|
|
@@ -37,13 +32,6 @@ function rankMentionCandidates(candidates, followers) {
|
|
|
37
32
|
return candidates;
|
|
38
33
|
return [...candidates].sort((left, right) => Number(followers.has(right.member.memberId)) - Number(followers.has(left.member.memberId)));
|
|
39
34
|
}
|
|
40
|
-
/** Every member a manual handle pick could reach: the @all expansion snapshot. */
|
|
41
|
-
function allMentionMembers(members) {
|
|
42
|
-
return members.filter(status => status.presence !== 'unavailable' && status.member.state !== 'inactive' && status.member.state !== 'archived');
|
|
43
|
-
}
|
|
44
|
-
function containsAllMention(draft) {
|
|
45
|
-
return /(?:^|[^\p{L}\p{N}_])@all(?=$|[^\p{L}\p{N}_])/u.test(draft);
|
|
46
|
-
}
|
|
47
35
|
/** One object URL per draft file; revoked when the draft is removed. */
|
|
48
36
|
const draftPreviewUrls = new WeakMap();
|
|
49
37
|
function draftPreviewUrl(file) {
|
|
@@ -121,15 +109,17 @@ export function TeamComposer({ members, followerMemberIds, drafts, draftKey, pen
|
|
|
121
109
|
return;
|
|
122
110
|
inputRef.current?.focus({ preventScroll: true });
|
|
123
111
|
}, [confirmation, pending]);
|
|
112
|
+
// Handle lookup for both directions of the same judgement: whether a picked
|
|
113
|
+
// recipient is still spelled in the text, and which Members the text names.
|
|
114
|
+
const memberHandles = new Map(members.map(status => [status.member.memberId, status.member.handle]));
|
|
124
115
|
const pruneRecipients = (nextDraft) => {
|
|
125
116
|
// An @all marker stands for its expansion snapshot: the member handles it
|
|
126
117
|
// stands for are not in the text, so text-based pruning must stand down.
|
|
127
118
|
if (containsAllMention(nextDraft))
|
|
128
119
|
return;
|
|
129
|
-
const knownMembers = new Map(members.map(status => [status.member.memberId, status.member]));
|
|
130
120
|
const next = new Set([...recipients].filter(memberId => {
|
|
131
|
-
const
|
|
132
|
-
return
|
|
121
|
+
const handle = memberHandles.get(memberId);
|
|
122
|
+
return handle !== undefined && containsMention(nextDraft, handle);
|
|
133
123
|
}));
|
|
134
124
|
if (next.size !== recipients.size)
|
|
135
125
|
drafts.writeRecipients(draftKey, next);
|
|
@@ -143,6 +133,10 @@ export function TeamComposer({ members, followerMemberIds, drafts, draftKey, pen
|
|
|
143
133
|
return;
|
|
144
134
|
pruneRecipients(draft);
|
|
145
135
|
}, [draft, recipients, members]);
|
|
136
|
+
// The notify row reports what the Host will resolve from this draft, not just
|
|
137
|
+
// what the mention menu picked: an authored `@Handle` delivers exactly like a
|
|
138
|
+
// pick, and `@all` stands for the menu's expansion.
|
|
139
|
+
const notifiedIds = [...new Set([...recipients, ...mentionedMemberIds(draft, members)])].sort();
|
|
146
140
|
const updateMention = (nextDraft, caret) => {
|
|
147
141
|
const match = findMention(nextDraft, caret);
|
|
148
142
|
setMention(match);
|
|
@@ -252,7 +246,7 @@ export function TeamComposer({ members, followerMemberIds, drafts, draftKey, pen
|
|
|
252
246
|
return option.kind === 'all'
|
|
253
247
|
? _jsxs("button", { id: optionId, type: "button", role: "option", "aria-selected": selected, className: css.mentionOption, ref: selected ? activeOptionRef : undefined, onMouseDown: event => { event.preventDefault(); }, onClick: () => { selectOption(option); }, children: [_jsx("span", { className: css.mentionAllDot, "aria-hidden": "true" }), _jsx("span", { className: css.mentionName, children: "@all" }), _jsx("span", { className: css.mentionDescription, children: t('mentionAll', { count: allCount }) })] }, "all")
|
|
254
248
|
: _jsxs("button", { id: optionId, type: "button", role: "option", "aria-selected": selected, className: css.mentionOption, ref: selected ? activeOptionRef : undefined, onMouseDown: event => { event.preventDefault(); }, onClick: () => { selectOption(option); }, children: [_jsx(TeamPresenceDot, { status: option.status, t: t }), _jsxs("span", { className: css.mentionName, children: ["@", option.status.member.handle] }), _jsx("span", { className: css.mentionDescription, children: option.status.member.description })] }, option.status.member.memberId);
|
|
255
|
-
}) }), _jsx("textarea", { ref: inputRef, "aria-label": t('messageDraft'), "aria-autocomplete": "list", "aria-controls": menuOpen ? listId : undefined, "aria-activedescendant": menuOpen && activeOption !== undefined ? activeOption.kind === 'all' ? `${listId}-all` : `${listId}-${activeOption.status.member.memberId}` : undefined, "aria-expanded": menuOpen, value: draft, readOnly: pending, placeholder: placeholder ?? t('messagePlaceholder'), rows: 1, onChange: onChange, onPaste: onPaste, onKeyDown: onKeyDown, onSelect: event => { updateMention(event.currentTarget.value, event.currentTarget.selectionStart ?? event.currentTarget.value.length); }, onCompositionStart: () => { composingRef.current = true; }, onCompositionEnd: () => { setTimeout(() => { composingRef.current = false; }, 10); } })] }),
|
|
249
|
+
}) }), _jsx("textarea", { ref: inputRef, "aria-label": t('messageDraft'), "aria-autocomplete": "list", "aria-controls": menuOpen ? listId : undefined, "aria-activedescendant": menuOpen && activeOption !== undefined ? activeOption.kind === 'all' ? `${listId}-all` : `${listId}-${activeOption.status.member.memberId}` : undefined, "aria-expanded": menuOpen, value: draft, readOnly: pending, placeholder: placeholder ?? t('messagePlaceholder'), rows: 1, onChange: onChange, onPaste: onPaste, onKeyDown: onKeyDown, onSelect: event => { updateMention(event.currentTarget.value, event.currentTarget.selectionStart ?? event.currentTarget.value.length); }, onCompositionStart: () => { composingRef.current = true; }, onCompositionEnd: () => { setTimeout(() => { composingRef.current = false; }, 10); } })] }), notifiedIds.length > 0 && _jsx("p", { className: css.notifyRow, "data-team-notify": true, children: t('composerNotify', { ids: notifiedIds.map(memberId => `@${memberHandles.get(memberId) ?? memberId}`).join(', ') }) }), onFilesChange !== undefined && pendingFiles !== undefined && pendingFiles.length > 0 && (_jsx("ul", { className: css.fileChips, "aria-label": t('attachFiles'), children: pendingFiles.map((file, index) => {
|
|
256
250
|
const previewUrl = draftPreviewUrl(file);
|
|
257
251
|
return _jsxs("li", { className: `${css.fileChip} ${previewUrl !== undefined ? css.imageChip : ''}`, children: [previewUrl !== undefined && _jsx("img", { src: previewUrl, alt: "", className: css.imageChipPreview }), _jsxs("span", { className: css.fileChipName, title: file.name, children: [file.name, _jsx("span", { className: css.fileChipSize, children: formatByteSize(file.size) })] }), _jsx("button", { type: "button", className: css.fileChipRemove, "aria-label": t('removeFile', { name: file.name }), disabled: pending, onClick: () => {
|
|
258
252
|
const url = draftPreviewUrls.get(file);
|
|
@@ -18,7 +18,7 @@ export function TeamConversation({ t, useWorkspaces, navigation, drafts, putAtta
|
|
|
18
18
|
return _jsx(TeamThreadPage, { workspaceId: current.workspaceId, putAttachment: putAttachment, threadRef: navigationState.threadRef, backToWorkspace: backToWorkspace, selectChannel: selectChannel, selectThread: selectThread, resolveTaskRefs: resolveTaskRefs, ...(navigationState.channelRef === undefined ? {} : { channelRef: navigationState.channelRef }), ...(navigationState.taskRef === undefined ? {} : { taskRef: navigationState.taskRef }), ...(navigationState.taskNumber === undefined ? {} : { taskNumber: navigationState.taskNumber }), drafts: drafts, getAttachment: getAttachment, readThread: readThread, loadChannels: loadChannels, loadThreadHistory: loadThreadHistory, threadObservations: threadObservations, subscribeChanges: subscribeChanges, loadMembers: loadMembers, reply: reply, changeTask: changeTask, promoteThread: promoteThread, t: t }, navigationState.threadRef);
|
|
19
19
|
}
|
|
20
20
|
if (current !== undefined && navigationState.channelRef !== undefined) {
|
|
21
|
-
return _jsx(TeamChannelPage, { workspaceId: current.workspaceId, channelRef: navigationState.channelRef, drafts: drafts, putAttachment: putAttachment, getAttachment: getAttachment, loadChannels: loadChannels, subscribeChanges: subscribeChanges, loadMembers: loadMembers, sendMessage: sendMessage, joinChannel: joinChannel, removeChannelMember: removeChannelMember, selectThread: selectThread, selectChannel: selectChannel, backToChannels: backToChannels, resolveTaskRefs: resolveTaskRefs, t: t }, navigationState.channelRef);
|
|
21
|
+
return _jsx(TeamChannelPage, { workspaceId: current.workspaceId, channelRef: navigationState.channelRef, drafts: drafts, putAttachment: putAttachment, getAttachment: getAttachment, loadChannels: loadChannels, subscribeChanges: subscribeChanges, loadMembers: loadMembers, loadInbox: loadInbox, sendMessage: sendMessage, joinChannel: joinChannel, removeChannelMember: removeChannelMember, selectThread: selectThread, selectChannel: selectChannel, backToChannels: backToChannels, resolveTaskRefs: resolveTaskRefs, t: t }, navigationState.channelRef);
|
|
22
22
|
}
|
|
23
23
|
const welcome = current === undefined
|
|
24
24
|
? { eyebrow: t('teamMode'), title: t('team'), body: t('empty') }
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one count capsule. The Human Inbox entry, the Channel feed's Thread
|
|
3
|
+
* entry, and the Inbox queue row all answer the same question — how much is
|
|
4
|
+
* waiting here — so they wear the same box rather than one hand-written copy
|
|
5
|
+
* of the same declarations per surface. Three copies is how the feed's digit
|
|
6
|
+
* ended up on a different line box from the other two.
|
|
7
|
+
*
|
|
8
|
+
* Zero is the absence of a count rather than a capsule reading zero, which is
|
|
9
|
+
* the rule every caller wants and therefore the component's own.
|
|
10
|
+
*/
|
|
11
|
+
export declare function TeamCountBadge({ count, tone, label, className }: {
|
|
12
|
+
readonly count: number;
|
|
13
|
+
/** `hairline` is unread that merely arrived; the solid fill is unread that names this reader. */
|
|
14
|
+
readonly tone?: 'solid' | 'hairline' | undefined;
|
|
15
|
+
/** The accessible name while the count is the only thing saying it; omit it when the surrounding control already carries the number. */
|
|
16
|
+
readonly label?: string | undefined;
|
|
17
|
+
/** Placement belongs to the surface hanging the capsule: the narrow rail pins it to the icon's corner. */
|
|
18
|
+
readonly className?: string | undefined;
|
|
19
|
+
}): import("react").JSX.Element | null;
|
|
20
|
+
//# sourceMappingURL=TeamCountBadge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TeamCountBadge.d.ts","sourceRoot":"","sources":["../../../src/client/TeamCountBadge.tsx"],"names":[],"mappings":"AAKA;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,IAAc,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE;IAC1E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,iGAAiG;IACjG,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,CAAA;IAChD,wIAAwI;IACxI,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,0GAA0G;IAC1G,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACxC,sCASA"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import css from './countBadge.module.css';
|
|
3
|
+
/** Every count the Human reads is capped at this, so one wide number never widens the capsule. */
|
|
4
|
+
const COUNT_CAP = 99;
|
|
5
|
+
/**
|
|
6
|
+
* The one count capsule. The Human Inbox entry, the Channel feed's Thread
|
|
7
|
+
* entry, and the Inbox queue row all answer the same question — how much is
|
|
8
|
+
* waiting here — so they wear the same box rather than one hand-written copy
|
|
9
|
+
* of the same declarations per surface. Three copies is how the feed's digit
|
|
10
|
+
* ended up on a different line box from the other two.
|
|
11
|
+
*
|
|
12
|
+
* Zero is the absence of a count rather than a capsule reading zero, which is
|
|
13
|
+
* the rule every caller wants and therefore the component's own.
|
|
14
|
+
*/
|
|
15
|
+
export function TeamCountBadge({ count, tone = 'solid', label, className }) {
|
|
16
|
+
if (count <= 0)
|
|
17
|
+
return null;
|
|
18
|
+
const text = count > COUNT_CAP ? `${COUNT_CAP}+` : String(count);
|
|
19
|
+
const classes = `${css.badge}${tone === 'hairline' ? ` ${css.hairline}` : ''}${className === undefined ? '' : ` ${className}`}`;
|
|
20
|
+
// Tests and the browser journey read the capsule by this attribute rather
|
|
21
|
+
// than by a CSS-module hash, which changes whenever a rule is renamed.
|
|
22
|
+
return label === undefined
|
|
23
|
+
? _jsx("span", { className: classes, "data-team-count-badge": tone, "aria-hidden": "true", children: text })
|
|
24
|
+
: _jsx("span", { className: classes, "data-team-count-badge": tone, role: "img", "aria-label": label, title: label, children: text });
|
|
25
|
+
}
|
|
@@ -9,12 +9,16 @@ interface TeamInboxPageProps {
|
|
|
9
9
|
readonly t: TeamConversationProps['t'];
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* The Human
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* The Human Inbox: one Inbox call per visible Workspace, rendering the Host's
|
|
13
|
+
* two slices — the unread queue (「需要我」, mentions counted inside it rather
|
|
14
|
+
* than alone) and the 「最近活跃」 tail of Threads the reader took part in.
|
|
15
|
+
* Both merge across every Workspace into one recency-ordered list, newest
|
|
16
|
+
* first, since a queue is read by recency rather than by Workspace.
|
|
15
17
|
* Opening the page never acknowledges anything — only a durable Thread read
|
|
16
|
-
* consumes a mention marker, so rows and the badge
|
|
17
|
-
* opened through the existing auto-ack path.
|
|
18
|
+
* advances the watermark and consumes a mention marker, so rows and the badge
|
|
19
|
+
* drop after the Thread is opened through the existing auto-ack path. A Thread
|
|
20
|
+
* holding unread is only ever in the queue: the Host already excludes it from
|
|
21
|
+
* the tail, and this page never re-derives that judgement.
|
|
18
22
|
*/
|
|
19
23
|
export declare function TeamInboxPage({ useWorkspaces, loadInbox, subscribeChanges, selectWorkspace, selectThread, t }: TeamInboxPageProps): import("react").JSX.Element;
|
|
20
24
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamInboxPage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamInboxPage.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"TeamInboxPage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamInboxPage.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAavD,UAAU,kBAAkB;IAC1B,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,SAAS,EAAE,qBAAqB,CAAC,WAAW,CAAC,CAAA;IACtD,mGAAmG;IACnG,QAAQ,CAAC,gBAAgB,EAAE,qBAAqB,CAAC,kBAAkB,CAAC,CAAA;IACpE,QAAQ,CAAC,eAAe,EAAE,qBAAqB,CAAC,iBAAiB,CAAC,CAAA;IAClE,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC;AAyBD;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,EAAE,aAAa,EAAE,SAAS,EAAE,gBAAgB,EAAE,eAAe,EAAE,YAAY,EAAE,CAAC,EAAE,EAAE,kBAAkB,+BA0GjI"}
|