@taskforcehq/taskforce 0.3.327 → 0.3.328
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/dist/Taskforce.module.css +24 -53
- package/dist/components/activity/EntityActivityTimeline.js +15 -5
- package/dist/components/context/AttachmentPreviewHost.js +1 -1
- package/dist/components/context/ContextAttachmentManager.d.ts +3 -0
- package/dist/components/context/ContextAttachmentManager.js +116 -20
- package/dist/components/features/planning/PlanningEntitySummary.d.ts +3 -1
- package/dist/components/features/planning/PlanningEntitySummary.js +3 -2
- package/dist/components/features/planning/PlanningLinkPicker.d.ts +3 -1
- package/dist/components/features/planning/PlanningLinkPicker.js +5 -3
- package/dist/components/features/planning/PlanningModule.js +26 -9
- package/dist/components/features/planning/PlanningVisualIdentity.d.ts +14 -0
- package/dist/components/features/planning/PlanningVisualIdentity.js +106 -0
- package/dist/components/features/planning/planningWorkspaceModel.d.ts +11 -0
- package/dist/components/task/TaskActionHeader.d.ts +2 -1
- package/dist/components/task/TaskActionHeader.js +2 -2
- package/dist/components/task/TaskCard.js +37 -4
- package/dist/components/task/TaskContextUpload.d.ts +3 -0
- package/dist/components/task/TaskContextUpload.js +2 -2
- package/dist/components/task/TaskForm.d.ts +4 -0
- package/dist/components/task/TaskForm.js +32 -7
- package/dist/components/task/TaskImageReviews.js +16 -16
- package/dist/components/task/TaskStatusActions.js +11 -2
- package/dist/components/task/TaskWorkstreamPicker.js +2 -1
- package/dist/components/ui/DocumentTypeIcon.d.ts +7 -0
- package/dist/components/ui/DocumentTypeIcon.js +8 -0
- package/dist/components/ui/TaxonomyDropdown.d.ts +9 -1
- package/dist/components/ui/TaxonomyDropdown.js +30 -3
- package/dist/components/views/StandaloneLayout.js +62 -9
- package/dist/components/views/WidgetView.js +8 -5
- package/dist/components/views/panels/PlanningDrawer.d.ts +7 -1
- package/dist/components/views/panels/PlanningDrawer.js +48 -44
- package/dist/config/envSchema.js +1 -0
- package/dist/config/localServiceCloudTarget.d.ts +10 -0
- package/dist/config/localServiceCloudTarget.js +76 -0
- package/dist/core/AiProfileService.d.ts +1 -0
- package/dist/core/AiProfileService.js +2 -1
- package/dist/core/AttachmentLinkService.js +7 -4
- package/dist/core/DeletedTaskLifecycleService.d.ts +4 -0
- package/dist/core/DeletedTaskLifecycleService.js +21 -0
- package/dist/core/McpTokenService.d.ts +4 -0
- package/dist/core/McpTokenService.js +52 -0
- package/dist/core/TaskAttachmentLinksDurableMutationService.d.ts +5 -0
- package/dist/core/TaskAttachmentLinksDurableMutationService.js +20 -11
- package/dist/core/Taskforce.d.ts +63 -1
- package/dist/core/Taskforce.js +558 -73
- package/dist/core/types.d.ts +2 -1
- package/dist/documentReviews/DocumentReviewCommentStore.d.ts +11 -0
- package/dist/documentReviews/DocumentReviewCommentStore.js +35 -2
- package/dist/documentReviews/service.d.ts +4 -0
- package/dist/documentReviews/service.js +141 -46
- package/dist/hooks/tasks/useTaskFormActions.d.ts +3 -0
- package/dist/hooks/tasks/useTaskFormActions.js +33 -7
- package/dist/hooks/useTaskforce.d.ts +59 -50
- package/dist/hooks/useTaskforce.js +103 -1
- package/dist/mcp/canonicalAssetHelpers.d.ts +13 -0
- package/dist/mcp/canonicalAssetHelpers.js +39 -5
- package/dist/mcp/collaborationRegistrar.js +15 -13
- package/dist/mcp/documentAssetRegistrar.js +16 -9
- package/dist/mcp/localServiceProxy.js +4 -1
- package/dist/mcp/runtime.d.ts +16 -0
- package/dist/mcp/runtime.js +566 -171
- package/dist/mcp/taskAttachmentCommandAdapter.d.ts +2 -0
- package/dist/mcp/taskAttachmentCommandAdapter.js +14 -8
- package/dist/mcp/taskPlanningRegistrar.js +42 -34
- package/dist/mcp/toolRegistry.js +2 -2
- package/dist/migrations/taskSchemaMigrations.js +35 -0
- package/dist/runtime/appGateDefinitions.d.ts +2 -2
- package/dist/runtime/appGateDefinitions.js +2 -2
- package/dist/server/appAccess.d.ts +12 -0
- package/dist/server/appAccess.js +21 -0
- package/dist/server/authorizedWorkspaceAccess.d.ts +12 -0
- package/dist/server/authorizedWorkspaceAccess.js +40 -0
- package/dist/server/cloudCollectionRead.d.ts +25 -0
- package/dist/server/cloudCollectionRead.js +120 -0
- package/dist/server/cloudCollectionReadPool.d.ts +52 -0
- package/dist/server/cloudCollectionReadPool.js +140 -0
- package/dist/server/cloudCollectionReadWorker.d.ts +1 -0
- package/dist/server/cloudCollectionReadWorker.js +36 -0
- package/dist/server/index.d.ts +3 -11
- package/dist/server/index.js +53 -40
- package/dist/server/localServiceLease.d.ts +3 -0
- package/dist/server/localServiceLease.js +9 -1
- package/dist/server/routes/documents.d.ts +5 -2
- package/dist/server/routes/documents.js +160 -13
- package/dist/server/routes/durableTaskHttpRouters.d.ts +2 -0
- package/dist/server/routes/durableTaskHttpRouters.js +2 -2
- package/dist/server/routes/localService.js +2 -0
- package/dist/server/routes/primitives.d.ts +2 -0
- package/dist/server/routes/shared.d.ts +24 -0
- package/dist/server/routes/shared.js +114 -18
- package/dist/server/routes/sync.js +22 -0
- package/dist/server/routes/syncV3FeedRoutes.js +4 -2
- package/dist/server/routes/syncV3LocalCaptureRoutes.js +20 -2
- package/dist/server/routes/syncV3OperationRoutes.js +17 -2
- package/dist/server/routes/tasks.js +199 -89
- package/dist/server/routes.js +35 -7
- package/dist/service/localServiceCli.js +36 -25
- package/dist/service/localServiceClientLifecycle.js +67 -6
- package/dist/shared/planningIdentity.d.ts +20 -0
- package/dist/shared/planningIdentity.js +79 -0
- package/dist/storage/postgresAdapter.js +4 -0
- package/dist/storage/postgresRequestDiagnostics.d.ts +8 -0
- package/dist/storage/postgresRequestDiagnostics.js +24 -0
- package/dist/storage/workspaceAssetStore.d.ts +32 -0
- package/dist/storage/workspaceAssetStore.js +70 -0
- package/dist/sync/coordinator/localSyncExecutionPermitStore.d.ts +16 -0
- package/dist/sync/coordinator/localSyncExecutionPermitStore.js +70 -22
- package/dist/sync/coordinator/workspaceSyncTransportGateway.js +4 -2
- package/dist/sync/engine/workspaceSyncEngineRemoteServices.d.ts +5 -0
- package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.d.ts +1 -1
- package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.js +15 -0
- package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.js +26 -3
- package/dist/sync/engine/workspaceSyncV2ChangeDispatcher.js +2 -0
- package/dist/sync/syncApplyHandlers.d.ts +3 -1
- package/dist/sync/syncApplyHandlers.js +29 -0
- package/dist/sync/syncService.js +9 -0
- package/dist/sync/taskSyncPayload.d.ts +1 -0
- package/dist/sync/taskSyncPayload.js +6 -0
- package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.d.ts +1 -0
- package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.js +21 -3
- package/dist/sync/v3/durableTaskHttpBulkMutationRouter.js +17 -4
- package/dist/sync/v3/localInitiativeOutboxService.js +4 -2
- package/dist/sync/v3/localOutboxDispatcher.js +5 -1
- package/dist/sync/v3/localTaskAttachmentLinksOutboxHandler.js +12 -2
- package/dist/sync/v3/localTaskAttachmentLinksOutboxService.d.ts +1 -0
- package/dist/sync/v3/localTaskAttachmentLinksOutboxService.js +15 -3
- package/dist/sync/v3/localWorkstreamOutboxService.js +4 -2
- package/dist/sync/v3/syncDurabilityStore.d.ts +8 -0
- package/dist/sync/v3/syncDurabilityStore.js +171 -33
- package/dist/sync/v3/taskAttachmentLinksMutationService.d.ts +1 -0
- package/dist/sync/v3/taskAttachmentLinksMutationService.js +18 -2
- package/dist/sync/v3/taskMetadataCoexistence.js +4 -0
- package/dist/sync/v3/workspaceSyncV2ProjectionExclusions.js +1 -9
- package/dist/sync/v3/workspaceSyncV3CloudOperationHandler.js +1 -0
- package/dist/sync/v3/workspaceSyncV3CombinedFeedOrchestrator.js +7 -1
- package/dist/sync/v3/workspaceSyncV3CombinedLocalApply.js +15 -3
- package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.d.ts +4 -0
- package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.js +29 -6
- package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.d.ts +1 -0
- package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.js +29 -7
- package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.js +62 -7
- package/dist/sync/v3/workspaceSyncV3OperationProtocol.d.ts +1 -0
- package/dist/sync/v3/workspaceSyncV3RepairSqlBatching.d.ts +2 -0
- package/dist/sync/v3/workspaceSyncV3RepairSqlBatching.js +10 -0
- package/dist/sync/v3/workspaceSyncV3TaskRootProjection.d.ts +1 -1
- package/dist/sync/v3/workspaceSyncV3TaskRootProjection.js +1 -1
- package/dist/sync/workspacePullFeed.d.ts +6 -1
- package/dist/sync/workspacePullFeed.js +172 -161
- package/dist/sync/workspaceSyncModel.d.ts +11 -0
- package/dist/sync/workspaceSyncModel.js +27 -2
- package/dist/types.d.ts +8 -0
- package/dist/ui/assets/{AiIdentityRosterCard-DB1BLO_j.js → AiIdentityRosterCard-MZs7lVED.js} +1 -1
- package/dist/ui/assets/{AiProfilesModule-DPxQeF9N.js → AiProfilesModule-C3i0LOxe.js} +1 -1
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-DHNYaNg6.js → AnnotatedAttachmentWorkspace-DVZk5Tln.js} +1 -1
- package/dist/ui/assets/AssetTraySortControl-Kf1pelSy.js +1 -0
- package/dist/ui/assets/ContextAttachmentManager-B8boOs6Q.js +3 -0
- package/dist/ui/assets/{DocumentWorkspace-BC7sy5TG.js → DocumentWorkspace-DXH4XUsB.js} +4 -4
- package/dist/ui/assets/EntityActivityTimeline-BtSTq0ON.js +1 -0
- package/dist/ui/assets/PlanningModule-BPVyEa-f.js +1 -0
- package/dist/ui/assets/PlanningModule-CoIR9hbV.css +1 -0
- package/dist/ui/assets/{PlansPage-BVH_NfgT.js → PlansPage-ChqgxALc.js} +1 -1
- package/dist/ui/assets/TaskContextUpload-CB3ol0sN.js +1 -0
- package/dist/ui/assets/TaskContextUpload-qmBeUw3u.css +1 -0
- package/dist/ui/assets/{TaskSettings-xVsxSa0g.js → TaskSettings-g4ECKNOz.js} +1 -1
- package/dist/ui/assets/{TaskforceAgentsModule-CkAxW19K.js → TaskforceAgentsModule-BazdUwxZ.js} +1 -1
- package/dist/ui/assets/WorkflowManagerModule-DWFspC-o.js +1 -0
- package/dist/ui/assets/index-BUplSsv_.js +7 -0
- package/dist/ui/assets/index-gz2EXuoK.css +1 -0
- package/dist/ui/assets/{vendor-icons-B8ZNsH1g.js → vendor-icons-BkFLXavV.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/utils/constants.d.ts +2 -2
- package/dist/utils/constants.js +1 -1
- package/dist/utils/contextAttachmentUpload.d.ts +2 -0
- package/dist/utils/contextAttachmentUpload.js +7 -0
- package/dist/utils/contextFiles.d.ts +1 -1
- package/dist/utils/contextFiles.js +4 -4
- package/package.json +6 -1
- package/dist/ui/assets/AssetTraySortControl-DphbyHVc.js +0 -1
- package/dist/ui/assets/ContextAttachmentManager-CMxSzHqc.js +0 -3
- package/dist/ui/assets/EntityActivityTimeline-pJ2ZjZ-k.js +0 -1
- package/dist/ui/assets/PlanningModule-CeTqXMMz.css +0 -1
- package/dist/ui/assets/PlanningModule-t5GbvVR9.js +0 -1
- package/dist/ui/assets/TaskContextUpload-Ds4LnDzV.css +0 -1
- package/dist/ui/assets/TaskContextUpload-bl4fOOUz.js +0 -1
- package/dist/ui/assets/WorkflowManagerModule-BKqIGVnF.js +0 -1
- package/dist/ui/assets/index-BzJlVnNS.css +0 -1
- package/dist/ui/assets/index-Cx7vZJHL.js +0 -7
|
@@ -959,6 +959,15 @@
|
|
|
959
959
|
transform: scale(1.1);
|
|
960
960
|
}
|
|
961
961
|
|
|
962
|
+
.restoreTaskBtn {
|
|
963
|
+
background: rgba(59, 130, 246, 0.1) !important;
|
|
964
|
+
color: #3b82f6 !important;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
.restoreTaskBtn:hover {
|
|
968
|
+
background: rgba(59, 130, 246, 0.25) !important;
|
|
969
|
+
}
|
|
970
|
+
|
|
962
971
|
.startWorkBtn {
|
|
963
972
|
background: rgba(59, 130, 246, 0.1) !important;
|
|
964
973
|
color: #3b82f6 !important;
|
|
@@ -997,15 +1006,6 @@
|
|
|
997
1006
|
}
|
|
998
1007
|
|
|
999
1008
|
|
|
1000
|
-
.archiveTaskBtn {
|
|
1001
|
-
background: rgba(59, 130, 246, 0.1) !important;
|
|
1002
|
-
color: #3b82f6 !important;
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
.archiveTaskBtn:hover {
|
|
1006
|
-
background: rgba(59, 130, 246, 0.25) !important;
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
1009
|
.bulkArchiveBtn {
|
|
1010
1010
|
display: flex;
|
|
1011
1011
|
align-items: center;
|
|
@@ -3415,6 +3415,12 @@
|
|
|
3415
3415
|
transform: scale(1.05);
|
|
3416
3416
|
}
|
|
3417
3417
|
|
|
3418
|
+
.attachmentActionBtnActive {
|
|
3419
|
+
color: var(--brand-primary);
|
|
3420
|
+
border-color: color-mix(in srgb, var(--brand-primary) 65%, white);
|
|
3421
|
+
background: color-mix(in srgb, var(--brand-primary) 32%, rgba(0, 0, 0, 0.82));
|
|
3422
|
+
}
|
|
3423
|
+
|
|
3418
3424
|
.attachmentCaptionInput,
|
|
3419
3425
|
.documentAttachmentCaptionInput {
|
|
3420
3426
|
min-width: 0;
|
|
@@ -3495,6 +3501,15 @@
|
|
|
3495
3501
|
top: calc(100% + 2px);
|
|
3496
3502
|
}
|
|
3497
3503
|
|
|
3504
|
+
.attachmentImageActionsMenu {
|
|
3505
|
+
position: fixed;
|
|
3506
|
+
z-index: calc(var(--z-overlay) + 1);
|
|
3507
|
+
top: auto;
|
|
3508
|
+
right: auto;
|
|
3509
|
+
width: 150px;
|
|
3510
|
+
max-width: calc(100vw - 16px);
|
|
3511
|
+
}
|
|
3512
|
+
|
|
3498
3513
|
.contextMenu .contextDestructiveMenuItem {
|
|
3499
3514
|
color: var(--color-error, #ef4444);
|
|
3500
3515
|
}
|
|
@@ -3652,50 +3667,6 @@
|
|
|
3652
3667
|
color: var(--text-primary, #fff);
|
|
3653
3668
|
}
|
|
3654
3669
|
|
|
3655
|
-
.documentAttachmentType {
|
|
3656
|
-
flex-shrink: 0;
|
|
3657
|
-
position: relative;
|
|
3658
|
-
width: 28px;
|
|
3659
|
-
height: 28px;
|
|
3660
|
-
display: inline-flex;
|
|
3661
|
-
align-items: center;
|
|
3662
|
-
justify-content: center;
|
|
3663
|
-
font-size: 9px;
|
|
3664
|
-
line-height: 1;
|
|
3665
|
-
font-weight: 700;
|
|
3666
|
-
letter-spacing: 0.03em;
|
|
3667
|
-
color: var(--text-muted, #94a3b8);
|
|
3668
|
-
padding: 0;
|
|
3669
|
-
}
|
|
3670
|
-
|
|
3671
|
-
.documentAttachmentTypeOutline {
|
|
3672
|
-
position: absolute;
|
|
3673
|
-
inset: 0;
|
|
3674
|
-
width: 28px;
|
|
3675
|
-
height: 28px;
|
|
3676
|
-
overflow: visible;
|
|
3677
|
-
fill: rgba(255, 255, 255, 0.04);
|
|
3678
|
-
stroke: var(--border-primary);
|
|
3679
|
-
stroke-width: 1;
|
|
3680
|
-
stroke-linecap: round;
|
|
3681
|
-
stroke-linejoin: round;
|
|
3682
|
-
}
|
|
3683
|
-
|
|
3684
|
-
.documentAttachmentTypeFoldFill {
|
|
3685
|
-
fill: color-mix(in srgb, var(--border-primary) 35%, var(--bg-secondary));
|
|
3686
|
-
stroke: none;
|
|
3687
|
-
}
|
|
3688
|
-
|
|
3689
|
-
.documentAttachmentTypeFoldLine {
|
|
3690
|
-
fill: none;
|
|
3691
|
-
}
|
|
3692
|
-
|
|
3693
|
-
.documentAttachmentTypeLabel {
|
|
3694
|
-
position: relative;
|
|
3695
|
-
z-index: 1;
|
|
3696
|
-
transform: translateY(2px);
|
|
3697
|
-
}
|
|
3698
|
-
|
|
3699
3670
|
.brokenImage {
|
|
3700
3671
|
display: flex;
|
|
3701
3672
|
align-items: center;
|
|
@@ -24,6 +24,17 @@ export function EntityActivityTimeline({ activity, comments, formatFieldValue, r
|
|
|
24
24
|
normalizedCurrentActorLabel !== 'user' &&
|
|
25
25
|
normalizedCurrentActorLabel !== 'human' &&
|
|
26
26
|
normalizedCurrentActorLabel !== normalizedCurrentActorId);
|
|
27
|
+
const resolveCanonicalCurrentActorLabel = React.useCallback((presentation) => {
|
|
28
|
+
const normalizedLabel = String(presentation.label || '').trim().toLowerCase();
|
|
29
|
+
const isLegacyCurrentUserLabel = normalizedLabel === 'you'
|
|
30
|
+
|| normalizedLabel === 'user'
|
|
31
|
+
|| normalizedLabel === 'human';
|
|
32
|
+
return presentation.isCurrentActorHint === true
|
|
33
|
+
&& hasCanonicalCurrentActorLabel
|
|
34
|
+
&& isLegacyCurrentUserLabel
|
|
35
|
+
? currentActorLabel
|
|
36
|
+
: presentation.label;
|
|
37
|
+
}, [currentActorLabel, hasCanonicalCurrentActorLabel]);
|
|
27
38
|
return (_jsxs("div", { ref: listRef, className: [timelineStyles.thread, listClassName].filter(Boolean).join(' '), onScroll: onScroll, children: [items.length === 0 ? (_jsx("div", { className: [timelineStyles.empty, emptyClassName].filter(Boolean).join(' '), children: emptyMessage })) : null, items.map((item) => {
|
|
28
39
|
const isTargetEntry = Boolean(normalizedTargetEntryId
|
|
29
40
|
&& (item.id === normalizedTargetEntryId
|
|
@@ -60,10 +71,8 @@ export function EntityActivityTimeline({ activity, comments, formatFieldValue, r
|
|
|
60
71
|
? commentActorIds.includes(normalizedCurrentActorId)
|
|
61
72
|
: actorPresentation.isCurrentActorHint === true
|
|
62
73
|
: commentKind === 'member';
|
|
63
|
-
const authorLabel = isCurrentActor
|
|
64
|
-
actorPresentation
|
|
65
|
-
hasCanonicalCurrentActorLabel
|
|
66
|
-
? currentActorLabel
|
|
74
|
+
const authorLabel = isCurrentActor
|
|
75
|
+
? resolveCanonicalCurrentActorLabel(actorPresentation)
|
|
67
76
|
: actorPresentation.label;
|
|
68
77
|
const { ActorIcon } = actorPresentation;
|
|
69
78
|
const authorColor = actorPresentation.kind === 'system'
|
|
@@ -96,6 +105,7 @@ export function EntityActivityTimeline({ activity, comments, formatFieldValue, r
|
|
|
96
105
|
actorProfile: item.event.actorProfile || null,
|
|
97
106
|
fallbackKind: 'system',
|
|
98
107
|
});
|
|
108
|
+
const authorLabel = resolveCanonicalCurrentActorLabel(actorPresentation);
|
|
99
109
|
const { ActorIcon } = actorPresentation;
|
|
100
110
|
const authorColor = actorPresentation.color;
|
|
101
111
|
const changeEntries = Object.entries(item.event.details?.changes || {});
|
|
@@ -109,7 +119,7 @@ export function EntityActivityTimeline({ activity, comments, formatFieldValue, r
|
|
|
109
119
|
timelineStyles.item,
|
|
110
120
|
timelineStyles.event,
|
|
111
121
|
actorPresentation.kind === 'system' ? timelineStyles.eventSystem : '',
|
|
112
|
-
].filter(Boolean).join(' '), children: _jsxs("div", { className: timelineStyles.eventMain, children: [_jsxs("span", { className: timelineStyles.author, style: authorColor ? { color: authorColor } : undefined, children: [_jsx(ActorIcon, { size: 12, strokeWidth: 1.5 }),
|
|
122
|
+
].filter(Boolean).join(' '), children: _jsxs("div", { className: timelineStyles.eventMain, children: [_jsxs("span", { className: timelineStyles.author, style: authorColor ? { color: authorColor } : undefined, children: [_jsx(ActorIcon, { size: 12, strokeWidth: 1.5 }), authorLabel] }), _jsxs("div", { className: timelineStyles.eventText, children: [_jsx("div", { children: summarizeEntityEvent(item.event, formatFieldValue) }), displayedChangeEntries.length > 0 ? (_jsx("div", { className: timelineStyles.eventChanges, children: displayedChangeEntries.map(([field, change]) => (_jsx("div", { className: timelineStyles.eventChange, children: summarizeTaskChange(field, change, formatFieldValue) }, field))) })) : null] }), _jsx("time", { className: timelineStyles.time, dateTime: item.event.createdAt, children: formatDate(item.event.createdAt, {
|
|
113
123
|
month: 'short',
|
|
114
124
|
day: 'numeric',
|
|
115
125
|
hour: '2-digit',
|
|
@@ -218,5 +218,5 @@ export function AttachmentPreviewHost({ theme }) {
|
|
|
218
218
|
? `Copied ${referenceLabel}`
|
|
219
219
|
: `Copy ${referenceLabel}`, className: styles.referenceBadge }))] })] }), _jsx("button", { type: "button", className: "tf-control-icon", "aria-label": "Close preview", title: "Close preview", onClick: () => setPreview(null), children: _jsx(X, { size: 18 }) })] }), _jsxs("div", { className: styles.content, children: [isImage && _jsx("img", { className: styles.image, src: path, alt: attachmentName }), isPdf && (_jsx("iframe", { className: styles.pdf, src: path, title: `Preview ${attachmentName}` })), isText && textLoading && _jsx("div", { className: styles.message, children: "Loading preview\u2026" }), isText && textError && _jsx("div", { className: styles.message, children: textError }), isText && !textLoading && !textError && (isMarkdown
|
|
220
220
|
? _jsx("div", { className: styles.textPreview, children: _jsx(Markdown, { variant: "detail", children: textContent }) })
|
|
221
|
-
: _jsx("pre", { className: styles.codePreview, children: textContent })), !isImage && !isPdf && !isText && (_jsxs("div", { className: styles.unavailable, children: [_jsx(FileText, { size: 34, "aria-hidden": "true" }), _jsx("strong", { children: "Preview unavailable" }), _jsx("span", { children: "This file type can be opened or downloaded instead." })] }))] }), _jsxs("footer", { className: styles.footer, children: [_jsxs("div", { className: styles.secondaryActions, children: [!isExternal && (_jsxs("a", { className: styles.secondaryLink, href: buildDownloadUrl(path, attachmentName), target: "_blank", rel: "noreferrer", children: [_jsx(Download, { size: 14 }), " Download"] })), (isExternal || (!canOpenImageNotes && !canOpenDocumentManager)) && (_jsxs("a", { className: styles.secondaryLink, href: path, target: "_blank", rel: "noreferrer", children: [_jsx(ExternalLink, { size: 14 }), " Open original"] }))] }), (canOpenImageNotes || canOpenDocumentManager) && (_jsxs("button", { type: "button", className: "tf-button-primary tf-button-compact", onClick: openManager, children: [canOpenImageNotes ? _jsx(PencilLine, { size: 14 }) : _jsx(ExternalLink, { size: 14 }), canOpenImageNotes ? 'Open in Image Notes' : 'Open
|
|
221
|
+
: _jsx("pre", { className: styles.codePreview, children: textContent })), !isImage && !isPdf && !isText && (_jsxs("div", { className: styles.unavailable, children: [_jsx(FileText, { size: 34, "aria-hidden": "true" }), _jsx("strong", { children: "Preview unavailable" }), _jsx("span", { children: "This file type can be opened or downloaded instead." })] }))] }), _jsxs("footer", { className: styles.footer, children: [_jsxs("div", { className: styles.secondaryActions, children: [!isExternal && (_jsxs("a", { className: styles.secondaryLink, href: buildDownloadUrl(path, attachmentName), target: "_blank", rel: "noreferrer", children: [_jsx(Download, { size: 14 }), " Download"] })), (isExternal || (!canOpenImageNotes && !canOpenDocumentManager)) && (_jsxs("a", { className: styles.secondaryLink, href: path, target: "_blank", rel: "noreferrer", children: [_jsx(ExternalLink, { size: 14 }), " Open original"] }))] }), (canOpenImageNotes || canOpenDocumentManager) && (_jsxs("button", { type: "button", className: "tf-button-primary tf-button-compact", onClick: openManager, children: [canOpenImageNotes ? _jsx(PencilLine, { size: 14 }) : _jsx(ExternalLink, { size: 14 }), canOpenImageNotes ? 'Open in Image Notes' : 'Open'] }))] })] }) }), document.body);
|
|
222
222
|
}
|
|
@@ -11,6 +11,9 @@ export interface ContextAttachmentManagerProps {
|
|
|
11
11
|
onAddAttachment: (file: AttachmentItem) => void;
|
|
12
12
|
onRemoveAttachment: (index: number) => void;
|
|
13
13
|
onUpdateAttachmentCaption: (index: number, caption: string) => void;
|
|
14
|
+
cardCoverAssetId?: string | null;
|
|
15
|
+
onSetCardCover?: (assetId: string | null) => void | Promise<void>;
|
|
16
|
+
cardCoverDisabled?: boolean;
|
|
14
17
|
readOnly?: boolean;
|
|
15
18
|
defaultExpanded?: boolean;
|
|
16
19
|
supplementalContent?: React.ReactNode;
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
4
|
+
import { AlertTriangle, Check, Copy, Download, ExternalLink, Eye, FileUp, Link2, Loader2, MoreHorizontal, PanelTop, PencilLine, Plus, Trash2, X } from 'lucide-react';
|
|
4
5
|
import styles from '../../Taskforce.module.css';
|
|
5
6
|
import { DisclosureSectionHeader } from '../ui/DisclosureSectionHeader';
|
|
6
|
-
import { dispatchOpenMarkdownDocument, isMarkdownContextFile, resolveContextFsPath } from '../../utils/contextFiles';
|
|
7
|
+
import { dispatchOpenMarkdownDocument, isMarkdownContextFile, resolveContextAssetId, resolveContextFsPath } from '../../utils/contextFiles';
|
|
7
8
|
import { canOpenAnnotatedAttachment, dispatchOpenAnnotatedAttachment } from '../../utils/annotatedAttachments';
|
|
8
9
|
import { dispatchTaskforceContextAssetsMutated } from '../../utils/contextAssetEvents';
|
|
9
10
|
import { getDocumentReferenceLabel } from '../../utils/documentReferences';
|
|
10
11
|
import { uploadContextAttachment } from '../../utils/contextAttachmentUpload';
|
|
11
12
|
import { dispatchPreviewContextAttachment } from '../../utils/contextAttachmentPreview';
|
|
12
13
|
import { ReferenceBadgeButton } from '../ui/ReferenceBadgeButton';
|
|
14
|
+
import { DocumentTypeIcon } from '../ui/DocumentTypeIcon';
|
|
13
15
|
const ACCEPTED_CONTEXT_FILE_TYPES = [
|
|
14
16
|
'.png', '.jpg', '.jpeg', '.webp', '.gif',
|
|
15
17
|
'.pdf', '.txt', '.md', '.csv', '.json', '.doc', '.docx',
|
|
@@ -75,7 +77,7 @@ function getAttachmentTypeLabel(item) {
|
|
|
75
77
|
return /^https?:\/\//i.test(path) ? 'LINK' : 'FILE';
|
|
76
78
|
}
|
|
77
79
|
export function ContextAttachmentManager(props) {
|
|
78
|
-
const { ownerType = 'task', ownerId, ownerReferenceLabel, workspaceId, attachments, onAddAttachment, onRemoveAttachment, onUpdateAttachmentCaption, readOnly = false, defaultExpanded = true, supplementalContent, } = props;
|
|
80
|
+
const { ownerType = 'task', ownerId, ownerReferenceLabel, workspaceId, attachments, onAddAttachment, onRemoveAttachment, onUpdateAttachmentCaption, cardCoverAssetId = null, onSetCardCover, cardCoverDisabled = false, readOnly = false, defaultExpanded = true, supplementalContent, } = props;
|
|
79
81
|
const inputRef = useRef(null);
|
|
80
82
|
const managerRef = useRef(null);
|
|
81
83
|
const [uploading, setUploading] = useState(false);
|
|
@@ -83,10 +85,13 @@ export function ContextAttachmentManager(props) {
|
|
|
83
85
|
const [linkValue, setLinkValue] = useState('');
|
|
84
86
|
const [linkError, setLinkError] = useState(null);
|
|
85
87
|
const [contextNotice, setContextNotice] = useState(null);
|
|
88
|
+
const [cardCoverBusyAssetId, setCardCoverBusyAssetId] = useState(null);
|
|
86
89
|
const [showContextUpload, setShowContextUpload] = useState(defaultExpanded);
|
|
87
90
|
const [showAddMenu, setShowAddMenu] = useState(false);
|
|
88
91
|
const [showLinkComposer, setShowLinkComposer] = useState(false);
|
|
89
92
|
const [openActionsIndex, setOpenActionsIndex] = useState(null);
|
|
93
|
+
const [openImageActionsIndex, setOpenImageActionsIndex] = useState(null);
|
|
94
|
+
const [imageActionsMenuPosition, setImageActionsMenuPosition] = useState({ top: 0, left: 0 });
|
|
90
95
|
const [captionEditor, setCaptionEditor] = useState(null);
|
|
91
96
|
const [copiedDocumentReference, setCopiedDocumentReference] = useState(null);
|
|
92
97
|
const copyResetTimerRef = useRef(null);
|
|
@@ -94,6 +99,9 @@ export function ContextAttachmentManager(props) {
|
|
|
94
99
|
if (copyResetTimerRef.current !== null)
|
|
95
100
|
window.clearTimeout(copyResetTimerRef.current);
|
|
96
101
|
}, []);
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
setCardCoverBusyAssetId(null);
|
|
104
|
+
}, [ownerId, ownerType, workspaceId]);
|
|
97
105
|
const copyDocumentReference = async (referenceLabel) => {
|
|
98
106
|
try {
|
|
99
107
|
await navigator.clipboard.writeText(referenceLabel);
|
|
@@ -121,6 +129,9 @@ export function ContextAttachmentManager(props) {
|
|
|
121
129
|
const addFirstActionRef = useRef(null);
|
|
122
130
|
const attachmentActionsTriggerRef = useRef(null);
|
|
123
131
|
const attachmentFirstActionRef = useRef(null);
|
|
132
|
+
const imageActionsTriggerRef = useRef(null);
|
|
133
|
+
const imageActionsMenuRef = useRef(null);
|
|
134
|
+
const imageFirstActionRef = useRef(null);
|
|
124
135
|
const captionFocusReturnRef = useRef(null);
|
|
125
136
|
const linkInputRef = useRef(null);
|
|
126
137
|
const contextKeyRef = useRef(new Set());
|
|
@@ -171,6 +182,7 @@ export function ContextAttachmentManager(props) {
|
|
|
171
182
|
setShowAddMenu(false);
|
|
172
183
|
setShowLinkComposer(false);
|
|
173
184
|
setOpenActionsIndex(null);
|
|
185
|
+
setOpenImageActionsIndex(null);
|
|
174
186
|
setCaptionEditor(null);
|
|
175
187
|
setIsDropActive(false);
|
|
176
188
|
setDragDepth(0);
|
|
@@ -217,7 +229,7 @@ export function ContextAttachmentManager(props) {
|
|
|
217
229
|
return () => window.clearTimeout(timeoutId);
|
|
218
230
|
}, [contextNotice]);
|
|
219
231
|
useEffect(() => {
|
|
220
|
-
if (!showAddMenu && openActionsIndex === null)
|
|
232
|
+
if (!showAddMenu && openActionsIndex === null && openImageActionsIndex === null)
|
|
221
233
|
return;
|
|
222
234
|
const handlePointerDown = (event) => {
|
|
223
235
|
const target = event.target;
|
|
@@ -227,10 +239,15 @@ export function ContextAttachmentManager(props) {
|
|
|
227
239
|
if (openActionsIndex !== null && !target?.closest('[data-context-actions-menu="true"]')) {
|
|
228
240
|
setOpenActionsIndex(null);
|
|
229
241
|
}
|
|
242
|
+
if (openImageActionsIndex !== null
|
|
243
|
+
&& !imageActionsTriggerRef.current?.contains(target)
|
|
244
|
+
&& !imageActionsMenuRef.current?.contains(target)) {
|
|
245
|
+
setOpenImageActionsIndex(null);
|
|
246
|
+
}
|
|
230
247
|
};
|
|
231
248
|
document.addEventListener('pointerdown', handlePointerDown);
|
|
232
249
|
return () => document.removeEventListener('pointerdown', handlePointerDown);
|
|
233
|
-
}, [openActionsIndex, showAddMenu]);
|
|
250
|
+
}, [openActionsIndex, openImageActionsIndex, showAddMenu]);
|
|
234
251
|
useEffect(() => {
|
|
235
252
|
if (!showLinkComposer)
|
|
236
253
|
return;
|
|
@@ -246,12 +263,45 @@ export function ContextAttachmentManager(props) {
|
|
|
246
263
|
return;
|
|
247
264
|
attachmentFirstActionRef.current?.focus();
|
|
248
265
|
}, [openActionsIndex]);
|
|
266
|
+
useEffect(() => {
|
|
267
|
+
if (openImageActionsIndex === null)
|
|
268
|
+
return;
|
|
269
|
+
imageFirstActionRef.current?.focus();
|
|
270
|
+
}, [openImageActionsIndex]);
|
|
271
|
+
useLayoutEffect(() => {
|
|
272
|
+
if (openImageActionsIndex === null)
|
|
273
|
+
return;
|
|
274
|
+
const positionMenu = () => {
|
|
275
|
+
const triggerRect = imageActionsTriggerRef.current?.getBoundingClientRect();
|
|
276
|
+
if (!triggerRect)
|
|
277
|
+
return;
|
|
278
|
+
const menuRect = imageActionsMenuRef.current?.getBoundingClientRect();
|
|
279
|
+
const menuWidth = menuRect?.width || 150;
|
|
280
|
+
const menuHeight = menuRect?.height || 108;
|
|
281
|
+
const gap = 4;
|
|
282
|
+
const viewportPadding = 8;
|
|
283
|
+
const left = Math.max(viewportPadding, Math.min(triggerRect.right - menuWidth, window.innerWidth - menuWidth - viewportPadding));
|
|
284
|
+
const preferredTop = triggerRect.bottom + gap;
|
|
285
|
+
const top = preferredTop + menuHeight <= window.innerHeight - viewportPadding
|
|
286
|
+
? preferredTop
|
|
287
|
+
: Math.max(viewportPadding, triggerRect.top - menuHeight - gap);
|
|
288
|
+
setImageActionsMenuPosition({ top, left });
|
|
289
|
+
};
|
|
290
|
+
positionMenu();
|
|
291
|
+
window.addEventListener('resize', positionMenu);
|
|
292
|
+
window.addEventListener('scroll', positionMenu, true);
|
|
293
|
+
return () => {
|
|
294
|
+
window.removeEventListener('resize', positionMenu);
|
|
295
|
+
window.removeEventListener('scroll', positionMenu, true);
|
|
296
|
+
};
|
|
297
|
+
}, [openImageActionsIndex]);
|
|
249
298
|
useEffect(() => {
|
|
250
299
|
if (!readOnly)
|
|
251
300
|
return;
|
|
252
301
|
setShowAddMenu(false);
|
|
253
302
|
setShowLinkComposer(false);
|
|
254
303
|
setOpenActionsIndex(null);
|
|
304
|
+
setOpenImageActionsIndex(null);
|
|
255
305
|
setCaptionEditor(null);
|
|
256
306
|
setIsDropActive(false);
|
|
257
307
|
setDragDepth(0);
|
|
@@ -572,6 +622,7 @@ export function ContextAttachmentManager(props) {
|
|
|
572
622
|
setShowAddMenu(false);
|
|
573
623
|
setShowLinkComposer(false);
|
|
574
624
|
setOpenActionsIndex(null);
|
|
625
|
+
setOpenImageActionsIndex(null);
|
|
575
626
|
setCaptionEditor(null);
|
|
576
627
|
setIsDropActive(false);
|
|
577
628
|
setDragDepth(0);
|
|
@@ -623,6 +674,16 @@ export function ContextAttachmentManager(props) {
|
|
|
623
674
|
const isExternalUrl = /^https?:\/\//i.test(path);
|
|
624
675
|
const canAnnotate = ownerType === 'task'
|
|
625
676
|
&& canOpenAnnotatedAttachment(item, { taskId: trimmedOwnerId });
|
|
677
|
+
const assetId = typeof item === 'string' ? '' : String(item.assetId || '').trim();
|
|
678
|
+
const isCardCover = Boolean(assetId && assetId === cardCoverAssetId);
|
|
679
|
+
const canSetCardCover = ownerType === 'task'
|
|
680
|
+
&& Boolean(trimmedOwnerId)
|
|
681
|
+
&& Boolean(assetId)
|
|
682
|
+
&& typeof item !== 'string'
|
|
683
|
+
&& item.linkRole === 'image'
|
|
684
|
+
&& Boolean(onSetCardCover)
|
|
685
|
+
&& !readOnly;
|
|
686
|
+
const hasImageActionMenu = Boolean(fsPath || !isExternalUrl || !readOnly);
|
|
626
687
|
return (_jsxs("div", { className: styles.attachmentCard, children: [_jsxs("div", { className: styles.attachmentPreviewContainer, children: [isImage ? (_jsx("button", { type: "button", className: styles.attachmentPreviewButton, "aria-label": `Open ${caption || displayName}`, onClick: () => {
|
|
627
688
|
if (dispatchPreviewContextAttachment(item, {
|
|
628
689
|
ownerType,
|
|
@@ -650,22 +711,56 @@ export function ContextAttachmentManager(props) {
|
|
|
650
711
|
if (dispatchOpenMarkdownDocument(markdownTarget, fsPath))
|
|
651
712
|
return;
|
|
652
713
|
window.open(path, '_blank');
|
|
653
|
-
}, title: "Open file", children: [_jsx(FileUp, { size: 16 }), _jsx("span", { children: caption || fsPath || path.split('/').pop() || 'Context file' })] })), _jsxs("div", { className: styles.brokenImagePlaceholder, children: [_jsx(AlertTriangle, { size: 16 }), _jsx("span", { children: "Preview unavailable" })] }), _jsxs("div", { className: styles.attachmentActions, children: [
|
|
714
|
+
}, title: "Open file", children: [_jsx(FileUp, { size: 16 }), _jsx("span", { children: caption || fsPath || path.split('/').pop() || 'Context file' })] })), _jsxs("div", { className: styles.brokenImagePlaceholder, children: [_jsx(AlertTriangle, { size: 16 }), _jsx("span", { children: "Preview unavailable" })] }), _jsxs("div", { className: styles.attachmentActions, children: [canSetCardCover && (_jsx("button", { type: "button", className: `${styles.attachmentActionBtn} ${isCardCover ? styles.attachmentActionBtnActive : ''}`, "aria-pressed": isCardCover, "aria-label": isCardCover ? 'Remove from task card' : 'Show on task card', onClick: (event) => {
|
|
715
|
+
event.stopPropagation();
|
|
716
|
+
setCardCoverBusyAssetId(assetId);
|
|
717
|
+
setUploadError(null);
|
|
718
|
+
void Promise.resolve(onSetCardCover?.(isCardCover ? null : assetId))
|
|
719
|
+
.then(() => {
|
|
720
|
+
setContextNotice(isCardCover
|
|
721
|
+
? 'Removed image from task card.'
|
|
722
|
+
: 'Image shown on task card.');
|
|
723
|
+
})
|
|
724
|
+
.catch((error) => {
|
|
725
|
+
setUploadError(error instanceof Error
|
|
726
|
+
? error.message
|
|
727
|
+
: 'Unable to update the task card image.');
|
|
728
|
+
})
|
|
729
|
+
.finally(() => setCardCoverBusyAssetId(null));
|
|
730
|
+
}, disabled: cardCoverDisabled || cardCoverBusyAssetId !== null, title: cardCoverDisabled
|
|
731
|
+
? 'Attachment is still saving'
|
|
732
|
+
: isCardCover ? 'Remove from task card' : 'Show on task card', children: cardCoverBusyAssetId === assetId
|
|
733
|
+
? _jsx(Loader2, { size: 12, className: styles.spinner })
|
|
734
|
+
: _jsx(PanelTop, { size: 12 }) })), canAnnotate && (_jsx("button", { type: "button", className: styles.attachmentActionBtn, "aria-label": "Annotate image", onClick: (e) => {
|
|
654
735
|
e.stopPropagation();
|
|
655
736
|
dispatchOpenAnnotatedAttachment(item, {
|
|
656
737
|
taskId: trimmedOwnerId,
|
|
657
738
|
taskReferenceLabel: ownerReferenceLabel,
|
|
658
739
|
});
|
|
659
|
-
}, title: "Annotate", children: _jsx(PencilLine, { size: 12 }) })),
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
740
|
+
}, title: "Annotate", children: _jsx(PencilLine, { size: 12 }) })), hasImageActionMenu && (_jsx("button", { type: "button", className: styles.attachmentActionBtn, "aria-label": `Actions for ${displayName}`, "aria-expanded": openImageActionsIndex === idx, onClick: (event) => {
|
|
741
|
+
event.stopPropagation();
|
|
742
|
+
imageActionsTriggerRef.current = event.currentTarget;
|
|
743
|
+
setOpenActionsIndex(null);
|
|
744
|
+
setOpenImageActionsIndex(openImageActionsIndex === idx ? null : idx);
|
|
745
|
+
}, title: "More actions", children: _jsx(MoreHorizontal, { size: 12 }) })), openImageActionsIndex === idx && createPortal(_jsxs("div", { ref: imageActionsMenuRef, className: `${styles.contextMenu} ${styles.attachmentImageActionsMenu}`, role: "group", "aria-label": `Actions for ${displayName}`, style: imageActionsMenuPosition, onClick: (event) => event.stopPropagation(), onKeyDown: (event) => {
|
|
746
|
+
if (event.key !== 'Escape')
|
|
747
|
+
return;
|
|
748
|
+
event.preventDefault();
|
|
749
|
+
event.stopPropagation();
|
|
750
|
+
setOpenImageActionsIndex(null);
|
|
751
|
+
imageActionsTriggerRef.current?.focus();
|
|
752
|
+
}, children: [fsPath && (_jsxs("button", { ref: imageFirstActionRef, type: "button", onClick: () => {
|
|
753
|
+
void navigator.clipboard.writeText(fsPath);
|
|
754
|
+
setOpenImageActionsIndex(null);
|
|
755
|
+
window.requestAnimationFrame(() => imageActionsTriggerRef.current?.focus());
|
|
756
|
+
}, children: [_jsx(Copy, { size: 14 }), " Copy path"] })), !isExternalUrl && (_jsxs("button", { ref: fsPath ? undefined : imageFirstActionRef, type: "button", onClick: () => {
|
|
757
|
+
window.open(buildDownloadUrl(path, caption || displayName), '_blank');
|
|
758
|
+
setOpenImageActionsIndex(null);
|
|
759
|
+
window.requestAnimationFrame(() => imageActionsTriggerRef.current?.focus());
|
|
760
|
+
}, children: [_jsx(Download, { size: 14 }), " Download"] })), !readOnly && (_jsxs("button", { ref: !fsPath && isExternalUrl ? imageFirstActionRef : undefined, type: "button", className: styles.contextDestructiveMenuItem, onClick: () => {
|
|
761
|
+
onRemoveAttachment(idx);
|
|
762
|
+
setOpenImageActionsIndex(null);
|
|
763
|
+
}, children: [_jsx(Trash2, { size: 14 }), " Delete"] }))] }), document.body)] })] }), captionEditor?.index === idx ? (_jsxs("div", { className: styles.contextCaptionEditor, onBlur: (event) => {
|
|
669
764
|
if (!event.currentTarget.contains(event.relatedTarget)) {
|
|
670
765
|
commitCaptionEdit(false);
|
|
671
766
|
}
|
|
@@ -700,8 +795,9 @@ export function ContextAttachmentManager(props) {
|
|
|
700
795
|
: { ...item, taskId: trimmedOwnerId })
|
|
701
796
|
: item;
|
|
702
797
|
const canOpenInDocumentManager = isMarkdownContextFile(markdownTarget, fsPath)
|
|
703
|
-
&& Boolean(resolveContextFsPath(markdownTarget, fsPath)
|
|
704
|
-
|
|
798
|
+
&& Boolean(resolveContextFsPath(markdownTarget, fsPath)
|
|
799
|
+
|| resolveContextAssetId(markdownTarget));
|
|
800
|
+
return (_jsxs("div", { className: styles.documentAttachmentItem, children: [_jsxs("div", { className: styles.documentAttachmentRow, children: [_jsxs("div", { className: styles.documentAttachmentMain, children: [_jsx(DocumentTypeIcon, { label: fileType }), documentReferenceLabel && (_jsx(ReferenceBadgeButton, { label: documentReferenceLabel, copied: copiedDocumentReference === documentReferenceLabel, title: `Copy document reference ${documentReferenceLabel}`, ariaLabel: `Copy document reference ${documentReferenceLabel}`, className: styles.documentAttachmentReference, onClick: () => { void copyDocumentReference(documentReferenceLabel); } })), _jsx("button", { type: "button", className: styles.documentAttachmentLink, onClick: () => {
|
|
705
801
|
if (dispatchPreviewContextAttachment(item, {
|
|
706
802
|
ownerType,
|
|
707
803
|
ownerId: trimmedOwnerId || null,
|
|
@@ -712,7 +808,7 @@ export function ContextAttachmentManager(props) {
|
|
|
712
808
|
if (dispatchOpenMarkdownDocument(markdownTarget, fsPath))
|
|
713
809
|
return;
|
|
714
810
|
window.open(path, '_blank');
|
|
715
|
-
}, title: displayName, "aria-label": `${caption || displayName} ${fileType}`, children: _jsx("span", { className: styles.documentAttachmentName, children: caption || displayName }) })
|
|
811
|
+
}, title: displayName, "aria-label": `${caption || displayName} ${fileType}`, children: _jsx("span", { className: styles.documentAttachmentName, children: caption || displayName }) })] }), (!readOnly || Boolean(fsPath) || !isExternalUrl) && (_jsxs("div", { className: styles.contextMenuControl, "data-context-actions-menu": "true", onKeyDown: (event) => {
|
|
716
812
|
if (event.key !== 'Escape' || openActionsIndex !== idx)
|
|
717
813
|
return;
|
|
718
814
|
event.preventDefault();
|
|
@@ -737,7 +833,7 @@ export function ContextAttachmentManager(props) {
|
|
|
737
833
|
window.open(path, '_blank');
|
|
738
834
|
}
|
|
739
835
|
setOpenActionsIndex(null);
|
|
740
|
-
}, children: [_jsx(ExternalLink, { size: 14 }), " Open
|
|
836
|
+
}, children: [_jsx(ExternalLink, { size: 14 }), " Open"] })), !readOnly && (_jsxs("button", { type: "button", onClick: () => {
|
|
741
837
|
captionFocusReturnRef.current = attachmentActionsTriggerRef.current;
|
|
742
838
|
startCaptionEdit(idx, caption);
|
|
743
839
|
setOpenActionsIndex(null);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { TaskAssigneeOption } from '../../../utils/assignees';
|
|
3
3
|
import type { PlanningNodeSummary, PlanningStatusCounts } from './planningWorkspaceModel';
|
|
4
|
+
import { type PlanningIdentityPatch } from './PlanningVisualIdentity';
|
|
4
5
|
export declare function PlanningReferenceBadge({ label, entityType, entityName, interactive, provisional, pending, }: {
|
|
5
6
|
label: string;
|
|
6
7
|
entityType: 'initiative' | 'workstream' | 'task';
|
|
@@ -34,11 +35,12 @@ export declare function PlanningSummaryMeta({ item, workstreamCount, className,
|
|
|
34
35
|
workstreamCount?: number;
|
|
35
36
|
className?: string;
|
|
36
37
|
}): import("react/jsx-runtime").JSX.Element;
|
|
37
|
-
export declare function PlanningEntitySummary({ item, entityType, workstreamCount, ownerOption, ownerOptions, onChangeOwner, }: {
|
|
38
|
+
export declare function PlanningEntitySummary({ item, entityType, workstreamCount, ownerOption, ownerOptions, onChangeOwner, onChangeIdentity, }: {
|
|
38
39
|
item: PlanningNodeSummary;
|
|
39
40
|
entityType: 'initiative' | 'workstream';
|
|
40
41
|
workstreamCount?: number;
|
|
41
42
|
ownerOption?: TaskAssigneeOption;
|
|
42
43
|
ownerOptions?: TaskAssigneeOption[];
|
|
43
44
|
onChangeOwner?: (ownerId: string) => void | Promise<void>;
|
|
45
|
+
onChangeIdentity?: (patch: PlanningIdentityPatch) => void | Promise<void>;
|
|
44
46
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -6,6 +6,7 @@ import { getStatusDefinition, resolveColor } from '../../../utils/constants';
|
|
|
6
6
|
import { ReferenceBadgeButton } from '../../ui/ReferenceBadgeButton';
|
|
7
7
|
import { PlanningCardIdentity } from './PlanningCardIdentity';
|
|
8
8
|
import styles from './PlanningEntitySummary.module.css';
|
|
9
|
+
import { PlanningVisualIdentity } from './PlanningVisualIdentity';
|
|
9
10
|
const PLANNING_PROGRESS_STATUS_ORDER = [
|
|
10
11
|
'done',
|
|
11
12
|
'review',
|
|
@@ -91,7 +92,7 @@ export function PlanningSummaryMeta({ item, workstreamCount, className = '', })
|
|
|
91
92
|
const normalizedWorkstreamCount = Math.max(0, Number(workstreamCount) || 0);
|
|
92
93
|
return (_jsxs(PlanningCardMeta, { className: className, children: [workstreamCount !== undefined ? (_jsxs(_Fragment, { children: [_jsxs("span", { className: styles.scopeCount, children: [normalizedWorkstreamCount, " ", normalizedWorkstreamCount === 1 ? 'workstream' : 'workstreams'] }), _jsx("span", { "aria-hidden": "true", children: "\u2022" })] })) : null, hasTasks ? (_jsxs(_Fragment, { children: [_jsxs("span", { children: [item.completedTaskCount, " of ", item.taskCount, " complete"] }), _jsx("span", { "aria-hidden": "true", children: "\u2022" })] })) : null, _jsx(PlanningContextCount, { count: item.attachmentCount })] }));
|
|
93
94
|
}
|
|
94
|
-
export function PlanningEntitySummary({ item, entityType, workstreamCount, ownerOption, ownerOptions, onChangeOwner, }) {
|
|
95
|
+
export function PlanningEntitySummary({ item, entityType, workstreamCount, ownerOption, ownerOptions, onChangeOwner, onChangeIdentity, }) {
|
|
95
96
|
const hasTasks = item.taskCount > 0;
|
|
96
|
-
return (_jsxs(
|
|
97
|
+
return (_jsxs("div", { className: styles.summaryLayout, children: [_jsx(PlanningVisualIdentity, { entityType: entityType, entityId: item.id, icon: item.icon, color: item.color, disabled: Boolean(item.isArchived), onChange: onChangeIdentity }), _jsxs("div", { className: styles.summaryContent, children: [_jsx(PlanningCardTitle, { children: item.title }), hasTasks ? (_jsx(PlanningProgress, { value: item.progressPercent, label: `${item.title}: ${item.progressPercent}% resolved`, statusCounts: item.statusCounts })) : null, _jsxs(PlanningCardFooter, { identity: (_jsx(PlanningCardIdentity, { role: "owner", option: ownerOption, label: item.ownerLabel, value: item.ownerId, options: ownerOptions, onChange: onChangeOwner, disabled: Boolean(item.isArchived) })), children: [!hasTasks ? _jsx(PlanningNoTasks, {}) : null, _jsx(PlanningSummaryMeta, { item: item, workstreamCount: entityType === 'initiative' ? workstreamCount : undefined })] })] })] }));
|
|
97
98
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { PlanningLinkOption } from './planningWorkspaceModel';
|
|
2
|
-
export declare function PlanningLinkPicker({ options, selectedId, entityLabel, onSelect, disabled, className, }: {
|
|
2
|
+
export declare function PlanningLinkPicker({ options, selectedId, entityLabel, onSelect, disabled, className, iconTrigger, triggerTitle, }: {
|
|
3
3
|
options: PlanningLinkOption[];
|
|
4
4
|
selectedId?: string;
|
|
5
5
|
entityLabel: 'initiative' | 'workstream' | 'task';
|
|
6
6
|
onSelect: (option: PlanningLinkOption) => void | Promise<void>;
|
|
7
7
|
disabled?: boolean;
|
|
8
8
|
className?: string;
|
|
9
|
+
iconTrigger?: boolean;
|
|
10
|
+
triggerTitle?: string;
|
|
9
11
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -3,9 +3,11 @@ import React from 'react';
|
|
|
3
3
|
import { Link2 } from 'lucide-react';
|
|
4
4
|
import { TaxonomyDropdown } from '../../ui/TaxonomyDropdown';
|
|
5
5
|
import { PlanningReferenceBadge } from './PlanningEntitySummary';
|
|
6
|
+
import taskforceStyles from '../../../Taskforce.module.css';
|
|
6
7
|
import controlStyles from './PlanningDropdownControl.module.css';
|
|
7
8
|
import styles from './PlanningLinkPicker.module.css';
|
|
8
|
-
|
|
9
|
+
import { PlanningVisualIdentity } from './PlanningVisualIdentity';
|
|
10
|
+
export function PlanningLinkPicker({ options, selectedId = '', entityLabel, onSelect, disabled = false, className = '', iconTrigger = false, triggerTitle, }) {
|
|
9
11
|
const optionById = React.useMemo(() => new Map(options.map((option) => [option.id, option])), [options]);
|
|
10
12
|
const dropdownOptions = React.useMemo(() => options.map((option) => ({
|
|
11
13
|
value: option.id,
|
|
@@ -17,10 +19,10 @@ export function PlanningLinkPicker({ options, selectedId = '', entityLabel, onSe
|
|
|
17
19
|
const option = optionById.get(String(value));
|
|
18
20
|
if (option)
|
|
19
21
|
void onSelect(option);
|
|
20
|
-
}, disabled: disabled || options.length === 0, className: `${controlStyles.control} ${className}`.trim(), portalPanel: true, panelAlign: "start", panelMinWidth: 320, searchable: true, searchPlaceholder: `Search ${entityLabel}s by title or reference`, noResultsText: `No matching ${entityLabel}s.`, ariaLabel: linkLabel, triggerContent: selectedOption ? (_jsxs("span", { className: styles.triggerSelection, children: [_jsx(PlanningReferenceBadge, { label: selectedOption.referenceLabel, entityType: entityLabel, entityName: selectedOption.title }), _jsx("span", { children: selectedOption.title })] })) : (_jsxs("span", { className: styles.triggerAction, children: [_jsx(Link2, { size: 14, "aria-hidden": "true" }), _jsx("span", { children: linkLabel })] })), renderOptionContent: (option) => {
|
|
22
|
+
}, disabled: disabled || options.length === 0, className: `${iconTrigger ? `${styles.iconPicker} ${taskforceStyles.taskWorkstreamPicker}` : controlStyles.control} ${className}`.trim(), portalPanel: true, panelAlign: "start", panelMinWidth: 320, searchable: true, searchPlaceholder: `Search ${entityLabel}s by title or reference`, noResultsText: `No matching ${entityLabel}s.`, ariaLabel: linkLabel, title: iconTrigger ? (triggerTitle || linkLabel) : undefined, hideChevron: iconTrigger, triggerContent: iconTrigger ? (_jsx(Link2, { size: 14, "aria-hidden": "true" })) : selectedOption ? (_jsxs("span", { className: styles.triggerSelection, children: [_jsx(PlanningReferenceBadge, { label: selectedOption.referenceLabel, entityType: entityLabel, entityName: selectedOption.title }), _jsx("span", { children: selectedOption.title })] })) : (_jsxs("span", { className: styles.triggerAction, children: [_jsx(Link2, { size: 14, "aria-hidden": "true" }), _jsx("span", { children: linkLabel })] })), renderOptionContent: (option) => {
|
|
21
23
|
const candidate = optionById.get(String(option.value));
|
|
22
24
|
if (!candidate)
|
|
23
25
|
return option.label;
|
|
24
|
-
return (_jsxs("span", { className: styles.option, children: [_jsxs("span", { className: styles.optionTopRow, children: [_jsx(PlanningReferenceBadge, { label: candidate.referenceLabel, entityType: entityLabel, entityName: candidate.title }), _jsx("span", { className: styles.optionTitle, children: candidate.title })] }), candidate.meta ? _jsx("span", { className: styles.optionMeta, children: candidate.meta }) : null] }));
|
|
26
|
+
return (_jsxs("span", { className: styles.option, children: [_jsxs("span", { className: styles.optionTopRow, children: [entityLabel !== 'task' ? (_jsx(PlanningVisualIdentity, { entityType: entityLabel, entityId: candidate.id, icon: candidate.icon, color: candidate.color, size: "inline", disabled: true })) : null, _jsx(PlanningReferenceBadge, { label: candidate.referenceLabel, entityType: entityLabel, entityName: candidate.title }), _jsx("span", { className: styles.optionTitle, children: candidate.title })] }), candidate.meta ? _jsx("span", { className: styles.optionMeta, children: candidate.meta }) : null] }));
|
|
25
27
|
}, placeholder: linkLabel }));
|
|
26
28
|
}
|