@arbidocs/blocks 0.5.119 → 0.5.120
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/{ConnectedNativeDocxComparison-546ILGLW.cjs → ConnectedNativeDocxComparison-SGE4R3M4.cjs} +6 -6
- package/dist/{ConnectedNativeDocxComparison-546ILGLW.cjs.map → ConnectedNativeDocxComparison-SGE4R3M4.cjs.map} +1 -1
- package/dist/{ConnectedStreamedWordArtifact-S2ZFZLZQ.cjs → ConnectedStreamedWordArtifact-6MCLJRIT.cjs} +6 -6
- package/dist/{ConnectedStreamedWordArtifact-S2ZFZLZQ.cjs.map → ConnectedStreamedWordArtifact-6MCLJRIT.cjs.map} +1 -1
- package/dist/{DocumentReviewCard-ZSBZBGFK.cjs → DocumentReviewCard-BPIAJJED.cjs} +14 -14
- package/dist/{DocumentReviewCard-ZSBZBGFK.cjs.map → DocumentReviewCard-BPIAJJED.cjs.map} +1 -1
- package/dist/{FilesViewerDialog-6UKR5WEF.cjs → FilesViewerDialog-67WK3TZC.cjs} +11 -11
- package/dist/{FilesViewerDialog-6UKR5WEF.cjs.map → FilesViewerDialog-67WK3TZC.cjs.map} +1 -1
- package/dist/agents/index.cjs +185 -185
- package/dist/agents/tasks/ConversationListView.js +10 -2
- package/dist/agents/tasks/ConversationListView.js.map +2 -2
- package/dist/agents/tasks/TaskTable.js +5 -4
- package/dist/agents/tasks/TaskTable.js.map +2 -2
- package/dist/agents/tasks/WorkflowsTable.js +4 -5
- package/dist/agents/tasks/WorkflowsTable.js.map +2 -2
- package/dist/agents/tasks/taskColumns.js +1 -1
- package/dist/agents/tasks/taskColumns.js.map +1 -1
- package/dist/agents/tasks/taskTableLabels.js +1 -1
- package/dist/agents/tasks/taskTableLabels.js.map +1 -1
- package/dist/agents/tasks/workflowTableLabels.js +1 -1
- package/dist/agents/tasks/workflowTableLabels.js.map +1 -1
- package/dist/{chunk-P77YTTJ3.cjs → chunk-3GWWOYGS.cjs} +4 -4
- package/dist/{chunk-P77YTTJ3.cjs.map → chunk-3GWWOYGS.cjs.map} +1 -1
- package/dist/{chunk-HB2O4THB.cjs → chunk-5IZFSAEZ.cjs} +4 -4
- package/dist/{chunk-HB2O4THB.cjs.map → chunk-5IZFSAEZ.cjs.map} +1 -1
- package/dist/{chunk-DDIW4HLU.cjs → chunk-775S43W7.cjs} +19 -10
- package/dist/chunk-775S43W7.cjs.map +1 -0
- package/dist/{chunk-Y24V6HPR.cjs → chunk-BP3IJ34D.cjs} +4 -4
- package/dist/{chunk-Y24V6HPR.cjs.map → chunk-BP3IJ34D.cjs.map} +1 -1
- package/dist/{chunk-TT4IF52H.cjs → chunk-BUVIBHFU.cjs} +37 -38
- package/dist/chunk-BUVIBHFU.cjs.map +1 -0
- package/dist/{chunk-OEREKAAP.cjs → chunk-CKCG4NDB.cjs} +8 -8
- package/dist/{chunk-OEREKAAP.cjs.map → chunk-CKCG4NDB.cjs.map} +1 -1
- package/dist/{chunk-YGXOKB5Z.cjs → chunk-DFBDQKGE.cjs} +4 -4
- package/dist/{chunk-YGXOKB5Z.cjs.map → chunk-DFBDQKGE.cjs.map} +1 -1
- package/dist/{chunk-VGCTYTEW.cjs → chunk-ECLUFKYR.cjs} +4 -4
- package/dist/{chunk-VGCTYTEW.cjs.map → chunk-ECLUFKYR.cjs.map} +1 -1
- package/dist/{chunk-CPG7II4E.cjs → chunk-HA66DHW6.cjs} +28 -28
- package/dist/{chunk-CPG7II4E.cjs.map → chunk-HA66DHW6.cjs.map} +1 -1
- package/dist/{chunk-57NHXEU2.cjs → chunk-MXBXZXXQ.cjs} +5 -5
- package/dist/{chunk-57NHXEU2.cjs.map → chunk-MXBXZXXQ.cjs.map} +1 -1
- package/dist/{chunk-NNUK5ZM7.cjs → chunk-NLSWDP42.cjs} +9 -17
- package/dist/chunk-NLSWDP42.cjs.map +1 -0
- package/dist/{chunk-B2HNNG25.cjs → chunk-Q6CLWOP2.cjs} +29 -29
- package/dist/{chunk-B2HNNG25.cjs.map → chunk-Q6CLWOP2.cjs.map} +1 -1
- package/dist/{chunk-R7CJEIA5.cjs → chunk-QQWHG3KF.cjs} +5 -6
- package/dist/chunk-QQWHG3KF.cjs.map +1 -0
- package/dist/{chunk-VLCY46K6.cjs → chunk-TG2GM7WJ.cjs} +4 -3
- package/dist/chunk-TG2GM7WJ.cjs.map +1 -0
- package/dist/{chunk-QHOHDNBU.cjs → chunk-VZCPMXWC.cjs} +4 -4
- package/dist/{chunk-QHOHDNBU.cjs.map → chunk-VZCPMXWC.cjs.map} +1 -1
- package/dist/{chunk-NIE3N6OM.cjs → chunk-W6INT5GS.cjs} +5 -5
- package/dist/{chunk-NIE3N6OM.cjs.map → chunk-W6INT5GS.cjs.map} +1 -1
- package/dist/{chunk-ZPPMAL5P.cjs → chunk-WQMR3O2D.cjs} +6 -10
- package/dist/chunk-WQMR3O2D.cjs.map +1 -0
- package/dist/{chunk-SB4M3ITT.cjs → chunk-X36UR4A7.cjs} +173 -56
- package/dist/chunk-X36UR4A7.cjs.map +1 -0
- package/dist/{chunk-XKVPSOKP.cjs → chunk-YYSBWMDJ.cjs} +4 -4
- package/dist/{chunk-XKVPSOKP.cjs.map → chunk-YYSBWMDJ.cjs.map} +1 -1
- package/dist/collaboration/index.cjs +3 -3
- package/dist/collaboration/sharing/ShareControl.js +10 -2
- package/dist/collaboration/sharing/ShareControl.js.map +2 -2
- package/dist/components/SharedCell.d.ts +2 -4
- package/dist/components/SharedCell.js +4 -8
- package/dist/components/SharedCell.js.map +2 -2
- package/dist/connect/docx.cjs +9 -9
- package/dist/connect/index.cjs +595 -567
- package/dist/connect/index.cjs.map +1 -1
- package/dist/connect/viewer.cjs +9 -9
- package/dist/connection/facts/factPageColumns.js +3 -9
- package/dist/connection/facts/factPageColumns.js.map +2 -2
- package/dist/connection/files/grid/cells.js +1 -3
- package/dist/connection/files/grid/cells.js.map +2 -2
- package/dist/connection/files/grid/documentColumns.js +3 -2
- package/dist/connection/files/grid/documentColumns.js.map +2 -2
- package/dist/connection/sharing/WorkspaceMembersList.d.ts +4 -0
- package/dist/connection/sharing/WorkspaceMembersList.js +34 -0
- package/dist/connection/sharing/WorkspaceMembersList.js.map +7 -0
- package/dist/connection/shell/ConnectedAppShell.js +4 -0
- package/dist/connection/shell/ConnectedAppShell.js.map +2 -2
- package/dist/connection/viewer/ConnectedDocumentViewer.js +3 -2
- package/dist/connection/viewer/ConnectedDocumentViewer.js.map +2 -2
- package/dist/connection/viewer/ViewerPdfPane.js +20 -2
- package/dist/connection/viewer/ViewerPdfPane.js.map +2 -2
- package/dist/connection/viewer/pdf/PdfPassageHighlights.d.ts +12 -0
- package/dist/connection/viewer/pdf/PdfPassageHighlights.js +36 -0
- package/dist/connection/viewer/pdf/PdfPassageHighlights.js.map +7 -0
- package/dist/connection/viewer/pdf/passageHighlights.d.ts +26 -0
- package/dist/connection/viewer/pdf/passageHighlights.js +40 -0
- package/dist/connection/viewer/pdf/passageHighlights.js.map +7 -0
- package/dist/connection/viewer/pdf/usePdfPassageHighlights.d.ts +9 -0
- package/dist/connection/viewer/pdf/usePdfPassageHighlights.js +47 -0
- package/dist/connection/viewer/pdf/usePdfPassageHighlights.js.map +7 -0
- package/dist/connection/workflows/workflowsPageLabels.js +1 -1
- package/dist/connection/workflows/workflowsPageLabels.js.map +1 -1
- package/dist/embed/index.cjs +13 -13
- package/dist/facts/factColumns.js +3 -3
- package/dist/facts/factColumns.js.map +1 -1
- package/dist/facts/index.cjs +42 -42
- package/dist/files/HtmlViewerParts.js +7 -15
- package/dist/files/HtmlViewerParts.js.map +2 -2
- package/dist/files/index.cjs +223 -223
- package/dist/index.cjs +238 -238
- package/dist/styles.css +1 -1
- package/dist/workflow-canvas/index.cjs +24 -24
- package/dist/workflow-canvas/index.cjs.map +1 -1
- package/node_modules/@arbidocs/coedit/package.json +1 -1
- package/package.json +5 -5
- package/dist/chunk-DDIW4HLU.cjs.map +0 -1
- package/dist/chunk-NNUK5ZM7.cjs.map +0 -1
- package/dist/chunk-R7CJEIA5.cjs.map +0 -1
- package/dist/chunk-SB4M3ITT.cjs.map +0 -1
- package/dist/chunk-TT4IF52H.cjs.map +0 -1
- package/dist/chunk-VLCY46K6.cjs.map +0 -1
- package/dist/chunk-ZPPMAL5P.cjs.map +0 -1
package/dist/connect/viewer.cjs
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var
|
|
4
|
+
var chunkX36UR4A7_cjs = require('../chunk-X36UR4A7.cjs');
|
|
5
5
|
require('../chunk-4CIVQWAX.cjs');
|
|
6
6
|
var chunkPL7HUTM3_cjs = require('../chunk-PL7HUTM3.cjs');
|
|
7
|
-
require('../chunk-
|
|
7
|
+
require('../chunk-DFBDQKGE.cjs');
|
|
8
8
|
require('../chunk-KXIRVKCJ.cjs');
|
|
9
|
-
require('../chunk-
|
|
9
|
+
require('../chunk-BUVIBHFU.cjs');
|
|
10
10
|
require('../chunk-NJCK6V6U.cjs');
|
|
11
|
-
require('../chunk-
|
|
11
|
+
require('../chunk-NLSWDP42.cjs');
|
|
12
12
|
require('../chunk-223D4727.cjs');
|
|
13
13
|
require('../chunk-SNUBQMIS.cjs');
|
|
14
|
-
require('../chunk-
|
|
14
|
+
require('../chunk-ECLUFKYR.cjs');
|
|
15
15
|
require('../chunk-4ONPEVTP.cjs');
|
|
16
16
|
require('../chunk-V3KLUWQQ.cjs');
|
|
17
17
|
require('../chunk-P5AQYC4D.cjs');
|
|
@@ -28,19 +28,19 @@ require('../chunk-B3LZITVF.cjs');
|
|
|
28
28
|
require('../chunk-MZZ3SBQL.cjs');
|
|
29
29
|
require('../chunk-NY3YUCOW.cjs');
|
|
30
30
|
require('../chunk-325GVZE6.cjs');
|
|
31
|
-
require('../chunk-
|
|
31
|
+
require('../chunk-WQMR3O2D.cjs');
|
|
32
32
|
require('../chunk-ORELHENJ.cjs');
|
|
33
33
|
require('../chunk-NWNVWO6L.cjs');
|
|
34
34
|
require('../chunk-SK6HQ46Q.cjs');
|
|
35
35
|
require('../chunk-MNIQDXM6.cjs');
|
|
36
36
|
require('../chunk-E3Y7RI3J.cjs');
|
|
37
|
-
require('../chunk-
|
|
37
|
+
require('../chunk-TG2GM7WJ.cjs');
|
|
38
38
|
require('../chunk-VKH3BEZE.cjs');
|
|
39
39
|
require('../chunk-OEJQFPWO.cjs');
|
|
40
40
|
require('../chunk-OYXWQTHF.cjs');
|
|
41
41
|
require('../chunk-S237ORM3.cjs');
|
|
42
42
|
require('../chunk-II2OEXHW.cjs');
|
|
43
|
-
require('../chunk-
|
|
43
|
+
require('../chunk-QQWHG3KF.cjs');
|
|
44
44
|
require('../chunk-3YEXZX4H.cjs');
|
|
45
45
|
require('../chunk-4PA6VCCR.cjs');
|
|
46
46
|
require('../chunk-WXUU3COI.cjs');
|
|
@@ -85,7 +85,7 @@ require('../chunk-PLIF5CUV.cjs');
|
|
|
85
85
|
|
|
86
86
|
Object.defineProperty(exports, "ConnectedDocumentViewer", {
|
|
87
87
|
enumerable: true,
|
|
88
|
-
get: function () { return
|
|
88
|
+
get: function () { return chunkX36UR4A7_cjs.ConnectedDocumentViewer; }
|
|
89
89
|
});
|
|
90
90
|
Object.defineProperty(exports, "ConnectedDocumentToolsProvider", {
|
|
91
91
|
enumerable: true,
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
4
|
-
UserEntity,
|
|
5
|
-
VerifiedToggle,
|
|
6
|
-
VisibilityToggle,
|
|
7
|
-
formatFriendlyDate
|
|
8
|
-
} from "@arbidocs/react/ui";
|
|
3
|
+
import { UserEntity, VerifiedToggle, formatFriendlyDate, SharingEntity } from "@arbidocs/react/ui";
|
|
9
4
|
import { Quote } from "lucide-react";
|
|
10
5
|
import { memo, useEffect, useState } from "react";
|
|
11
6
|
import {
|
|
@@ -111,13 +106,12 @@ function createFactPageColumns({
|
|
|
111
106
|
}
|
|
112
107
|
),
|
|
113
108
|
renderShared: (fact, params) => /* @__PURE__ */ jsx(
|
|
114
|
-
|
|
109
|
+
SharingEntity,
|
|
115
110
|
{
|
|
116
|
-
|
|
111
|
+
shared: fact.shared,
|
|
117
112
|
onToggle: () => onToggleShared(fact),
|
|
118
113
|
disabled: params.context?.canChangeSharing?.(fact) === false,
|
|
119
114
|
disabledTooltip: "Only the creator can change who can see this",
|
|
120
|
-
showLabel: false,
|
|
121
115
|
tooltipActive: "Shared with this workspace \u2014 click to make private to you & your agents",
|
|
122
116
|
tooltipInactive: "Private to you & your agents \u2014 click to share with this workspace",
|
|
123
117
|
"data-testid": `facts-grid-shared-${fact.external_id}`
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/connection/facts/factPageColumns.tsx"],
|
|
4
|
-
"sourcesContent": ["/**\n * The Facts page's grid columns: the blocks Facts columns bound to the page \u2014 toggles, creator\n * avatar, label picker and row actions come from the page via the grid `context` (so the column\n * defs are built once and never rebuilt), and the callbacks own the mutations. A column header\n * keeps its filter button on screen (`FactHeader`).\n */\nimport {\n UserEntity,\n VerifiedToggle,\n VisibilityToggle,\n formatFriendlyDate,\n} from '@arbidocs/react/ui'\nimport type { ColDef, ICellRendererParams, IHeaderParams } from 'ag-grid-community'\nimport { Quote } from 'lucide-react'\nimport { memo, useEffect, useState, type ReactNode } from 'react'\nimport {\n DEFAULT_FACT_COLUMN_LABELS,\n createFactColumns,\n sourceDocCount,\n} from '../../facts/factColumns'\nimport { FactRowActions } from '../../facts/FactRowActions'\nimport { FilterButton } from '../../files/grid/FilterButton'\nimport type { Fact } from './types'\n\n/** A fact's creator, as the page knows them. */\nexport interface FactCreator {\n external_id: string\n given_name?: string | null\n family_name?: string | null\n email?: string | null\n picture?: string | null\n}\n\n/** Resolves a creator's external id to the user. */\nexport type LookupUser = (externalId: string | null | undefined) => FactCreator | null\n\ninterface FactGridContext {\n lookupUser?: LookupUser\n /** The host's own creator avatar. */\n renderUser?: (user: FactCreator | null) => ReactNode\n renderFactTags?: (fact: Fact, rowHeight?: number) => ReactNode\n openEdit?: (fact: Fact) => void\n removeFact?: (fact: Fact) => void\n /** Who may change who can see a fact: its creator or a workspace owner. */\n canChangeSharing?: (fact: Fact) => boolean\n /** Who may reword a fact (it replaces the note): its creator or a workspace owner. */\n canRewrite?: (fact: Fact) => boolean\n /** Whether the fact is in the current chat (read live: the column defs are built once). */\n isInChat?: (fact: Fact) => boolean\n}\n\ninterface FactColumnCallbacks {\n /** Add the fact to the current chat, or take it out (absent: no chat, no In chat column). */\n onToggleInChat?: (fact: Fact) => void\n /** Whether the fact is in the current chat. */\n isInChat?: (fact: Fact) => boolean\n onToggleConfirmed: (fact: Fact) => void\n onToggleShared: (fact: Fact) => void\n}\n\n/** The Sources count opens the fact (as a double-click does), where its passages are listed. */\nfunction renderSourcesCell(params: ICellRendererParams<Fact>): ReactNode {\n const fact = params.data\n const count = sourceDocCount(fact)\n if (!fact || count === 0) return <span className=\"text-muted-foreground/40\">\u2014</span>\n return (\n <button\n type=\"button\"\n onClick={() => (params.context as FactGridContext | undefined)?.openEdit?.(fact)}\n title={DEFAULT_FACT_COLUMN_LABELS.sourcedFrom(count)}\n data-testid={`facts-grid-sources-${fact.external_id}`}\n className=\"inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground\"\n >\n <Quote className=\"size-3 opacity-70\" />\n {count}\n </button>\n )\n}\n\n/** Column header with a PERSISTENT filter button, as the document grid uses.\n *\n * AG Grid's built-in button only appears on header hover, which is invisible on\n * a touchscreen and easy to miss anywhere else. Rendering FilterButton\n * ourselves keeps the control on screen and matches every other grid.\n *\n * A custom header also has to wire sorting itself. This mirrors the tag column\n * header exactly (createTagColumn): the whole header is the click target and\n * the direction shows via AG Grid's own `ag-icon` sort indicator \u2014 so a\n * sortable column here (e.g. Dates) sorts and LOOKS identical to the standard\n * columns, rather than with a bespoke control.\n */\nconst FactHeader = memo(function FactHeader({\n params,\n colId,\n}: {\n params: IHeaderParams\n colId: string\n}) {\n const [sort, setSort] = useState<'asc' | 'desc' | null | undefined>(params.column.getSort())\n\n useEffect(() => {\n const updateSort = () => setSort(params.column.getSort())\n updateSort()\n params.column.addEventListener('sortChanged', updateSort)\n return () => params.column.removeEventListener('sortChanged', updateSort)\n }, [params.column])\n\n return (\n <div\n className={`flex h-full w-full items-center gap-2 px-2 ${\n params.enableSorting ? 'cursor-pointer' : ''\n }`}\n onClick={(e) => {\n if (params.enableSorting) params.progressSort(e.shiftKey)\n }}\n >\n {/* FilterButton carries AG Grid's `.ag-header-icon` class, which the theme\n gives `order: 1`; order-2 keeps the label to its right. */}\n <FilterButton params={params} colId={colId} />\n <span className=\"order-2 min-w-0 flex-1 truncate\">{params.displayName}</span>\n {/* AG Grid's own sort-indicator markup (container + asc/desc icons toggled\n via `ag-hidden`), so the theme styles it byte-for-byte like the default\n header on the standard columns. */}\n <span className=\"ag-sort-indicator-container order-2 flex-shrink-0\" aria-hidden=\"true\">\n <span\n className={`ag-sort-indicator-icon ag-sort-ascending-icon ${\n sort === 'asc' ? '' : 'ag-hidden'\n }`}\n >\n <span className=\"ag-icon ag-icon-asc\" role=\"presentation\" />\n </span>\n <span\n className={`ag-sort-indicator-icon ag-sort-descending-icon ${\n sort === 'desc' ? '' : 'ag-hidden'\n }`}\n >\n <span className=\"ag-icon ag-icon-desc\" role=\"presentation\" />\n </span>\n </span>\n </div>\n )\n})\n\nexport function createFactPageColumns({\n onToggleInChat,\n isInChat,\n onToggleConfirmed,\n onToggleShared,\n}: FactColumnCallbacks): ColDef<Fact>[] {\n const columns = createFactColumns<Fact>({\n onToggleInChat,\n isInChat,\n renderHeader: (params, colId) => <FactHeader params={params} colId={colId} />,\n renderRowActions: (fact, params) => {\n const ctx = params.context as FactGridContext | undefined\n return (\n <FactRowActions\n onEdit={() => ctx?.openEdit?.(fact)}\n canEdit={ctx?.canRewrite?.(fact) !== false}\n onDelete={() => ctx?.removeFact?.(fact)}\n />\n )\n },\n // Pass the row height so the labels clamp to the rows that fully fit.\n renderLabels: (fact, params) =>\n (params.context as FactGridContext | undefined)?.renderFactTags?.(\n fact,\n params.node.rowHeight ?? 52\n ) ?? null,\n renderVerified: (fact) => (\n <VerifiedToggle\n verified={fact.confirmed}\n onToggle={() => onToggleConfirmed(fact)}\n showLabel={false}\n data-testid={`facts-grid-confirmed-${fact.external_id}`}\n />\n ),\n renderShared: (fact, params) => (\n <VisibilityToggle\n items={{ shared: fact.shared }}\n onToggle={() => onToggleShared(fact)}\n disabled={\n (params.context as FactGridContext | undefined)?.canChangeSharing?.(fact) === false\n }\n disabledTooltip=\"Only the creator can change who can see this\"\n showLabel={false}\n tooltipActive=\"Shared with this workspace \u2014 click to make private to you & your agents\"\n tooltipInactive=\"Private to you & your agents \u2014 click to share with this workspace\"\n data-testid={`facts-grid-shared-${fact.external_id}`}\n />\n ),\n // Users are read from the grid context, not captured in the closure: the column defs are\n // built once, and a stale lookup would render every creator as unknown.\n renderCreator: (fact, params) => {\n const ctx = params.context as FactGridContext | undefined\n const user = ctx?.lookupUser?.(fact.created_by_ext_id) ?? null\n if (ctx?.renderUser) return ctx.renderUser(user)\n return (\n <UserEntity\n size=\"sm\"\n variant=\"icon\"\n displayName={\n user\n ? [user.given_name, user.family_name].filter(Boolean).join(' ') || user.email || ''\n : undefined\n }\n initials={\n user\n ? [user.given_name, user.family_name]\n .filter(Boolean)\n .map((n) => (n as string)[0]?.toUpperCase())\n .join('')\n .slice(0, 2)\n : undefined\n }\n picture={user?.picture}\n />\n )\n },\n getCreatorName: (params) => {\n const creator = (params.context as FactGridContext | undefined)?.lookupUser?.(\n params.data?.created_by_ext_id\n )\n if (!creator) return ''\n return (\n [creator.given_name, creator.family_name].filter(Boolean).join(' ') || creator.email || ''\n )\n },\n formatDate: formatFriendlyDate,\n })\n return dateBeforeFact(\n columns.map((col) => {\n if (col.colId === 'sources') return { ...col, cellRenderer: renderSourcesCell }\n // The list arrives newest-created first, which reads as a jumbled chronology: open by date.\n if (col.colId === 'date') return { ...col, sort: 'asc', comparator: compareDatesUndatedLast }\n return col\n })\n )\n}\n\n/**\n * Orders the date-only values (null when undated) so undated facts come after the dated ones,\n * instead of AG Grid's default of listing nulls first. Descending, the grid reverses this, so\n * undated facts lead; the ascending chronology is the one that opens on them otherwise.\n */\nfunction compareDatesUndatedLast(a: string | null, b: string | null): number {\n if (!a && !b) return 0\n if (!a) return 1\n if (!b) return -1\n if (a === b) return 0\n return a < b ? -1 : 1\n}\n\n/**\n * Dates read before the fact, as a chronology does (date, fact, source), so the date sits beside\n * the text instead of past Labels and Verified, where a narrow table pushes it out of view.\n */\nfunction dateBeforeFact(columns: ColDef<Fact>[]): ColDef<Fact>[] {\n const date = columns.find((c) => c.colId === 'date')\n const rest = columns.filter((c) => c.colId !== 'date')\n const textAt = rest.findIndex((c) => c.colId === 'text')\n if (!date || textAt === -1) return columns\n return [...rest.slice(0, textAt), date, ...rest.slice(textAt)]\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["/**\n * The Facts page's grid columns: the blocks Facts columns bound to the page \u2014 toggles, creator\n * avatar, label picker and row actions come from the page via the grid `context` (so the column\n * defs are built once and never rebuilt), and the callbacks own the mutations. A column header\n * keeps its filter button on screen (`FactHeader`).\n */\nimport { UserEntity, VerifiedToggle, formatFriendlyDate, SharingEntity } from '@arbidocs/react/ui'\nimport type { ColDef, ICellRendererParams, IHeaderParams } from 'ag-grid-community'\nimport { Quote } from 'lucide-react'\nimport { memo, useEffect, useState, type ReactNode } from 'react'\nimport {\n DEFAULT_FACT_COLUMN_LABELS,\n createFactColumns,\n sourceDocCount,\n} from '../../facts/factColumns'\nimport { FactRowActions } from '../../facts/FactRowActions'\nimport { FilterButton } from '../../files/grid/FilterButton'\nimport type { Fact } from './types'\n\n/** A fact's creator, as the page knows them. */\nexport interface FactCreator {\n external_id: string\n given_name?: string | null\n family_name?: string | null\n email?: string | null\n picture?: string | null\n}\n\n/** Resolves a creator's external id to the user. */\nexport type LookupUser = (externalId: string | null | undefined) => FactCreator | null\n\ninterface FactGridContext {\n lookupUser?: LookupUser\n /** The host's own creator avatar. */\n renderUser?: (user: FactCreator | null) => ReactNode\n renderFactTags?: (fact: Fact, rowHeight?: number) => ReactNode\n openEdit?: (fact: Fact) => void\n removeFact?: (fact: Fact) => void\n /** Who may change who can see a fact: its creator or a workspace owner. */\n canChangeSharing?: (fact: Fact) => boolean\n /** Who may reword a fact (it replaces the note): its creator or a workspace owner. */\n canRewrite?: (fact: Fact) => boolean\n /** Whether the fact is in the current chat (read live: the column defs are built once). */\n isInChat?: (fact: Fact) => boolean\n}\n\ninterface FactColumnCallbacks {\n /** Add the fact to the current chat, or take it out (absent: no chat, no In chat column). */\n onToggleInChat?: (fact: Fact) => void\n /** Whether the fact is in the current chat. */\n isInChat?: (fact: Fact) => boolean\n onToggleConfirmed: (fact: Fact) => void\n onToggleShared: (fact: Fact) => void\n}\n\n/** The Sources count opens the fact (as a double-click does), where its passages are listed. */\nfunction renderSourcesCell(params: ICellRendererParams<Fact>): ReactNode {\n const fact = params.data\n const count = sourceDocCount(fact)\n if (!fact || count === 0) return <span className=\"text-muted-foreground/40\">\u2014</span>\n return (\n <button\n type=\"button\"\n onClick={() => (params.context as FactGridContext | undefined)?.openEdit?.(fact)}\n title={DEFAULT_FACT_COLUMN_LABELS.sourcedFrom(count)}\n data-testid={`facts-grid-sources-${fact.external_id}`}\n className=\"inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground\"\n >\n <Quote className=\"size-3 opacity-70\" />\n {count}\n </button>\n )\n}\n\n/** Column header with a PERSISTENT filter button, as the document grid uses.\n *\n * AG Grid's built-in button only appears on header hover, which is invisible on\n * a touchscreen and easy to miss anywhere else. Rendering FilterButton\n * ourselves keeps the control on screen and matches every other grid.\n *\n * A custom header also has to wire sorting itself. This mirrors the tag column\n * header exactly (createTagColumn): the whole header is the click target and\n * the direction shows via AG Grid's own `ag-icon` sort indicator \u2014 so a\n * sortable column here (e.g. Dates) sorts and LOOKS identical to the standard\n * columns, rather than with a bespoke control.\n */\nconst FactHeader = memo(function FactHeader({\n params,\n colId,\n}: {\n params: IHeaderParams\n colId: string\n}) {\n const [sort, setSort] = useState<'asc' | 'desc' | null | undefined>(params.column.getSort())\n\n useEffect(() => {\n const updateSort = () => setSort(params.column.getSort())\n updateSort()\n params.column.addEventListener('sortChanged', updateSort)\n return () => params.column.removeEventListener('sortChanged', updateSort)\n }, [params.column])\n\n return (\n <div\n className={`flex h-full w-full items-center gap-2 px-2 ${\n params.enableSorting ? 'cursor-pointer' : ''\n }`}\n onClick={(e) => {\n if (params.enableSorting) params.progressSort(e.shiftKey)\n }}\n >\n {/* FilterButton carries AG Grid's `.ag-header-icon` class, which the theme\n gives `order: 1`; order-2 keeps the label to its right. */}\n <FilterButton params={params} colId={colId} />\n <span className=\"order-2 min-w-0 flex-1 truncate\">{params.displayName}</span>\n {/* AG Grid's own sort-indicator markup (container + asc/desc icons toggled\n via `ag-hidden`), so the theme styles it byte-for-byte like the default\n header on the standard columns. */}\n <span className=\"ag-sort-indicator-container order-2 flex-shrink-0\" aria-hidden=\"true\">\n <span\n className={`ag-sort-indicator-icon ag-sort-ascending-icon ${\n sort === 'asc' ? '' : 'ag-hidden'\n }`}\n >\n <span className=\"ag-icon ag-icon-asc\" role=\"presentation\" />\n </span>\n <span\n className={`ag-sort-indicator-icon ag-sort-descending-icon ${\n sort === 'desc' ? '' : 'ag-hidden'\n }`}\n >\n <span className=\"ag-icon ag-icon-desc\" role=\"presentation\" />\n </span>\n </span>\n </div>\n )\n})\n\nexport function createFactPageColumns({\n onToggleInChat,\n isInChat,\n onToggleConfirmed,\n onToggleShared,\n}: FactColumnCallbacks): ColDef<Fact>[] {\n const columns = createFactColumns<Fact>({\n onToggleInChat,\n isInChat,\n renderHeader: (params, colId) => <FactHeader params={params} colId={colId} />,\n renderRowActions: (fact, params) => {\n const ctx = params.context as FactGridContext | undefined\n return (\n <FactRowActions\n onEdit={() => ctx?.openEdit?.(fact)}\n canEdit={ctx?.canRewrite?.(fact) !== false}\n onDelete={() => ctx?.removeFact?.(fact)}\n />\n )\n },\n // Pass the row height so the labels clamp to the rows that fully fit.\n renderLabels: (fact, params) =>\n (params.context as FactGridContext | undefined)?.renderFactTags?.(\n fact,\n params.node.rowHeight ?? 52\n ) ?? null,\n renderVerified: (fact) => (\n <VerifiedToggle\n verified={fact.confirmed}\n onToggle={() => onToggleConfirmed(fact)}\n showLabel={false}\n data-testid={`facts-grid-confirmed-${fact.external_id}`}\n />\n ),\n renderShared: (fact, params) => (\n <SharingEntity\n shared={fact.shared}\n onToggle={() => onToggleShared(fact)}\n disabled={\n (params.context as FactGridContext | undefined)?.canChangeSharing?.(fact) === false\n }\n disabledTooltip=\"Only the creator can change who can see this\"\n tooltipActive=\"Shared with this workspace \u2014 click to make private to you & your agents\"\n tooltipInactive=\"Private to you & your agents \u2014 click to share with this workspace\"\n data-testid={`facts-grid-shared-${fact.external_id}`}\n />\n ),\n // Users are read from the grid context, not captured in the closure: the column defs are\n // built once, and a stale lookup would render every creator as unknown.\n renderCreator: (fact, params) => {\n const ctx = params.context as FactGridContext | undefined\n const user = ctx?.lookupUser?.(fact.created_by_ext_id) ?? null\n if (ctx?.renderUser) return ctx.renderUser(user)\n return (\n <UserEntity\n size=\"sm\"\n variant=\"icon\"\n displayName={\n user\n ? [user.given_name, user.family_name].filter(Boolean).join(' ') || user.email || ''\n : undefined\n }\n initials={\n user\n ? [user.given_name, user.family_name]\n .filter(Boolean)\n .map((n) => (n as string)[0]?.toUpperCase())\n .join('')\n .slice(0, 2)\n : undefined\n }\n picture={user?.picture}\n />\n )\n },\n getCreatorName: (params) => {\n const creator = (params.context as FactGridContext | undefined)?.lookupUser?.(\n params.data?.created_by_ext_id\n )\n if (!creator) return ''\n return (\n [creator.given_name, creator.family_name].filter(Boolean).join(' ') || creator.email || ''\n )\n },\n formatDate: formatFriendlyDate,\n })\n return dateBeforeFact(\n columns.map((col) => {\n if (col.colId === 'sources') return { ...col, cellRenderer: renderSourcesCell }\n // The list arrives newest-created first, which reads as a jumbled chronology: open by date.\n if (col.colId === 'date') return { ...col, sort: 'asc', comparator: compareDatesUndatedLast }\n return col\n })\n )\n}\n\n/**\n * Orders the date-only values (null when undated) so undated facts come after the dated ones,\n * instead of AG Grid's default of listing nulls first. Descending, the grid reverses this, so\n * undated facts lead; the ascending chronology is the one that opens on them otherwise.\n */\nfunction compareDatesUndatedLast(a: string | null, b: string | null): number {\n if (!a && !b) return 0\n if (!a) return 1\n if (!b) return -1\n if (a === b) return 0\n return a < b ? -1 : 1\n}\n\n/**\n * Dates read before the fact, as a chronology does (date, fact, source), so the date sits beside\n * the text instead of past Labels and Verified, where a narrow table pushes it out of view.\n */\nfunction dateBeforeFact(columns: ColDef<Fact>[]): ColDef<Fact>[] {\n const date = columns.find((c) => c.colId === 'date')\n const rest = columns.filter((c) => c.colId !== 'date')\n const textAt = rest.findIndex((c) => c.colId === 'text')\n if (!date || textAt === -1) return columns\n return [...rest.slice(0, textAt), date, ...rest.slice(textAt)]\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["FactHeader"]
|
|
7
7
|
}
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
agentAvatarColors,
|
|
15
15
|
formatFolderLabel,
|
|
16
16
|
getEntityColor,
|
|
17
|
-
getEntityLabel,
|
|
18
17
|
getUserAvatarColor,
|
|
19
18
|
getUserDisplayName,
|
|
20
19
|
getUserInitials,
|
|
@@ -180,8 +179,7 @@ const DocumentSharedCell = memo(function DocumentSharedCell2({
|
|
|
180
179
|
disabled,
|
|
181
180
|
disabledTooltip,
|
|
182
181
|
tooltipActive: visibilityToggleTooltip(true),
|
|
183
|
-
tooltipInactive: visibilityToggleTooltip(false)
|
|
184
|
-
workspaceLabel: getEntityLabel("WORKSPACE")
|
|
182
|
+
tooltipInactive: visibilityToggleTooltip(false)
|
|
185
183
|
}
|
|
186
184
|
);
|
|
187
185
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/connection/files/grid/cells.tsx"],
|
|
4
|
-
"sourcesContent": ["/**\n * The documents table's cell bindings: the document pill with its row menu, who can see it, its\n * folder, its related documents, its labels and its uploader. The presentation is in `files/`;\n * these bind it to the session. Writes come in through props or the grid's context, never a hook\n * per row, so a large table adds no per-row subscriptions beyond what a cell must show.\n */\n\nimport { memo, useCallback, useEffect, useMemo, useRef } from 'react'\nimport { Eye } from 'lucide-react'\nimport {\n useArbi,\n useIsCreatingTag,\n useOpenViewerDocumentId,\n useTags,\n type DocumentHostActions,\n type RelatedDocs,\n type useAssignDocTags,\n type useCreateTag,\n type useRemoveDocTags,\n} from '@arbidocs/react'\nimport {\n DocumentEntity,\n UserEntity,\n agentAvatarColors,\n formatFolderLabel,\n getEntityColor,\n getEntityLabel,\n getUserAvatarColor,\n getUserDisplayName,\n getUserInitials,\n isAgent,\n} from '@arbidocs/react/ui'\nimport {\n SIGNED_LOCKED_REASON,\n currentHasMarkup,\n downloadBlob,\n fetchMarkupPdf,\n isSignedVersion,\n} from '@arbidocs/react/document-tools/core'\nimport { SharedCell as GenericSharedCell } from '../../../components/SharedCell'\nimport { DocumentNameCellFrame } from '../../../files/cells/DocumentNameCell'\nimport { DocumentRowMenu } from '../../../files/cells/DocumentNameCell'\nimport { FolderCell } from '../../../files/cells/FolderCell'\nimport { RelatedDocsCell } from '../../../files/cells/RelatedDocsCell'\nimport { LabelMultiSelectView, type LabelOption } from '../../../files/cells/LabelMultiSelect'\nimport { FolderCommandList } from '../../../files/FolderPicker'\nimport {\n FolderProviderIcon,\n formatFolderSegment,\n hasFolderProviderIcon,\n} from '../../../files/folders'\nimport { visibilityToggleTooltip } from '../../../files/visibility'\nimport { useBlocksEnvironment } from '../../../provider/environment'\nimport { DocumentSyncGlyph } from '../DocumentSyncGlyph'\nimport { FileTooltipContent, type FileTileDocument, type FileTileUser } from '../explorer/FileTiles'\nimport { DocumentStatusGlyph } from './documentStatus'\n\n/** A person as a table cell shows them. */\nexport type GridUser = FileTileUser & {\n is_identity_verified?: boolean | null\n}\n\n/** A small avatar for a person (initials, or their picture); opens them when the host can. */\nexport const UserAvatar = memo(function UserAvatar({\n user,\n isMe = false,\n onOpen,\n}: {\n user: GridUser | undefined\n isMe?: boolean\n onOpen?: (userId: string) => void\n}) {\n if (!user) return <UserEntity empty size=\"xs\" />\n const colorClass = getUserAvatarColor(user.external_id)\n const agent = isAgent(user)\n return (\n <UserEntity\n size=\"xs\"\n variant=\"icon\"\n showTooltip={false}\n interactive={!!onOpen}\n initials={getUserInitials(user)}\n displayName={isMe ? 'You' : getUserDisplayName(user)}\n picture={user.picture}\n colorClass={colorClass}\n isAgent={agent}\n isVerified={user.is_identity_verified === true}\n agentStyle={agent ? agentAvatarColors(colorClass) : undefined}\n isMe={isMe}\n onClick={\n onOpen\n ? (e) => {\n e.stopPropagation()\n onOpen(user.external_id)\n }\n : undefined\n }\n />\n )\n})\n\n/** The document pill's hover card: rendered only while open, so its lookups run on hover. */\nfunction DocumentSummaryTooltip({\n doc,\n users,\n}: {\n doc: FileTileDocument\n users: readonly GridUser[]\n}) {\n const { data: tags } = useTags()\n const modifiedBy = users.find(\n (u) => u.external_id === (doc.updated_by_ext_id ?? doc.created_by_ext_id)\n )\n const tagNameById = new Map<string, string>()\n for (const tag of tags ?? []) tagNameById.set(tag.external_id, tag.name)\n return <FileTooltipContent doc={doc} modifiedBy={modifiedBy} tagNameById={tagNameById} />\n}\n\n/**\n * How long a click waits before opening: a double-click on the title edits it, so its first click\n * must not open the document before the second one lands.\n */\nconst OPEN_AFTER_DOUBLE_CLICK_WINDOW_MS = 300\n\nexport interface DocumentNameCellProps {\n doc: FileTileDocument\n /** The viewer may rename, move or delete the document. */\n canEdit: boolean\n /** The workspace's members (the hover card's \"last modified by\"). */\n users: readonly GridUser[]\n onOpen: () => void\n /** Open in the review view. Absent: not offered. */\n onReview?: () => void\n /** Start a chat about the document. Absent: not offered. */\n onDocumentChat?: () => void\n onRename: () => void\n onDownload: () => void\n onMove: () => void\n onDelete: () => void\n}\n\n/**\n * The Document column: the document pill (exactly as documents look everywhere), opened by a\n * click, with the offline mark on its icon, its status after the name and the row menu.\n */\nexport const DocumentNameCell = memo(function DocumentNameCell({\n doc,\n canEdit,\n users,\n onOpen,\n onReview,\n onDocumentChat,\n onRename,\n onDownload,\n onMove,\n onDelete,\n}: DocumentNameCellProps) {\n const arbi = useArbi()\n const { notify } = useBlocksEnvironment()\n // The row of the document open in the viewer is marked (the grid styles the row from the cell's\n // `data-viewing`); only the rows whose state flips re-render.\n const isViewing = useOpenViewerDocumentId() === doc.external_id\n const openTimerRef = useRef<number | undefined>(undefined)\n useEffect(() => () => window.clearTimeout(openTimerRef.current), [])\n\n const downloadMarkup = async () => {\n try {\n const { blob, fileName } = await fetchMarkupPdf(arbi.getClient().fetch, doc.external_id)\n downloadBlob(blob, fileName)\n } catch (e) {\n notify(e instanceof Error ? e.message : 'The markup could not be downloaded', 'error')\n }\n }\n\n return (\n <DocumentNameCellFrame\n testId={`open-document-viewer-button-${doc.external_id}`}\n isViewing={isViewing}\n subtitle={[doc.created_at?.slice(0, 10), doc.folder ? formatFolderLabel(doc.folder) : null]\n .filter(Boolean)\n .join(' \u00B7 ')}\n >\n <DocumentEntity\n doc={doc}\n size=\"md\"\n labelMaxWidth=\"max-w-full\"\n fullWidth\n status={<DocumentStatusGlyph doc={doc} />}\n tooltipContent={<DocumentSummaryTooltip doc={doc} users={users} />}\n iconBadge={\n <DocumentSyncGlyph\n docExtId={doc.external_id}\n size={10}\n className=\"absolute -bottom-1 -right-1\"\n />\n }\n onClick={(e) => {\n // Opens; the row is not selected too. Rename is the menu or a double-click.\n e.stopPropagation()\n window.clearTimeout(openTimerRef.current)\n if (e.detail > 1) return\n openTimerRef.current = window.setTimeout(onOpen, OPEN_AFTER_DOUBLE_CLICK_WINDOW_MS)\n }}\n actions={\n <DocumentRowMenu\n canEdit={canEdit}\n onOpen={onOpen}\n onReview={onReview}\n onDocumentChat={onDocumentChat}\n onRename={onRename}\n onDownload={onDownload}\n onDownloadMarkup={currentHasMarkup(doc) ? () => void downloadMarkup() : undefined}\n // A signed version is locked: its markup is never written into its PDF.\n downloadMarkupDisabledReason={isSignedVersion(doc) ? SIGNED_LOCKED_REASON : undefined}\n onMove={onMove}\n onDelete={onDelete}\n />\n }\n // Transparent, so the row's tint (private, selected, hover) shows through.\n className=\"min-w-0 max-w-full border-transparent bg-transparent hover:bg-foreground/5\"\n />\n </DocumentNameCellFrame>\n )\n})\n\n/** Who can see the document: a toggle for its owner. */\nexport const DocumentSharedCell = memo(function DocumentSharedCell({\n document,\n disabled = false,\n disabledTooltip,\n onToggle,\n}: {\n document: FileTileDocument\n disabled?: boolean\n disabledTooltip?: string\n onToggle: (docId: string, shared: boolean) => void\n}) {\n const handleToggle = useCallback(\n (shouldShare: boolean) => onToggle(document.external_id, shouldShare),\n [document.external_id, onToggle]\n )\n return (\n <GenericSharedCell\n item={document}\n onToggle={handleToggle}\n disabled={disabled}\n disabledTooltip={disabledTooltip}\n tooltipActive={visibilityToggleTooltip(true)}\n tooltipInactive={visibilityToggleTooltip(false)}\n workspaceLabel={getEntityLabel('WORKSPACE')}\n />\n )\n})\n\n/** The folders documents are in (and `value`), for a folder picker. */\nexport function useDocumentFolders(\n documents: readonly { folder?: string | null }[],\n value?: string | null\n): string[] {\n return useMemo(() => {\n const paths = new Set<string>()\n for (const doc of documents) if (doc.folder) paths.add(doc.folder)\n if (value) paths.add(value)\n return [...paths].sort((a, b) => a.localeCompare(b))\n }, [documents, value])\n}\n\n/** A provider root displays shortened (its logo carries the name); a bare root is dropped. */\nfunction folderSegments(folder: string): string[] {\n return folder\n .split('/')\n .map((part, i) => (i === 0 ? formatFolderSegment(part) : part))\n .filter((part, i) => i > 0 || part !== '')\n}\n\n/** The document's folder, changed by a picker of the workspace's folders. */\nexport const DocumentFolderCell = memo(\n function DocumentFolderCell({\n document,\n isEditable,\n allDocuments,\n onUpdateFolder,\n }: {\n document: FileTileDocument\n isEditable: boolean\n allDocuments: readonly FileTileDocument[]\n onUpdateFolder: (docId: string, folder: string | null) => void\n }) {\n const folder = document.folder\n const folders = useDocumentFolders(allDocuments, folder)\n return (\n <FolderCell\n folder={folder}\n segments={folder ? folderSegments(folder) : []}\n icon={\n folder && hasFolderProviderIcon(folder) ? (\n <FolderProviderIcon folderPath={folder} className=\"h-3.5 w-3.5 flex-none shrink-0\" />\n ) : undefined\n }\n isEditable={isEditable}\n picker={\n <FolderCommandList\n folders={folders}\n value={folder}\n onSelect={(next) => onUpdateFolder(document.external_id, next)}\n showRemove={!!folder}\n />\n }\n />\n )\n },\n (prev, next) =>\n prev.document.external_id === next.document.external_id &&\n prev.document.folder === next.document.folder &&\n prev.isEditable === next.isEditable &&\n prev.allDocuments === next.allDocuments &&\n prev.onUpdateFolder === next.onUpdateFolder\n)\n\n/** How a made-from or copy document is related, in words (its `derived_from` role). */\nconst RELATION_LABELS: Record<string, string> = {\n redaction: 'Redacted copy',\n annotated: 'Annotated copy',\n production: 'Production',\n crop: 'Crop',\n redline_base: 'Redline',\n redline_revised: 'Redline',\n translation: 'Translation',\n}\nconst relationLabel = (role: string | undefined, version?: number) => {\n const label = role ? RELATION_LABELS[role] : undefined\n return version ? `${label ?? 'Made'} from v${version}` : label\n}\n\n/** The document's related documents: its email family, copies, duplicates and near-duplicates. */\nexport const RelatedDocumentsCell = memo(function RelatedDocumentsCell({\n currentDoc,\n related,\n viewingDocumentId,\n onDocumentClick,\n onDelete,\n canDelete,\n onCompare,\n}: {\n currentDoc: FileTileDocument\n related: RelatedDocs<FileTileDocument>\n /** The document open in the viewer (its pill is marked). */\n viewingDocumentId: string | null\n onDocumentClick: (docId: string) => void\n onDelete: (doc: FileTileDocument) => void\n canDelete?: (doc: FileTileDocument) => boolean\n /** Compare with a similar document. Absent: not offered. */\n onCompare?: (docId: string, otherDocId: string) => void\n}) {\n return (\n <RelatedDocsCell<FileTileDocument>\n parent={related.parent}\n attachments={related.children}\n madeFrom={related.madeFrom}\n copies={related.copies}\n getRelation={(doc) =>\n relationLabel(related.roles[doc.external_id], related.versions?.[doc.external_id])\n }\n duplicates={related.duplicates}\n similar={related.similar.map(({ doc }) => doc)}\n renderDoc={(doc) => (\n <DocumentEntity\n doc={doc}\n icon={\n viewingDocumentId === doc.external_id ? (\n <Eye className=\"h-3.5 w-3.5 flex-shrink-0 text-primary\" />\n ) : undefined\n }\n onClick={() => onDocumentClick(doc.external_id)}\n />\n )}\n getKey={(doc) => doc.external_id}\n onSwitchTo={(doc) => {\n const version = related.versions?.[doc.external_id]\n onDocumentClick(version ? `${doc.external_id}@v${version}` : doc.external_id)\n }}\n onDelete={onDelete}\n canDelete={canDelete}\n onCompare={\n onCompare ? (doc) => onCompare(currentDoc.external_id, doc.external_id) : undefined\n }\n />\n )\n})\n\nconst NOT_A_MEMBER_MESSAGE =\n 'Join this workspace as a collaborator or owner to create or apply labels.'\n\n/** A label as the Labels column offers it. */\nexport interface GridLabel {\n external_id: string\n name: string\n}\n\n/** The document's labels: toggle one, or create one by name and apply it. */\nexport const DocumentLabelsCell = memo(function DocumentLabelsCell({\n doc,\n labels,\n createDocTag,\n deleteDocTag,\n createTag,\n isMember,\n rowHeight,\n actions,\n}: {\n doc: FileTileDocument\n labels: readonly GridLabel[]\n createDocTag: Pick<ReturnType<typeof useAssignDocTags>, 'mutateAsync'>\n deleteDocTag: Pick<ReturnType<typeof useRemoveDocTags>, 'mutateAsync'>\n createTag: Pick<ReturnType<typeof useCreateTag>, 'mutateAsync'>\n isMember: boolean\n /** The row height: the chips are clamped to the rows that fully fit. */\n rowHeight?: number\n actions?: DocumentHostActions\n}) {\n const isCreatingTag = useIsCreatingTag()\n const { notify } = useBlocksEnvironment()\n\n const options: LabelOption[] = useMemo(\n () =>\n labels.map((t) => ({\n id: t.external_id,\n name: t.name,\n color: getEntityColor(t.external_id, 'medium'),\n })),\n [labels]\n )\n const appliedIds = useMemo(\n () =>\n new Set(\n (doc.doctags || [])\n .filter((dt) => labels.some((t) => t.external_id === dt.tag_ext_id))\n .map((dt) => dt.tag_ext_id)\n ),\n [doc.doctags, labels]\n )\n\n const applyTag = useCallback(\n (tagId: string) => createDocTag.mutateAsync({ tagId, docIds: [doc.external_id] }),\n [createDocTag, doc.external_id]\n )\n\n const handleToggle = useCallback(\n async (tagId: string) => {\n if (!isMember) return notify(NOT_A_MEMBER_MESSAGE, 'info')\n if (appliedIds.has(tagId)) {\n await deleteDocTag.mutateAsync({ tagId, docIds: [doc.external_id] })\n } else {\n await applyTag(tagId)\n }\n },\n [isMember, appliedIds, deleteDocTag, applyTag, doc.external_id, notify]\n )\n\n const handleCreate = useCallback(\n async (name: string) => {\n const trimmed = name.trim()\n if (!trimmed) return\n if (!isMember) return notify(NOT_A_MEMBER_MESSAGE, 'info')\n const existing = labels.find((t) => t.name.toLowerCase() === trimmed.toLowerCase())\n if (existing) {\n if (!appliedIds.has(existing.external_id)) await applyTag(existing.external_id)\n return\n }\n // The same name is already being created (a double Enter): one label, not two.\n if (isCreatingTag(trimmed)) return\n const created = await createTag.mutateAsync({\n name: trimmed,\n tagType: { type: 'checkbox', options: [] },\n shared: false,\n })\n if (created?.external_id) await applyTag(created.external_id)\n },\n [isMember, labels, appliedIds, createTag, applyTag, isCreatingTag, notify]\n )\n\n const openFieldsManager = actions?.openFieldsManager\n return (\n <LabelMultiSelectView\n options={options}\n appliedIds={appliedIds}\n onToggle={handleToggle}\n onCreate={handleCreate}\n onEditLabels={openFieldsManager ? () => openFieldsManager() : undefined}\n rowHeight={rowHeight}\n />\n )\n})\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["/**\n * The documents table's cell bindings: the document pill with its row menu, who can see it, its\n * folder, its related documents, its labels and its uploader. The presentation is in `files/`;\n * these bind it to the session. Writes come in through props or the grid's context, never a hook\n * per row, so a large table adds no per-row subscriptions beyond what a cell must show.\n */\n\nimport { memo, useCallback, useEffect, useMemo, useRef } from 'react'\nimport { Eye } from 'lucide-react'\nimport {\n useArbi,\n useIsCreatingTag,\n useOpenViewerDocumentId,\n useTags,\n type DocumentHostActions,\n type RelatedDocs,\n type useAssignDocTags,\n type useCreateTag,\n type useRemoveDocTags,\n} from '@arbidocs/react'\nimport {\n DocumentEntity,\n UserEntity,\n agentAvatarColors,\n formatFolderLabel,\n getEntityColor,\n getUserAvatarColor,\n getUserDisplayName,\n getUserInitials,\n isAgent,\n} from '@arbidocs/react/ui'\nimport {\n SIGNED_LOCKED_REASON,\n currentHasMarkup,\n downloadBlob,\n fetchMarkupPdf,\n isSignedVersion,\n} from '@arbidocs/react/document-tools/core'\nimport { SharedCell as GenericSharedCell } from '../../../components/SharedCell'\nimport { DocumentNameCellFrame } from '../../../files/cells/DocumentNameCell'\nimport { DocumentRowMenu } from '../../../files/cells/DocumentNameCell'\nimport { FolderCell } from '../../../files/cells/FolderCell'\nimport { RelatedDocsCell } from '../../../files/cells/RelatedDocsCell'\nimport { LabelMultiSelectView, type LabelOption } from '../../../files/cells/LabelMultiSelect'\nimport { FolderCommandList } from '../../../files/FolderPicker'\nimport {\n FolderProviderIcon,\n formatFolderSegment,\n hasFolderProviderIcon,\n} from '../../../files/folders'\nimport { visibilityToggleTooltip } from '../../../files/visibility'\nimport { useBlocksEnvironment } from '../../../provider/environment'\nimport { DocumentSyncGlyph } from '../DocumentSyncGlyph'\nimport { FileTooltipContent, type FileTileDocument, type FileTileUser } from '../explorer/FileTiles'\nimport { DocumentStatusGlyph } from './documentStatus'\n\n/** A person as a table cell shows them. */\nexport type GridUser = FileTileUser & {\n is_identity_verified?: boolean | null\n}\n\n/** A small avatar for a person (initials, or their picture); opens them when the host can. */\nexport const UserAvatar = memo(function UserAvatar({\n user,\n isMe = false,\n onOpen,\n}: {\n user: GridUser | undefined\n isMe?: boolean\n onOpen?: (userId: string) => void\n}) {\n if (!user) return <UserEntity empty size=\"xs\" />\n const colorClass = getUserAvatarColor(user.external_id)\n const agent = isAgent(user)\n return (\n <UserEntity\n size=\"xs\"\n variant=\"icon\"\n showTooltip={false}\n interactive={!!onOpen}\n initials={getUserInitials(user)}\n displayName={isMe ? 'You' : getUserDisplayName(user)}\n picture={user.picture}\n colorClass={colorClass}\n isAgent={agent}\n isVerified={user.is_identity_verified === true}\n agentStyle={agent ? agentAvatarColors(colorClass) : undefined}\n isMe={isMe}\n onClick={\n onOpen\n ? (e) => {\n e.stopPropagation()\n onOpen(user.external_id)\n }\n : undefined\n }\n />\n )\n})\n\n/** The document pill's hover card: rendered only while open, so its lookups run on hover. */\nfunction DocumentSummaryTooltip({\n doc,\n users,\n}: {\n doc: FileTileDocument\n users: readonly GridUser[]\n}) {\n const { data: tags } = useTags()\n const modifiedBy = users.find(\n (u) => u.external_id === (doc.updated_by_ext_id ?? doc.created_by_ext_id)\n )\n const tagNameById = new Map<string, string>()\n for (const tag of tags ?? []) tagNameById.set(tag.external_id, tag.name)\n return <FileTooltipContent doc={doc} modifiedBy={modifiedBy} tagNameById={tagNameById} />\n}\n\n/**\n * How long a click waits before opening: a double-click on the title edits it, so its first click\n * must not open the document before the second one lands.\n */\nconst OPEN_AFTER_DOUBLE_CLICK_WINDOW_MS = 300\n\nexport interface DocumentNameCellProps {\n doc: FileTileDocument\n /** The viewer may rename, move or delete the document. */\n canEdit: boolean\n /** The workspace's members (the hover card's \"last modified by\"). */\n users: readonly GridUser[]\n onOpen: () => void\n /** Open in the review view. Absent: not offered. */\n onReview?: () => void\n /** Start a chat about the document. Absent: not offered. */\n onDocumentChat?: () => void\n onRename: () => void\n onDownload: () => void\n onMove: () => void\n onDelete: () => void\n}\n\n/**\n * The Document column: the document pill (exactly as documents look everywhere), opened by a\n * click, with the offline mark on its icon, its status after the name and the row menu.\n */\nexport const DocumentNameCell = memo(function DocumentNameCell({\n doc,\n canEdit,\n users,\n onOpen,\n onReview,\n onDocumentChat,\n onRename,\n onDownload,\n onMove,\n onDelete,\n}: DocumentNameCellProps) {\n const arbi = useArbi()\n const { notify } = useBlocksEnvironment()\n // The row of the document open in the viewer is marked (the grid styles the row from the cell's\n // `data-viewing`); only the rows whose state flips re-render.\n const isViewing = useOpenViewerDocumentId() === doc.external_id\n const openTimerRef = useRef<number | undefined>(undefined)\n useEffect(() => () => window.clearTimeout(openTimerRef.current), [])\n\n const downloadMarkup = async () => {\n try {\n const { blob, fileName } = await fetchMarkupPdf(arbi.getClient().fetch, doc.external_id)\n downloadBlob(blob, fileName)\n } catch (e) {\n notify(e instanceof Error ? e.message : 'The markup could not be downloaded', 'error')\n }\n }\n\n return (\n <DocumentNameCellFrame\n testId={`open-document-viewer-button-${doc.external_id}`}\n isViewing={isViewing}\n subtitle={[doc.created_at?.slice(0, 10), doc.folder ? formatFolderLabel(doc.folder) : null]\n .filter(Boolean)\n .join(' \u00B7 ')}\n >\n <DocumentEntity\n doc={doc}\n size=\"md\"\n labelMaxWidth=\"max-w-full\"\n fullWidth\n status={<DocumentStatusGlyph doc={doc} />}\n tooltipContent={<DocumentSummaryTooltip doc={doc} users={users} />}\n iconBadge={\n <DocumentSyncGlyph\n docExtId={doc.external_id}\n size={10}\n className=\"absolute -bottom-1 -right-1\"\n />\n }\n onClick={(e) => {\n // Opens; the row is not selected too. Rename is the menu or a double-click.\n e.stopPropagation()\n window.clearTimeout(openTimerRef.current)\n if (e.detail > 1) return\n openTimerRef.current = window.setTimeout(onOpen, OPEN_AFTER_DOUBLE_CLICK_WINDOW_MS)\n }}\n actions={\n <DocumentRowMenu\n canEdit={canEdit}\n onOpen={onOpen}\n onReview={onReview}\n onDocumentChat={onDocumentChat}\n onRename={onRename}\n onDownload={onDownload}\n onDownloadMarkup={currentHasMarkup(doc) ? () => void downloadMarkup() : undefined}\n // A signed version is locked: its markup is never written into its PDF.\n downloadMarkupDisabledReason={isSignedVersion(doc) ? SIGNED_LOCKED_REASON : undefined}\n onMove={onMove}\n onDelete={onDelete}\n />\n }\n // Transparent, so the row's tint (private, selected, hover) shows through.\n className=\"min-w-0 max-w-full border-transparent bg-transparent hover:bg-foreground/5\"\n />\n </DocumentNameCellFrame>\n )\n})\n\n/** Who can see the document: a toggle for its owner. */\nexport const DocumentSharedCell = memo(function DocumentSharedCell({\n document,\n disabled = false,\n disabledTooltip,\n onToggle,\n}: {\n document: FileTileDocument\n disabled?: boolean\n disabledTooltip?: string\n onToggle: (docId: string, shared: boolean) => void\n}) {\n const handleToggle = useCallback(\n (shouldShare: boolean) => onToggle(document.external_id, shouldShare),\n [document.external_id, onToggle]\n )\n return (\n <GenericSharedCell\n item={document}\n onToggle={handleToggle}\n disabled={disabled}\n disabledTooltip={disabledTooltip}\n tooltipActive={visibilityToggleTooltip(true)}\n tooltipInactive={visibilityToggleTooltip(false)}\n />\n )\n})\n\n/** The folders documents are in (and `value`), for a folder picker. */\nexport function useDocumentFolders(\n documents: readonly { folder?: string | null }[],\n value?: string | null\n): string[] {\n return useMemo(() => {\n const paths = new Set<string>()\n for (const doc of documents) if (doc.folder) paths.add(doc.folder)\n if (value) paths.add(value)\n return [...paths].sort((a, b) => a.localeCompare(b))\n }, [documents, value])\n}\n\n/** A provider root displays shortened (its logo carries the name); a bare root is dropped. */\nfunction folderSegments(folder: string): string[] {\n return folder\n .split('/')\n .map((part, i) => (i === 0 ? formatFolderSegment(part) : part))\n .filter((part, i) => i > 0 || part !== '')\n}\n\n/** The document's folder, changed by a picker of the workspace's folders. */\nexport const DocumentFolderCell = memo(\n function DocumentFolderCell({\n document,\n isEditable,\n allDocuments,\n onUpdateFolder,\n }: {\n document: FileTileDocument\n isEditable: boolean\n allDocuments: readonly FileTileDocument[]\n onUpdateFolder: (docId: string, folder: string | null) => void\n }) {\n const folder = document.folder\n const folders = useDocumentFolders(allDocuments, folder)\n return (\n <FolderCell\n folder={folder}\n segments={folder ? folderSegments(folder) : []}\n icon={\n folder && hasFolderProviderIcon(folder) ? (\n <FolderProviderIcon folderPath={folder} className=\"h-3.5 w-3.5 flex-none shrink-0\" />\n ) : undefined\n }\n isEditable={isEditable}\n picker={\n <FolderCommandList\n folders={folders}\n value={folder}\n onSelect={(next) => onUpdateFolder(document.external_id, next)}\n showRemove={!!folder}\n />\n }\n />\n )\n },\n (prev, next) =>\n prev.document.external_id === next.document.external_id &&\n prev.document.folder === next.document.folder &&\n prev.isEditable === next.isEditable &&\n prev.allDocuments === next.allDocuments &&\n prev.onUpdateFolder === next.onUpdateFolder\n)\n\n/** How a made-from or copy document is related, in words (its `derived_from` role). */\nconst RELATION_LABELS: Record<string, string> = {\n redaction: 'Redacted copy',\n annotated: 'Annotated copy',\n production: 'Production',\n crop: 'Crop',\n redline_base: 'Redline',\n redline_revised: 'Redline',\n translation: 'Translation',\n}\nconst relationLabel = (role: string | undefined, version?: number) => {\n const label = role ? RELATION_LABELS[role] : undefined\n return version ? `${label ?? 'Made'} from v${version}` : label\n}\n\n/** The document's related documents: its email family, copies, duplicates and near-duplicates. */\nexport const RelatedDocumentsCell = memo(function RelatedDocumentsCell({\n currentDoc,\n related,\n viewingDocumentId,\n onDocumentClick,\n onDelete,\n canDelete,\n onCompare,\n}: {\n currentDoc: FileTileDocument\n related: RelatedDocs<FileTileDocument>\n /** The document open in the viewer (its pill is marked). */\n viewingDocumentId: string | null\n onDocumentClick: (docId: string) => void\n onDelete: (doc: FileTileDocument) => void\n canDelete?: (doc: FileTileDocument) => boolean\n /** Compare with a similar document. Absent: not offered. */\n onCompare?: (docId: string, otherDocId: string) => void\n}) {\n return (\n <RelatedDocsCell<FileTileDocument>\n parent={related.parent}\n attachments={related.children}\n madeFrom={related.madeFrom}\n copies={related.copies}\n getRelation={(doc) =>\n relationLabel(related.roles[doc.external_id], related.versions?.[doc.external_id])\n }\n duplicates={related.duplicates}\n similar={related.similar.map(({ doc }) => doc)}\n renderDoc={(doc) => (\n <DocumentEntity\n doc={doc}\n icon={\n viewingDocumentId === doc.external_id ? (\n <Eye className=\"h-3.5 w-3.5 flex-shrink-0 text-primary\" />\n ) : undefined\n }\n onClick={() => onDocumentClick(doc.external_id)}\n />\n )}\n getKey={(doc) => doc.external_id}\n onSwitchTo={(doc) => {\n const version = related.versions?.[doc.external_id]\n onDocumentClick(version ? `${doc.external_id}@v${version}` : doc.external_id)\n }}\n onDelete={onDelete}\n canDelete={canDelete}\n onCompare={\n onCompare ? (doc) => onCompare(currentDoc.external_id, doc.external_id) : undefined\n }\n />\n )\n})\n\nconst NOT_A_MEMBER_MESSAGE =\n 'Join this workspace as a collaborator or owner to create or apply labels.'\n\n/** A label as the Labels column offers it. */\nexport interface GridLabel {\n external_id: string\n name: string\n}\n\n/** The document's labels: toggle one, or create one by name and apply it. */\nexport const DocumentLabelsCell = memo(function DocumentLabelsCell({\n doc,\n labels,\n createDocTag,\n deleteDocTag,\n createTag,\n isMember,\n rowHeight,\n actions,\n}: {\n doc: FileTileDocument\n labels: readonly GridLabel[]\n createDocTag: Pick<ReturnType<typeof useAssignDocTags>, 'mutateAsync'>\n deleteDocTag: Pick<ReturnType<typeof useRemoveDocTags>, 'mutateAsync'>\n createTag: Pick<ReturnType<typeof useCreateTag>, 'mutateAsync'>\n isMember: boolean\n /** The row height: the chips are clamped to the rows that fully fit. */\n rowHeight?: number\n actions?: DocumentHostActions\n}) {\n const isCreatingTag = useIsCreatingTag()\n const { notify } = useBlocksEnvironment()\n\n const options: LabelOption[] = useMemo(\n () =>\n labels.map((t) => ({\n id: t.external_id,\n name: t.name,\n color: getEntityColor(t.external_id, 'medium'),\n })),\n [labels]\n )\n const appliedIds = useMemo(\n () =>\n new Set(\n (doc.doctags || [])\n .filter((dt) => labels.some((t) => t.external_id === dt.tag_ext_id))\n .map((dt) => dt.tag_ext_id)\n ),\n [doc.doctags, labels]\n )\n\n const applyTag = useCallback(\n (tagId: string) => createDocTag.mutateAsync({ tagId, docIds: [doc.external_id] }),\n [createDocTag, doc.external_id]\n )\n\n const handleToggle = useCallback(\n async (tagId: string) => {\n if (!isMember) return notify(NOT_A_MEMBER_MESSAGE, 'info')\n if (appliedIds.has(tagId)) {\n await deleteDocTag.mutateAsync({ tagId, docIds: [doc.external_id] })\n } else {\n await applyTag(tagId)\n }\n },\n [isMember, appliedIds, deleteDocTag, applyTag, doc.external_id, notify]\n )\n\n const handleCreate = useCallback(\n async (name: string) => {\n const trimmed = name.trim()\n if (!trimmed) return\n if (!isMember) return notify(NOT_A_MEMBER_MESSAGE, 'info')\n const existing = labels.find((t) => t.name.toLowerCase() === trimmed.toLowerCase())\n if (existing) {\n if (!appliedIds.has(existing.external_id)) await applyTag(existing.external_id)\n return\n }\n // The same name is already being created (a double Enter): one label, not two.\n if (isCreatingTag(trimmed)) return\n const created = await createTag.mutateAsync({\n name: trimmed,\n tagType: { type: 'checkbox', options: [] },\n shared: false,\n })\n if (created?.external_id) await applyTag(created.external_id)\n },\n [isMember, labels, appliedIds, createTag, applyTag, isCreatingTag, notify]\n )\n\n const openFieldsManager = actions?.openFieldsManager\n return (\n <LabelMultiSelectView\n options={options}\n appliedIds={appliedIds}\n onToggle={handleToggle}\n onCreate={handleCreate}\n onEditLabels={openFieldsManager ? () => openFieldsManager() : undefined}\n rowHeight={rowHeight}\n />\n )\n})\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["UserAvatar", "DocumentNameCell", "DocumentSharedCell", "DocumentFolderCell", "RelatedDocumentsCell", "DocumentLabelsCell"]
|
|
7
7
|
}
|
|
@@ -492,9 +492,10 @@ function createDocumentColumns({
|
|
|
492
492
|
});
|
|
493
493
|
allColumns.push({
|
|
494
494
|
colId: "shared",
|
|
495
|
-
headerName: "
|
|
495
|
+
headerName: "Sharing",
|
|
496
496
|
field: "shared",
|
|
497
|
-
width:
|
|
497
|
+
width: 124,
|
|
498
|
+
minWidth: 124,
|
|
498
499
|
sortable: true,
|
|
499
500
|
cellDataType: "boolean",
|
|
500
501
|
context: { icon: Share2 },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/connection/files/grid/documentColumns.tsx"],
|
|
4
|
-
"sourcesContent": ["/**\n * The documents table's columns (explicit `colId`s; AG Grid's saved state decides which show and\n * in what order): the document pill, its status, its AI fields (type, author, abstract, date), its\n * file facts, folder, sender, fingerprint, related documents, visibility, uploader, labels, id, and\n * one column per AI field. Writes come in as parameters, the row-independent lookups through the\n * grid's context, so the columns are rebuilt only when the fields themselves change.\n */\n\nimport type { ColDef, IRowNode } from 'ag-grid-community'\nimport {\n Calendar,\n Clock,\n File,\n FileText,\n FileType,\n Fingerprint,\n FolderOpen,\n Hash,\n HardDrive,\n Link2,\n Mail,\n Paperclip,\n Share2,\n Sparkles,\n Tags,\n Upload,\n User,\n} from 'lucide-react'\nimport {\n formatFileSize,\n isAnnotationTag,\n isLabelTag,\n type TagResponse,\n} from '@arbidocs/sdk/browser'\nimport {\n type DocumentHostActions,\n type RelatedDocsMap,\n type useAssignDocTags,\n type useCreateTag,\n type useRemoveDocTags,\n type useUpdateDocTag,\n} from '@arbidocs/react'\nimport {\n formatDocDate,\n formatFolderLabel,\n formatFriendlyDate,\n friendlyFileType,\n getDocumentAuthor,\n getDocumentDate,\n getDocumentNature,\n getDocumentSubject,\n getDocumentTitle,\n markdownToPlainText,\n} from '@arbidocs/react/ui'\nimport { DOCUMENT_TYPE_OPTIONS } from '../../../files/documentColumns'\nimport {\n canChangeVisibility,\n canEditDocumentFields,\n type WorkspaceMemberRole,\n} from '../../../files/permissions'\nimport type { FileTileDocument } from '../explorer/FileTiles'\nimport type { GridUser } from './cells'\nimport {\n CreatedByCellRenderer,\n DocumentNameCellRenderer,\n FolderCellRenderer,\n LabelsCellRenderer,\n RelatedDocsCellRenderer,\n SharedCellRenderer,\n StatusCellRenderer,\n type DocumentNameCellParams,\n type FolderCellParams,\n type LabelsCellParams,\n type RelatedDocsCellParams,\n type SharedCellParams,\n type StatusCellParams,\n} from './documentCellRenderers'\nimport { createTagColumn } from './tagColumn'\n\n/** A row of the documents table. */\nexport type GridDocument = FileTileDocument & {\n /** The email sender of a document imported from mail (\"Ada <ada@x.com>\"). */\n sender?: string | null\n}\n\ntype Doc = GridDocument\ntype Tag = TagResponse\n\n/**\n * What the columns read through the grid's `context` (row-independent, so the column definitions\n * stay put when they change).\n */\nexport interface DocumentGridContext {\n getDocuments?: () => GridDocument[]\n getRelatedDocs?: () => RelatedDocsMap<GridDocument>\n getFamilyMeta?: () => Map<string, { tier: number; count: number }>\n getWorkspaceUsers?: () => GridUser[]\n getCurrentUserId?: () => string | undefined\n getViewingDocumentId?: () => string | null\n getHostActions?: () => DocumentHostActions\n updateFolder?: (docId: string, folder: string | null) => void\n}\n\n// Stable filter params object (reused across all filter columns)\nconst FILTER_PARAMS = {\n buttons: ['reset', 'apply'] as const,\n closeOnApply: true,\n}\n\nexport interface CreateDocumentColumnsParams {\n handleToggleDocumentViewer: (docId: string) => void\n onStatusClick?: () => void\n /** Per-row document actions for the unified name column's \u22EF kebab. */\n onReviewDocument?: (docId: string) => void\n /** Start a new chat scoped to one document, opening it in the side viewer. */\n onDocumentChat?: (docId: string) => void\n onDownloadDocument?: (doc: Doc) => void\n onMoveDocument?: (docId: string) => void\n onDeleteDocument?: (docId: string) => void\n isMember: boolean\n /**\n * The viewer's role in the workspace (null: not a member, e.g. a public workspace's visitor).\n * Only an owner or a collaborator edits documents someone else created.\n */\n workspaceRole: WorkspaceMemberRole | null\n currentUserExtId?: string\n availableTags: Tag[]\n createDocTag: Pick<ReturnType<typeof useAssignDocTags>, 'mutateAsync'>\n updateDocTag: Pick<ReturnType<typeof useUpdateDocTag>, 'mutateAsync'>\n deleteDocTag: Pick<ReturnType<typeof useRemoveDocTags>, 'mutateAsync'>\n createTag: Pick<ReturnType<typeof useCreateTag>, 'mutateAsync'>\n /** Share a document with the workspace, or make it private. */\n onToggleShared: (docId: string, shared: boolean) => void\n /** How a tag write's outcome is told (the environment's notify). */\n notify: (message: string, kind: 'success' | 'error') => void\n}\n\nexport function createDocumentColumns({\n handleToggleDocumentViewer,\n onStatusClick,\n onReviewDocument,\n onDocumentChat,\n onDownloadDocument,\n onMoveDocument,\n onDeleteDocument,\n isMember,\n workspaceRole,\n currentUserExtId,\n availableTags,\n createDocTag,\n updateDocTag,\n deleteDocTag,\n createTag,\n onToggleShared,\n notify,\n}: CreateDocumentColumnsParams): ColDef<Doc>[] {\n // Owner/creator check \u2014 the sole gate for the privacy/visibility toggle.\n const isRowEditableByOwner = (params: { data?: Doc | null }): boolean =>\n canChangeVisibility(params.data, currentUserExtId)\n\n // Regular fields: see canEditDocumentFields (shared with the detail pane).\n const isRowEditableByMember = (params: { data?: Doc | null }): boolean =>\n canEditDocumentFields(params.data, currentUserExtId, workspaceRole)\n\n const checkboxTags = availableTags.filter(isLabelTag)\n\n const allColumns: ColDef<Doc>[] = []\n\n // Status column - shows status indicator (checkmark, error, progress bar)\n allColumns.push({\n colId: 'status',\n headerName: 'Status',\n field: 'status',\n // Wide enough for the full \"Status\" header (plus its sort indicator) to show\n // without truncating. suppressSizeToFit so it doesn't grow to fill space.\n width: 144,\n minWidth: 128,\n suppressSizeToFit: true,\n sortable: true,\n cellDataType: 'text',\n context: { icon: Paperclip },\n valueGetter: (params) => {\n if (!params.data) return null\n return params.data.status?.toLowerCase() ?? null\n },\n comparator: (\n a: string | null,\n b: string | null,\n nodeA: IRowNode<Doc>,\n nodeB: IRowNode<Doc>\n ) => {\n const STATUS_ORDER: Record<string, number> = {\n queued: 0,\n parsing: 1,\n encrypting: 2,\n indexing: 3,\n analysing: 4,\n completed: 5,\n 'low-content': 6,\n empty: 7,\n skipped: 8,\n failed: 9,\n }\n const rankA = a != null ? (STATUS_ORDER[a] ?? 10) : 10\n const rankB = b != null ? (STATUS_ORDER[b] ?? 10) : 10\n if (rankA !== rankB) return rankA - rankB\n // Secondary sort: within 'failed', order by pipeline stage\n if (a === 'failed' && b === 'failed') {\n const STAGE_ORDER: Record<string, number> = {\n parsing: 0,\n encrypting: 1,\n indexing: 2,\n analysing: 3,\n queued: 4,\n unknown: 5,\n }\n const stageA = nodeA.data?.failed_stage ?? null\n const stageB = nodeB.data?.failed_stage ?? null\n const stageRankA = stageA != null ? (STAGE_ORDER[stageA] ?? 6) : 7\n const stageRankB = stageB != null ? (STAGE_ORDER[stageB] ?? 6) : 7\n return stageRankA - stageRankB\n }\n return 0\n },\n cellRenderer: StatusCellRenderer,\n cellRendererParams: { onStatusClick } satisfies StatusCellParams,\n // Status is a single icon \u2014 center it horizontally (header + cell) rather\n // than left-aligning a lone glyph against the column edge. `cell-status`\n // trims the cell's vertical padding so the progress bar + label fit (and sit\n // near the top) even at the minimum row height.\n cellClass: 'cell-hcenter cell-status',\n headerClass: 'header-hcenter',\n })\n\n // (The per-row \"In Chat\" toggle column was removed \u2014 adding documents to the\n // current chat is now done via the bulk-actions toolbar after selecting rows.)\n\n // Document Name column\n // Unified Document column: entity badge (icon + title) opens the doc on click;\n // a hover \u22EF kebab carries the row actions (incl. Rename \u2014 no inline pencil).\n // Stays editable so the kebab's Rename can trigger the inline title editor;\n // edit is double-click / kebab only (not single-click, which opens).\n allColumns.push({\n colId: 'title',\n headerName: 'Title',\n // `initialFlex` (not `flex`): a stateful `flex` gets re-asserted every time\n // AG Grid re-processes the column definitions \u2014 which happens on many host\n // re-renders, including the one triggered by persisting the grid state\n // right after a manual resize \u2014 recomputing Title back to fill the leftover\n // space and snapping away the user's drag. With initialFlex the fill only\n // applies when the column is first created; after that a manual resize\n // sticks. The 160px floor lets the phone's Title + Status list fit 390px without sideways scroll.\n initialFlex: 3,\n minWidth: isPhoneViewport() ? 160 : 280,\n // Title is intentionally NOT pinned on mount: the default layout wants the\n // row-select checkbox as the first column with Title second. AG Grid always\n // renders pinned-left columns ahead of unpinned ones, so pinning Title (while\n // the selection column stays unpinned) pushed Title into the first slot,\n // ahead of the checkbox. Leaving it unpinned matches the built-in \"Default\n // view\" (which unpins everything) and lets the selection column sit first.\n // The \"Document view\" still pins the checkbox + Title left for users who want\n // the name frozen while scrolling the many data columns.\n sortable: true,\n filter: 'agTextColumnFilter',\n filterParams: FILTER_PARAMS,\n editable: isRowEditableByMember,\n cellEditor: 'agLargeTextCellEditor',\n cellEditorPopup: true,\n cellEditorParams: { rows: 5, cols: 50 },\n context: { icon: FileText },\n cellRenderer: DocumentNameCellRenderer,\n cellRendererParams: {\n canEdit: isRowEditableByMember,\n onOpenDocument: handleToggleDocumentViewer,\n onReviewDocument,\n onDocumentChat,\n onDownloadDocument,\n onMoveDocument,\n onDeleteDocument,\n } satisfies DocumentNameCellParams,\n valueGetter: (params) => getDocumentTitle(params.data, 'Untitled'),\n valueSetter: (params) => {\n if (!params.data) return false\n if (!params.data.doc_metadata) {\n params.data.doc_metadata = {}\n }\n params.data.doc_metadata.title = params.newValue\n return true\n },\n // No AG Grid tooltip here \u2014 the DocumentEntity pill renders its own\n // AI-summary hover card (same one the explorer tiles use). A\n // tooltipValueGetter would stack a second, plain title tooltip on top.\n })\n\n // Document Type column (AI-generated, user-editable dropdown)\n // Options from DOCUMENT_TYPE_OPTIONS constant (synced with backend DocTagLLM.DEFAULT_METADATA_TAGS)\n allColumns.push({\n colId: 'doc_nature',\n headerName: 'Type',\n width: 120,\n minWidth: 100,\n sortable: true,\n filter: 'agTextColumnFilter',\n editable: isRowEditableByMember,\n singleClickEdit: true,\n cellEditor: 'agSelectCellEditor',\n cellEditorParams: {\n values: [...DOCUMENT_TYPE_OPTIONS],\n },\n context: { icon: Sparkles },\n valueGetter: (params) => getDocumentNature(params.data) || '',\n valueSetter: (params) => {\n if (!params.data) return false\n if (!params.data.doc_metadata) {\n params.data.doc_metadata = {}\n }\n params.data.doc_metadata.doc_nature = params.newValue || null\n return true\n },\n // Show dropdown indicator for editable cells (no custom renderer for performance)\n cellClass: (params) =>\n isRowEditableByMember(params) ? 'cell-select-editable' : 'cell-editable-readonly relative',\n })\n\n // Document Author column (AI-generated, user-editable)\n allColumns.push({\n colId: 'doc_author',\n headerName: 'Author',\n width: 150,\n minWidth: 100,\n sortable: true,\n filter: 'agTextColumnFilter',\n filterParams: FILTER_PARAMS,\n editable: isRowEditableByMember,\n singleClickEdit: true,\n cellEditor: 'agLargeTextCellEditor',\n cellEditorPopup: true,\n cellEditorParams: { rows: 3, cols: 40 },\n context: { icon: User },\n valueGetter: (params) => getDocumentAuthor(params.data) || '',\n valueSetter: (params) => {\n if (!params.data) return false\n if (!params.data.doc_metadata) {\n params.data.doc_metadata = {}\n }\n params.data.doc_metadata.doc_author = params.newValue || null\n return true\n },\n tooltipValueGetter: (params) => getDocumentAuthor(params.data) || '',\n // cell-multiline: wrap long author lists to as many lines as the current\n // row height allows (same height-driven clamp as Abstract).\n cellClass: (params) =>\n isRowEditableByMember(params)\n ? 'cell-editable-member relative cell-multiline'\n : 'cell-editable-readonly relative cell-multiline',\n })\n\n // Document Abstract column (AI-generated, user-editable)\n allColumns.push({\n colId: 'doc_subject',\n headerName: 'Abstract',\n width: 300,\n minWidth: 200,\n sortable: true,\n filter: 'agTextColumnFilter',\n filterParams: FILTER_PARAMS,\n editable: isRowEditableByMember,\n singleClickEdit: true,\n cellEditor: 'agLargeTextCellEditor',\n cellEditorPopup: true,\n cellEditorParams: { rows: 8, cols: 60 },\n context: { icon: FileText },\n // Edit against the raw markdown (so the large-text editor shows the real\n // source), but display a flattened plain-text preview in the cell and\n // tooltip \u2014 our AI summaries often contain markdown bullets/emphasis, and\n // rendering raw markers (\"* \u2026\", \"**\u2026**\") in a dense cell reads as noise.\n // markdownToPlainText is a cheap string transform run only on visible cells.\n valueGetter: (params) => getDocumentSubject(params.data) || '',\n valueFormatter: (params) => markdownToPlainText(params.value),\n valueSetter: (params) => {\n if (!params.data) return false\n if (!params.data.doc_metadata) {\n params.data.doc_metadata = {}\n }\n params.data.doc_metadata.doc_subject = params.newValue || null\n return true\n },\n tooltipValueGetter: (params) => markdownToPlainText(getDocumentSubject(params.data)),\n cellClass: (params) =>\n isRowEditableByMember(params)\n ? 'cell-editable-member relative cell-multiline'\n : 'cell-editable-readonly relative cell-multiline',\n })\n\n // Date column\n // Optimized: Using AG Grid's built-in editing + CSS-only hover icons (no React cellRenderer)\n allColumns.push({\n colId: 'doc_date',\n headerName: 'Date',\n width: 160,\n minWidth: 150, // Wider to fit an optional time (emails carry date + time)\n sortable: true,\n editable: isRowEditableByMember,\n singleClickEdit: true,\n context: { icon: Calendar },\n valueGetter: (params) => getDocumentDate(params.data),\n valueSetter: (params) => {\n if (!params.data) return false\n if (!params.data.doc_metadata) {\n params.data.doc_metadata = {}\n }\n params.data.doc_metadata.doc_date = params.newValue\n return true\n },\n valueFormatter: (params) => {\n if (!params.value) return '\u2014'\n return formatDocDate(params.value)\n },\n // Ensure text doesn't overflow - use cellStyle for overflow handling\n cellStyle: {\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n },\n // Add Tailwind classes + CSS class for hover icon (pencil if editable, ban if not)\n cellClass: (params) =>\n isRowEditableByMember(params)\n ? 'cell-editable-member relative cell-tnum'\n : 'cell-editable-readonly relative cell-tnum',\n // Date filter with a start/end range (date-only comparison; ignores any time).\n filter: 'agDateColumnFilter',\n filterParams: {\n browserDatePicker: true,\n inRangeInclusive: true,\n // cellValue is the raw doc_date string (date or datetime); compare date-only.\n comparator: (filterDate: Date, cellValue: string | null) => {\n if (!cellValue) return -1\n const d = new Date(cellValue)\n if (Number.isNaN(d.getTime())) return -1\n const cellDay = new Date(d.getFullYear(), d.getMonth(), d.getDate())\n if (cellDay < filterDate) return -1\n if (cellDay > filterDate) return 1\n return 0\n },\n },\n // No valueParser - let user type freely without validation interrupting\n // Validation happens in onCellValueChanged after editing completes\n // No cellRenderer - AG Grid handles rendering natively with valueFormatter\n })\n\n // Pages column\n // Optimized: Using valueFormatter instead of React cellRenderer\n allColumns.push({\n colId: 'n_pages',\n headerName: 'Pages',\n field: 'n_pages',\n width: 100,\n sortable: true,\n cellDataType: 'number',\n cellClass: 'cell-numeric',\n context: { icon: Hash },\n valueGetter: (params) => {\n return params.data?.n_pages || null\n },\n valueFormatter: (params) => {\n if (params.value === null || params.value === undefined) return '\u2014'\n return params.value.toLocaleString()\n },\n // No cellRenderer - valueFormatter handles display formatting\n })\n\n // Upload date column - just the date, sortable by date\n // Optimized: Using valueFormatter instead of React cellRenderer\n allColumns.push({\n colId: 'created_at',\n headerName: 'Uploaded',\n field: 'created_at',\n width: 160,\n sortable: true,\n filter: 'agDateColumnFilter',\n filterParams: FILTER_PARAMS,\n context: { icon: Upload },\n valueFormatter: (params) => {\n if (!params.value) return '\u2014'\n return formatFriendlyDate(params.value)\n },\n // No cellRenderer - valueFormatter handles display formatting\n })\n\n // Last modified column\n // Optimized: Using valueFormatter instead of React cellRenderer\n allColumns.push({\n colId: 'updated_at',\n headerName: 'Last modified',\n field: 'updated_at',\n width: 160,\n sortable: true,\n // Visibility controlled by grid state, not column definition\n filter: 'agDateColumnFilter',\n filterParams: FILTER_PARAMS,\n context: { icon: Clock },\n valueFormatter: (params) => {\n if (!params.value) return '\u2014'\n return formatFriendlyDate(params.value)\n },\n // No cellRenderer - valueFormatter handles display formatting\n })\n\n // File name column\n // Optimized: Using native rendering - no cellRenderer needed for simple text\n allColumns.push({\n colId: 'file_name',\n headerName: 'File name',\n field: 'file_name',\n width: 200,\n sortable: true,\n // Visibility controlled by grid state, not column definition\n filter: 'agTextColumnFilter',\n filterParams: FILTER_PARAMS,\n context: { icon: File },\n // No cellRenderer - AG Grid handles text rendering natively\n })\n\n // Size column\n // Optimized: Using valueFormatter instead of React cellRenderer\n allColumns.push({\n colId: 'file_size',\n headerName: 'Size',\n field: 'file_size',\n width: 100,\n sortable: true,\n // Visibility controlled by grid state, not column definition\n cellDataType: 'number',\n cellClass: 'cell-numeric',\n context: { icon: HardDrive },\n valueFormatter: (params) => formatFileSize(params.value),\n // No cellRenderer - valueFormatter handles display formatting\n })\n\n // File type column.\n //\n // Backend stores ``file_type`` as either a bare extension (``\"pdf\"``)\n // or a full MIME type (``\"application/pdf\"``) depending on which\n // upload path produced the row, so we never render the raw value.\n // ``valueGetter`` runs once per row to produce the friendly label \u2014\n // sort and the text filter then operate on that label, which is what\n // the user actually sees.\n allColumns.push({\n colId: 'file_type',\n headerName: 'File type',\n field: 'file_type',\n width: 120,\n sortable: true,\n filter: 'agTextColumnFilter',\n filterParams: FILTER_PARAMS,\n context: { icon: FileType },\n valueGetter: (params) => friendlyFileType(params.data?.file_type, params.data?.file_name),\n })\n\n // Folder column\n allColumns.push({\n colId: 'folder',\n headerName: 'Folder',\n field: 'folder',\n width: 180,\n sortable: true,\n filter: 'agTextColumnFilter',\n filterParams: FILTER_PARAMS,\n context: { icon: FolderOpen },\n valueFormatter: (params) => formatFolderLabel(params.value),\n cellRenderer: FolderCellRenderer,\n cellRendererParams: { canEdit: isRowEditableByMember } satisfies FolderCellParams,\n cellClass: (params) =>\n isRowEditableByMember(params)\n ? 'cell-editable-member relative'\n : 'cell-editable-readonly relative',\n })\n\n // From column \u2014 email sender for connector-imported docs (e.g.\n // \"John Smith <john@x.com>\"). Plain text; null for non-email docs.\n allColumns.push({\n colId: 'sender',\n headerName: 'From',\n field: 'sender',\n width: 180,\n minWidth: 100,\n sortable: true,\n filter: 'agTextColumnFilter',\n filterParams: FILTER_PARAMS,\n context: { icon: Mail },\n valueGetter: (params) => params.data?.sender ?? '',\n tooltipValueGetter: (params) => params.data?.sender ?? '',\n // No cellRenderer - AG Grid handles text rendering natively\n })\n\n // Content Hash column\n allColumns.push({\n colId: 'content_hash',\n headerName: 'Fingerprint',\n field: 'content_hash',\n width: 200,\n sortable: true,\n filter: 'agTextColumnFilter',\n filterParams: FILTER_PARAMS,\n context: { icon: Fingerprint },\n cellStyle: { fontFamily: 'monospace' },\n valueFormatter: (params) => params.value || '',\n })\n\n // Related documents column \u2014 shows parent, children, duplicates, similar docs.\n //\n // Tiered sort key (string-compared by AG Grid; defaults to descending so\n // the first header click pulls the most-relevant tier to the top):\n //\n // {tier}|{paddedCount}|{groupId}|{rank}|{external_id}\n //\n // - tier (single digit, higher = top under desc):\n // 3 family has any duplicates\n // 2 family has any similar (no dupes)\n // 1 pure parent/child family (no dupes, no similar)\n // 0 no relationships at all\n // Family tier is computed once per groupId in Documents.tsx so every\n // member of a family shares the same prefix and stays clustered.\n // - paddedCount: aggregate count for the tier, zero-padded to 6 chars\n // so a lexical compare matches a numeric one.\n // - groupId: parent_ext_id for children, own external_id for parents\n // and lone docs, content_hash for standalone duplicate clusters.\n // - rank: 0 for parent/standalone, 1 for children \u2192 parent sorts above\n // its children within a family.\n // - external_id: stable tiebreaker.\n allColumns.push({\n colId: 'related',\n headerName: 'Related',\n width: 200,\n minWidth: 120,\n sortable: true,\n sortingOrder: ['desc', 'asc', null],\n context: { icon: Link2 },\n valueGetter: (params) => {\n if (!params.data) return ''\n const doc = params.data\n const context = params.context as DocumentGridContext | undefined\n const map = context?.getRelatedDocs?.()\n const familyMeta = context?.getFamilyMeta?.()\n const related = map?.get(doc.external_id)\n const isChild = !!doc.parent_ext_id\n const isParent = related?.children && related.children.length > 0\n\n let groupId: string\n let rank: number\n\n if (isChild) {\n // Child: group under parent, rank 1 so parent sorts first\n groupId = doc.parent_ext_id!\n rank = 1\n } else if (isParent) {\n // Parent: group under self, rank 0\n groupId = doc.external_id\n rank = 0\n } else if (doc.content_hash && related && related.duplicates.length > 0) {\n // Standalone duplicate: group by fingerprint\n groupId = doc.content_hash\n rank = 0\n } else {\n // No relationships: sort by own ID\n groupId = doc.external_id\n rank = 0\n }\n\n const meta = familyMeta?.get(groupId) ?? { tier: 0, count: 0 }\n const paddedCount = String(meta.count).padStart(6, '0')\n\n return `${meta.tier}|${paddedCount}|${groupId}|${rank}|${doc.external_id}`\n },\n cellRenderer: RelatedDocsCellRenderer,\n cellRendererParams: {\n onOpenDocument: handleToggleDocumentViewer,\n onDeleteDocument,\n } satisfies RelatedDocsCellParams,\n })\n\n // Shared column\n // Note: Keeping SharedCell for interactive toggle (complex UI)\n allColumns.push({\n colId: 'shared',\n headerName: 'Shared',\n field: 'shared',\n width: 100,\n sortable: true,\n cellDataType: 'boolean',\n context: { icon: Share2 },\n valueGetter: (params) => {\n return params.data?.shared ?? false\n },\n cellRenderer: SharedCellRenderer,\n cellRendererParams: {\n canToggle: isRowEditableByOwner,\n onToggleShared,\n } satisfies SharedCellParams,\n })\n\n // Created by column - shows avatar icon (the members come through the grid's context)\n allColumns.push({\n colId: 'created_by',\n headerName: 'By',\n width: 60,\n sortable: true,\n context: { icon: User },\n valueGetter: (params) => {\n if (!params.data) return ''\n const users = (params.context as DocumentGridContext | undefined)?.getWorkspaceUsers?.()\n const creator = users?.find((u) => u.external_id === params.data!.created_by_ext_id)\n if (!creator) return ''\n const fullName = [creator.given_name, creator.family_name].filter(Boolean).join(' ')\n return fullName || creator.email || ''\n },\n cellRenderer: CreatedByCellRenderer,\n })\n\n // Labels column \u2014 shows applied checkbox/label tags as filled checkmarks\n allColumns.push({\n colId: 'all_tags',\n headerName: 'Labels',\n width: 300,\n // Never narrower than its own header (\"Labels\" + filter/menu icons) \u2014 the\n // title must not ellipsize, same floor as the Facts table's Labels column.\n minWidth: 132,\n sortable: false,\n filter: 'agTextColumnFilter',\n filterParams: FILTER_PARAMS,\n context: { icon: Tags },\n valueGetter: (params) => {\n if (!params.data || !params.data.doctags) return ''\n // Only index label (and issue) names for filtering\n const tagNames = params.data.doctags\n .map((dt) => {\n const tag = availableTags.find((t) => t.external_id === dt.tag_ext_id)\n if (!tag || !isLabelTag(tag)) return ''\n return tag.name\n })\n .filter(Boolean)\n return tagNames.join(' ')\n },\n cellRenderer: LabelsCellRenderer,\n cellRendererParams: {\n labels: checkboxTags,\n createDocTag,\n deleteDocTag,\n createTag,\n isMember,\n } satisfies LabelsCellParams,\n // Add CSS class for hover icon (matches title field)\n cellClass: isMember ? 'cell-editable-member relative !pr-0' : 'cell-editable-readonly relative',\n })\n\n // DocID column\n // Optimized: Using valueFormatter for monospace font styling\n // Note: AG Grid doesn't support className in valueFormatter, so we'll use cellStyle instead\n allColumns.push({\n colId: 'doc_id',\n headerName: 'DocID',\n field: 'external_id',\n width: 200,\n sortable: true,\n // Visibility controlled by grid state, not column definition\n filter: 'agTextColumnFilter',\n filterParams: FILTER_PARAMS,\n context: { icon: Hash },\n cellStyle: { fontFamily: 'monospace' },\n // No cellRenderer - native rendering with cellStyle for monospace font\n })\n\n // A column per annotation (labels and issues are in the Labels column)\n const tagColumns = availableTags.filter(isAnnotationTag).map((tag) =>\n createTagColumn({\n tag,\n isMember,\n notify,\n createDocTag,\n updateDocTag,\n deleteDocTag,\n })\n )\n\n // Return ALL columns - AG Grid state controls visibility and order\n return [...allColumns.map(withEmptyDash), ...tagColumns]\n}\n\n/** Title is the most important field, so it gets a wide floor everywhere but a phone. */\nfunction isPhoneViewport(): boolean {\n return (\n typeof window !== 'undefined' && window.matchMedia?.('(max-width: 480px)')?.matches === true\n )\n}\n\n/** Columns whose empty values read as a muted em dash (never a blank cell or an error-like word). */\nconst EMPTY_DASH_COLUMNS: ReadonlySet<string> = new Set([\n 'doc_nature',\n 'doc_author',\n 'doc_subject',\n 'doc_date',\n 'n_pages',\n 'created_at',\n 'updated_at',\n 'file_name',\n 'file_type',\n 'sender',\n])\n\n/** The AI abstract step writes this literal when it finds nothing; to a reader it looks like an error. */\nconst NO_VALUE = /^\\s*(not found|n\\/a)\\.?\\s*$/i\n\nfunction isEmptyCell(value: unknown): boolean {\n return value === null || value === undefined || value === '' || NO_VALUE.test(String(value))\n}\n\n/**\n * Show an em dash, in the muted text colour, for empty values of the plain data columns. Formatting\n * only: the underlying value (and what the cell editors start from) is untouched.\n */\nfunction withEmptyDash(col: ColDef<Doc>): ColDef<Doc> {\n if (!col.colId || !EMPTY_DASH_COLUMNS.has(col.colId)) return col\n const { valueFormatter, cellClass } = col\n return {\n ...col,\n valueFormatter: (params) => {\n if (col.colId !== 'n_pages' && isEmptyCell(params.value)) return '\u2014'\n return typeof valueFormatter === 'function'\n ? valueFormatter(params)\n : String(params.value ?? '')\n },\n cellClass: (params) => {\n const base = typeof cellClass === 'function' ? cellClass(params) : cellClass\n const classes = Array.isArray(base) ? base : base ? [base] : []\n return isEmptyCell(params.value) ? [...classes, 'cell-empty'] : classes\n },\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * The documents table's columns (explicit `colId`s; AG Grid's saved state decides which show and\n * in what order): the document pill, its status, its AI fields (type, author, abstract, date), its\n * file facts, folder, sender, fingerprint, related documents, visibility, uploader, labels, id, and\n * one column per AI field. Writes come in as parameters, the row-independent lookups through the\n * grid's context, so the columns are rebuilt only when the fields themselves change.\n */\n\nimport type { ColDef, IRowNode } from 'ag-grid-community'\nimport {\n Calendar,\n Clock,\n File,\n FileText,\n FileType,\n Fingerprint,\n FolderOpen,\n Hash,\n HardDrive,\n Link2,\n Mail,\n Paperclip,\n Share2,\n Sparkles,\n Tags,\n Upload,\n User,\n} from 'lucide-react'\nimport {\n formatFileSize,\n isAnnotationTag,\n isLabelTag,\n type TagResponse,\n} from '@arbidocs/sdk/browser'\nimport {\n type DocumentHostActions,\n type RelatedDocsMap,\n type useAssignDocTags,\n type useCreateTag,\n type useRemoveDocTags,\n type useUpdateDocTag,\n} from '@arbidocs/react'\nimport {\n formatDocDate,\n formatFolderLabel,\n formatFriendlyDate,\n friendlyFileType,\n getDocumentAuthor,\n getDocumentDate,\n getDocumentNature,\n getDocumentSubject,\n getDocumentTitle,\n markdownToPlainText,\n} from '@arbidocs/react/ui'\nimport { DOCUMENT_TYPE_OPTIONS } from '../../../files/documentColumns'\nimport {\n canChangeVisibility,\n canEditDocumentFields,\n type WorkspaceMemberRole,\n} from '../../../files/permissions'\nimport type { FileTileDocument } from '../explorer/FileTiles'\nimport type { GridUser } from './cells'\nimport {\n CreatedByCellRenderer,\n DocumentNameCellRenderer,\n FolderCellRenderer,\n LabelsCellRenderer,\n RelatedDocsCellRenderer,\n SharedCellRenderer,\n StatusCellRenderer,\n type DocumentNameCellParams,\n type FolderCellParams,\n type LabelsCellParams,\n type RelatedDocsCellParams,\n type SharedCellParams,\n type StatusCellParams,\n} from './documentCellRenderers'\nimport { createTagColumn } from './tagColumn'\n\n/** A row of the documents table. */\nexport type GridDocument = FileTileDocument & {\n /** The email sender of a document imported from mail (\"Ada <ada@x.com>\"). */\n sender?: string | null\n}\n\ntype Doc = GridDocument\ntype Tag = TagResponse\n\n/**\n * What the columns read through the grid's `context` (row-independent, so the column definitions\n * stay put when they change).\n */\nexport interface DocumentGridContext {\n getDocuments?: () => GridDocument[]\n getRelatedDocs?: () => RelatedDocsMap<GridDocument>\n getFamilyMeta?: () => Map<string, { tier: number; count: number }>\n getWorkspaceUsers?: () => GridUser[]\n getCurrentUserId?: () => string | undefined\n getViewingDocumentId?: () => string | null\n getHostActions?: () => DocumentHostActions\n updateFolder?: (docId: string, folder: string | null) => void\n}\n\n// Stable filter params object (reused across all filter columns)\nconst FILTER_PARAMS = {\n buttons: ['reset', 'apply'] as const,\n closeOnApply: true,\n}\n\nexport interface CreateDocumentColumnsParams {\n handleToggleDocumentViewer: (docId: string) => void\n onStatusClick?: () => void\n /** Per-row document actions for the unified name column's \u22EF kebab. */\n onReviewDocument?: (docId: string) => void\n /** Start a new chat scoped to one document, opening it in the side viewer. */\n onDocumentChat?: (docId: string) => void\n onDownloadDocument?: (doc: Doc) => void\n onMoveDocument?: (docId: string) => void\n onDeleteDocument?: (docId: string) => void\n isMember: boolean\n /**\n * The viewer's role in the workspace (null: not a member, e.g. a public workspace's visitor).\n * Only an owner or a collaborator edits documents someone else created.\n */\n workspaceRole: WorkspaceMemberRole | null\n currentUserExtId?: string\n availableTags: Tag[]\n createDocTag: Pick<ReturnType<typeof useAssignDocTags>, 'mutateAsync'>\n updateDocTag: Pick<ReturnType<typeof useUpdateDocTag>, 'mutateAsync'>\n deleteDocTag: Pick<ReturnType<typeof useRemoveDocTags>, 'mutateAsync'>\n createTag: Pick<ReturnType<typeof useCreateTag>, 'mutateAsync'>\n /** Share a document with the workspace, or make it private. */\n onToggleShared: (docId: string, shared: boolean) => void\n /** How a tag write's outcome is told (the environment's notify). */\n notify: (message: string, kind: 'success' | 'error') => void\n}\n\nexport function createDocumentColumns({\n handleToggleDocumentViewer,\n onStatusClick,\n onReviewDocument,\n onDocumentChat,\n onDownloadDocument,\n onMoveDocument,\n onDeleteDocument,\n isMember,\n workspaceRole,\n currentUserExtId,\n availableTags,\n createDocTag,\n updateDocTag,\n deleteDocTag,\n createTag,\n onToggleShared,\n notify,\n}: CreateDocumentColumnsParams): ColDef<Doc>[] {\n // Owner/creator check \u2014 the sole gate for the privacy/visibility toggle.\n const isRowEditableByOwner = (params: { data?: Doc | null }): boolean =>\n canChangeVisibility(params.data, currentUserExtId)\n\n // Regular fields: see canEditDocumentFields (shared with the detail pane).\n const isRowEditableByMember = (params: { data?: Doc | null }): boolean =>\n canEditDocumentFields(params.data, currentUserExtId, workspaceRole)\n\n const checkboxTags = availableTags.filter(isLabelTag)\n\n const allColumns: ColDef<Doc>[] = []\n\n // Status column - shows status indicator (checkmark, error, progress bar)\n allColumns.push({\n colId: 'status',\n headerName: 'Status',\n field: 'status',\n // Wide enough for the full \"Status\" header (plus its sort indicator) to show\n // without truncating. suppressSizeToFit so it doesn't grow to fill space.\n width: 144,\n minWidth: 128,\n suppressSizeToFit: true,\n sortable: true,\n cellDataType: 'text',\n context: { icon: Paperclip },\n valueGetter: (params) => {\n if (!params.data) return null\n return params.data.status?.toLowerCase() ?? null\n },\n comparator: (\n a: string | null,\n b: string | null,\n nodeA: IRowNode<Doc>,\n nodeB: IRowNode<Doc>\n ) => {\n const STATUS_ORDER: Record<string, number> = {\n queued: 0,\n parsing: 1,\n encrypting: 2,\n indexing: 3,\n analysing: 4,\n completed: 5,\n 'low-content': 6,\n empty: 7,\n skipped: 8,\n failed: 9,\n }\n const rankA = a != null ? (STATUS_ORDER[a] ?? 10) : 10\n const rankB = b != null ? (STATUS_ORDER[b] ?? 10) : 10\n if (rankA !== rankB) return rankA - rankB\n // Secondary sort: within 'failed', order by pipeline stage\n if (a === 'failed' && b === 'failed') {\n const STAGE_ORDER: Record<string, number> = {\n parsing: 0,\n encrypting: 1,\n indexing: 2,\n analysing: 3,\n queued: 4,\n unknown: 5,\n }\n const stageA = nodeA.data?.failed_stage ?? null\n const stageB = nodeB.data?.failed_stage ?? null\n const stageRankA = stageA != null ? (STAGE_ORDER[stageA] ?? 6) : 7\n const stageRankB = stageB != null ? (STAGE_ORDER[stageB] ?? 6) : 7\n return stageRankA - stageRankB\n }\n return 0\n },\n cellRenderer: StatusCellRenderer,\n cellRendererParams: { onStatusClick } satisfies StatusCellParams,\n // Status is a single icon \u2014 center it horizontally (header + cell) rather\n // than left-aligning a lone glyph against the column edge. `cell-status`\n // trims the cell's vertical padding so the progress bar + label fit (and sit\n // near the top) even at the minimum row height.\n cellClass: 'cell-hcenter cell-status',\n headerClass: 'header-hcenter',\n })\n\n // (The per-row \"In Chat\" toggle column was removed \u2014 adding documents to the\n // current chat is now done via the bulk-actions toolbar after selecting rows.)\n\n // Document Name column\n // Unified Document column: entity badge (icon + title) opens the doc on click;\n // a hover \u22EF kebab carries the row actions (incl. Rename \u2014 no inline pencil).\n // Stays editable so the kebab's Rename can trigger the inline title editor;\n // edit is double-click / kebab only (not single-click, which opens).\n allColumns.push({\n colId: 'title',\n headerName: 'Title',\n // `initialFlex` (not `flex`): a stateful `flex` gets re-asserted every time\n // AG Grid re-processes the column definitions \u2014 which happens on many host\n // re-renders, including the one triggered by persisting the grid state\n // right after a manual resize \u2014 recomputing Title back to fill the leftover\n // space and snapping away the user's drag. With initialFlex the fill only\n // applies when the column is first created; after that a manual resize\n // sticks. The 160px floor lets the phone's Title + Status list fit 390px without sideways scroll.\n initialFlex: 3,\n minWidth: isPhoneViewport() ? 160 : 280,\n // Title is intentionally NOT pinned on mount: the default layout wants the\n // row-select checkbox as the first column with Title second. AG Grid always\n // renders pinned-left columns ahead of unpinned ones, so pinning Title (while\n // the selection column stays unpinned) pushed Title into the first slot,\n // ahead of the checkbox. Leaving it unpinned matches the built-in \"Default\n // view\" (which unpins everything) and lets the selection column sit first.\n // The \"Document view\" still pins the checkbox + Title left for users who want\n // the name frozen while scrolling the many data columns.\n sortable: true,\n filter: 'agTextColumnFilter',\n filterParams: FILTER_PARAMS,\n editable: isRowEditableByMember,\n cellEditor: 'agLargeTextCellEditor',\n cellEditorPopup: true,\n cellEditorParams: { rows: 5, cols: 50 },\n context: { icon: FileText },\n cellRenderer: DocumentNameCellRenderer,\n cellRendererParams: {\n canEdit: isRowEditableByMember,\n onOpenDocument: handleToggleDocumentViewer,\n onReviewDocument,\n onDocumentChat,\n onDownloadDocument,\n onMoveDocument,\n onDeleteDocument,\n } satisfies DocumentNameCellParams,\n valueGetter: (params) => getDocumentTitle(params.data, 'Untitled'),\n valueSetter: (params) => {\n if (!params.data) return false\n if (!params.data.doc_metadata) {\n params.data.doc_metadata = {}\n }\n params.data.doc_metadata.title = params.newValue\n return true\n },\n // No AG Grid tooltip here \u2014 the DocumentEntity pill renders its own\n // AI-summary hover card (same one the explorer tiles use). A\n // tooltipValueGetter would stack a second, plain title tooltip on top.\n })\n\n // Document Type column (AI-generated, user-editable dropdown)\n // Options from DOCUMENT_TYPE_OPTIONS constant (synced with backend DocTagLLM.DEFAULT_METADATA_TAGS)\n allColumns.push({\n colId: 'doc_nature',\n headerName: 'Type',\n width: 120,\n minWidth: 100,\n sortable: true,\n filter: 'agTextColumnFilter',\n editable: isRowEditableByMember,\n singleClickEdit: true,\n cellEditor: 'agSelectCellEditor',\n cellEditorParams: {\n values: [...DOCUMENT_TYPE_OPTIONS],\n },\n context: { icon: Sparkles },\n valueGetter: (params) => getDocumentNature(params.data) || '',\n valueSetter: (params) => {\n if (!params.data) return false\n if (!params.data.doc_metadata) {\n params.data.doc_metadata = {}\n }\n params.data.doc_metadata.doc_nature = params.newValue || null\n return true\n },\n // Show dropdown indicator for editable cells (no custom renderer for performance)\n cellClass: (params) =>\n isRowEditableByMember(params) ? 'cell-select-editable' : 'cell-editable-readonly relative',\n })\n\n // Document Author column (AI-generated, user-editable)\n allColumns.push({\n colId: 'doc_author',\n headerName: 'Author',\n width: 150,\n minWidth: 100,\n sortable: true,\n filter: 'agTextColumnFilter',\n filterParams: FILTER_PARAMS,\n editable: isRowEditableByMember,\n singleClickEdit: true,\n cellEditor: 'agLargeTextCellEditor',\n cellEditorPopup: true,\n cellEditorParams: { rows: 3, cols: 40 },\n context: { icon: User },\n valueGetter: (params) => getDocumentAuthor(params.data) || '',\n valueSetter: (params) => {\n if (!params.data) return false\n if (!params.data.doc_metadata) {\n params.data.doc_metadata = {}\n }\n params.data.doc_metadata.doc_author = params.newValue || null\n return true\n },\n tooltipValueGetter: (params) => getDocumentAuthor(params.data) || '',\n // cell-multiline: wrap long author lists to as many lines as the current\n // row height allows (same height-driven clamp as Abstract).\n cellClass: (params) =>\n isRowEditableByMember(params)\n ? 'cell-editable-member relative cell-multiline'\n : 'cell-editable-readonly relative cell-multiline',\n })\n\n // Document Abstract column (AI-generated, user-editable)\n allColumns.push({\n colId: 'doc_subject',\n headerName: 'Abstract',\n width: 300,\n minWidth: 200,\n sortable: true,\n filter: 'agTextColumnFilter',\n filterParams: FILTER_PARAMS,\n editable: isRowEditableByMember,\n singleClickEdit: true,\n cellEditor: 'agLargeTextCellEditor',\n cellEditorPopup: true,\n cellEditorParams: { rows: 8, cols: 60 },\n context: { icon: FileText },\n // Edit against the raw markdown (so the large-text editor shows the real\n // source), but display a flattened plain-text preview in the cell and\n // tooltip \u2014 our AI summaries often contain markdown bullets/emphasis, and\n // rendering raw markers (\"* \u2026\", \"**\u2026**\") in a dense cell reads as noise.\n // markdownToPlainText is a cheap string transform run only on visible cells.\n valueGetter: (params) => getDocumentSubject(params.data) || '',\n valueFormatter: (params) => markdownToPlainText(params.value),\n valueSetter: (params) => {\n if (!params.data) return false\n if (!params.data.doc_metadata) {\n params.data.doc_metadata = {}\n }\n params.data.doc_metadata.doc_subject = params.newValue || null\n return true\n },\n tooltipValueGetter: (params) => markdownToPlainText(getDocumentSubject(params.data)),\n cellClass: (params) =>\n isRowEditableByMember(params)\n ? 'cell-editable-member relative cell-multiline'\n : 'cell-editable-readonly relative cell-multiline',\n })\n\n // Date column\n // Optimized: Using AG Grid's built-in editing + CSS-only hover icons (no React cellRenderer)\n allColumns.push({\n colId: 'doc_date',\n headerName: 'Date',\n width: 160,\n minWidth: 150, // Wider to fit an optional time (emails carry date + time)\n sortable: true,\n editable: isRowEditableByMember,\n singleClickEdit: true,\n context: { icon: Calendar },\n valueGetter: (params) => getDocumentDate(params.data),\n valueSetter: (params) => {\n if (!params.data) return false\n if (!params.data.doc_metadata) {\n params.data.doc_metadata = {}\n }\n params.data.doc_metadata.doc_date = params.newValue\n return true\n },\n valueFormatter: (params) => {\n if (!params.value) return '\u2014'\n return formatDocDate(params.value)\n },\n // Ensure text doesn't overflow - use cellStyle for overflow handling\n cellStyle: {\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n },\n // Add Tailwind classes + CSS class for hover icon (pencil if editable, ban if not)\n cellClass: (params) =>\n isRowEditableByMember(params)\n ? 'cell-editable-member relative cell-tnum'\n : 'cell-editable-readonly relative cell-tnum',\n // Date filter with a start/end range (date-only comparison; ignores any time).\n filter: 'agDateColumnFilter',\n filterParams: {\n browserDatePicker: true,\n inRangeInclusive: true,\n // cellValue is the raw doc_date string (date or datetime); compare date-only.\n comparator: (filterDate: Date, cellValue: string | null) => {\n if (!cellValue) return -1\n const d = new Date(cellValue)\n if (Number.isNaN(d.getTime())) return -1\n const cellDay = new Date(d.getFullYear(), d.getMonth(), d.getDate())\n if (cellDay < filterDate) return -1\n if (cellDay > filterDate) return 1\n return 0\n },\n },\n // No valueParser - let user type freely without validation interrupting\n // Validation happens in onCellValueChanged after editing completes\n // No cellRenderer - AG Grid handles rendering natively with valueFormatter\n })\n\n // Pages column\n // Optimized: Using valueFormatter instead of React cellRenderer\n allColumns.push({\n colId: 'n_pages',\n headerName: 'Pages',\n field: 'n_pages',\n width: 100,\n sortable: true,\n cellDataType: 'number',\n cellClass: 'cell-numeric',\n context: { icon: Hash },\n valueGetter: (params) => {\n return params.data?.n_pages || null\n },\n valueFormatter: (params) => {\n if (params.value === null || params.value === undefined) return '\u2014'\n return params.value.toLocaleString()\n },\n // No cellRenderer - valueFormatter handles display formatting\n })\n\n // Upload date column - just the date, sortable by date\n // Optimized: Using valueFormatter instead of React cellRenderer\n allColumns.push({\n colId: 'created_at',\n headerName: 'Uploaded',\n field: 'created_at',\n width: 160,\n sortable: true,\n filter: 'agDateColumnFilter',\n filterParams: FILTER_PARAMS,\n context: { icon: Upload },\n valueFormatter: (params) => {\n if (!params.value) return '\u2014'\n return formatFriendlyDate(params.value)\n },\n // No cellRenderer - valueFormatter handles display formatting\n })\n\n // Last modified column\n // Optimized: Using valueFormatter instead of React cellRenderer\n allColumns.push({\n colId: 'updated_at',\n headerName: 'Last modified',\n field: 'updated_at',\n width: 160,\n sortable: true,\n // Visibility controlled by grid state, not column definition\n filter: 'agDateColumnFilter',\n filterParams: FILTER_PARAMS,\n context: { icon: Clock },\n valueFormatter: (params) => {\n if (!params.value) return '\u2014'\n return formatFriendlyDate(params.value)\n },\n // No cellRenderer - valueFormatter handles display formatting\n })\n\n // File name column\n // Optimized: Using native rendering - no cellRenderer needed for simple text\n allColumns.push({\n colId: 'file_name',\n headerName: 'File name',\n field: 'file_name',\n width: 200,\n sortable: true,\n // Visibility controlled by grid state, not column definition\n filter: 'agTextColumnFilter',\n filterParams: FILTER_PARAMS,\n context: { icon: File },\n // No cellRenderer - AG Grid handles text rendering natively\n })\n\n // Size column\n // Optimized: Using valueFormatter instead of React cellRenderer\n allColumns.push({\n colId: 'file_size',\n headerName: 'Size',\n field: 'file_size',\n width: 100,\n sortable: true,\n // Visibility controlled by grid state, not column definition\n cellDataType: 'number',\n cellClass: 'cell-numeric',\n context: { icon: HardDrive },\n valueFormatter: (params) => formatFileSize(params.value),\n // No cellRenderer - valueFormatter handles display formatting\n })\n\n // File type column.\n //\n // Backend stores ``file_type`` as either a bare extension (``\"pdf\"``)\n // or a full MIME type (``\"application/pdf\"``) depending on which\n // upload path produced the row, so we never render the raw value.\n // ``valueGetter`` runs once per row to produce the friendly label \u2014\n // sort and the text filter then operate on that label, which is what\n // the user actually sees.\n allColumns.push({\n colId: 'file_type',\n headerName: 'File type',\n field: 'file_type',\n width: 120,\n sortable: true,\n filter: 'agTextColumnFilter',\n filterParams: FILTER_PARAMS,\n context: { icon: FileType },\n valueGetter: (params) => friendlyFileType(params.data?.file_type, params.data?.file_name),\n })\n\n // Folder column\n allColumns.push({\n colId: 'folder',\n headerName: 'Folder',\n field: 'folder',\n width: 180,\n sortable: true,\n filter: 'agTextColumnFilter',\n filterParams: FILTER_PARAMS,\n context: { icon: FolderOpen },\n valueFormatter: (params) => formatFolderLabel(params.value),\n cellRenderer: FolderCellRenderer,\n cellRendererParams: { canEdit: isRowEditableByMember } satisfies FolderCellParams,\n cellClass: (params) =>\n isRowEditableByMember(params)\n ? 'cell-editable-member relative'\n : 'cell-editable-readonly relative',\n })\n\n // From column \u2014 email sender for connector-imported docs (e.g.\n // \"John Smith <john@x.com>\"). Plain text; null for non-email docs.\n allColumns.push({\n colId: 'sender',\n headerName: 'From',\n field: 'sender',\n width: 180,\n minWidth: 100,\n sortable: true,\n filter: 'agTextColumnFilter',\n filterParams: FILTER_PARAMS,\n context: { icon: Mail },\n valueGetter: (params) => params.data?.sender ?? '',\n tooltipValueGetter: (params) => params.data?.sender ?? '',\n // No cellRenderer - AG Grid handles text rendering natively\n })\n\n // Content Hash column\n allColumns.push({\n colId: 'content_hash',\n headerName: 'Fingerprint',\n field: 'content_hash',\n width: 200,\n sortable: true,\n filter: 'agTextColumnFilter',\n filterParams: FILTER_PARAMS,\n context: { icon: Fingerprint },\n cellStyle: { fontFamily: 'monospace' },\n valueFormatter: (params) => params.value || '',\n })\n\n // Related documents column \u2014 shows parent, children, duplicates, similar docs.\n //\n // Tiered sort key (string-compared by AG Grid; defaults to descending so\n // the first header click pulls the most-relevant tier to the top):\n //\n // {tier}|{paddedCount}|{groupId}|{rank}|{external_id}\n //\n // - tier (single digit, higher = top under desc):\n // 3 family has any duplicates\n // 2 family has any similar (no dupes)\n // 1 pure parent/child family (no dupes, no similar)\n // 0 no relationships at all\n // Family tier is computed once per groupId in Documents.tsx so every\n // member of a family shares the same prefix and stays clustered.\n // - paddedCount: aggregate count for the tier, zero-padded to 6 chars\n // so a lexical compare matches a numeric one.\n // - groupId: parent_ext_id for children, own external_id for parents\n // and lone docs, content_hash for standalone duplicate clusters.\n // - rank: 0 for parent/standalone, 1 for children \u2192 parent sorts above\n // its children within a family.\n // - external_id: stable tiebreaker.\n allColumns.push({\n colId: 'related',\n headerName: 'Related',\n width: 200,\n minWidth: 120,\n sortable: true,\n sortingOrder: ['desc', 'asc', null],\n context: { icon: Link2 },\n valueGetter: (params) => {\n if (!params.data) return ''\n const doc = params.data\n const context = params.context as DocumentGridContext | undefined\n const map = context?.getRelatedDocs?.()\n const familyMeta = context?.getFamilyMeta?.()\n const related = map?.get(doc.external_id)\n const isChild = !!doc.parent_ext_id\n const isParent = related?.children && related.children.length > 0\n\n let groupId: string\n let rank: number\n\n if (isChild) {\n // Child: group under parent, rank 1 so parent sorts first\n groupId = doc.parent_ext_id!\n rank = 1\n } else if (isParent) {\n // Parent: group under self, rank 0\n groupId = doc.external_id\n rank = 0\n } else if (doc.content_hash && related && related.duplicates.length > 0) {\n // Standalone duplicate: group by fingerprint\n groupId = doc.content_hash\n rank = 0\n } else {\n // No relationships: sort by own ID\n groupId = doc.external_id\n rank = 0\n }\n\n const meta = familyMeta?.get(groupId) ?? { tier: 0, count: 0 }\n const paddedCount = String(meta.count).padStart(6, '0')\n\n return `${meta.tier}|${paddedCount}|${groupId}|${rank}|${doc.external_id}`\n },\n cellRenderer: RelatedDocsCellRenderer,\n cellRendererParams: {\n onOpenDocument: handleToggleDocumentViewer,\n onDeleteDocument,\n } satisfies RelatedDocsCellParams,\n })\n\n // Shared column\n // Note: Keeping SharedCell for interactive toggle (complex UI)\n allColumns.push({\n colId: 'shared',\n headerName: 'Sharing',\n field: 'shared',\n width: 124,\n minWidth: 124,\n sortable: true,\n cellDataType: 'boolean',\n context: { icon: Share2 },\n valueGetter: (params) => {\n return params.data?.shared ?? false\n },\n cellRenderer: SharedCellRenderer,\n cellRendererParams: {\n canToggle: isRowEditableByOwner,\n onToggleShared,\n } satisfies SharedCellParams,\n })\n\n // Created by column - shows avatar icon (the members come through the grid's context)\n allColumns.push({\n colId: 'created_by',\n headerName: 'By',\n width: 60,\n sortable: true,\n context: { icon: User },\n valueGetter: (params) => {\n if (!params.data) return ''\n const users = (params.context as DocumentGridContext | undefined)?.getWorkspaceUsers?.()\n const creator = users?.find((u) => u.external_id === params.data!.created_by_ext_id)\n if (!creator) return ''\n const fullName = [creator.given_name, creator.family_name].filter(Boolean).join(' ')\n return fullName || creator.email || ''\n },\n cellRenderer: CreatedByCellRenderer,\n })\n\n // Labels column \u2014 shows applied checkbox/label tags as filled checkmarks\n allColumns.push({\n colId: 'all_tags',\n headerName: 'Labels',\n width: 300,\n // Never narrower than its own header (\"Labels\" + filter/menu icons) \u2014 the\n // title must not ellipsize, same floor as the Facts table's Labels column.\n minWidth: 132,\n sortable: false,\n filter: 'agTextColumnFilter',\n filterParams: FILTER_PARAMS,\n context: { icon: Tags },\n valueGetter: (params) => {\n if (!params.data || !params.data.doctags) return ''\n // Only index label (and issue) names for filtering\n const tagNames = params.data.doctags\n .map((dt) => {\n const tag = availableTags.find((t) => t.external_id === dt.tag_ext_id)\n if (!tag || !isLabelTag(tag)) return ''\n return tag.name\n })\n .filter(Boolean)\n return tagNames.join(' ')\n },\n cellRenderer: LabelsCellRenderer,\n cellRendererParams: {\n labels: checkboxTags,\n createDocTag,\n deleteDocTag,\n createTag,\n isMember,\n } satisfies LabelsCellParams,\n // Add CSS class for hover icon (matches title field)\n cellClass: isMember ? 'cell-editable-member relative !pr-0' : 'cell-editable-readonly relative',\n })\n\n // DocID column\n // Optimized: Using valueFormatter for monospace font styling\n // Note: AG Grid doesn't support className in valueFormatter, so we'll use cellStyle instead\n allColumns.push({\n colId: 'doc_id',\n headerName: 'DocID',\n field: 'external_id',\n width: 200,\n sortable: true,\n // Visibility controlled by grid state, not column definition\n filter: 'agTextColumnFilter',\n filterParams: FILTER_PARAMS,\n context: { icon: Hash },\n cellStyle: { fontFamily: 'monospace' },\n // No cellRenderer - native rendering with cellStyle for monospace font\n })\n\n // A column per annotation (labels and issues are in the Labels column)\n const tagColumns = availableTags.filter(isAnnotationTag).map((tag) =>\n createTagColumn({\n tag,\n isMember,\n notify,\n createDocTag,\n updateDocTag,\n deleteDocTag,\n })\n )\n\n // Return ALL columns - AG Grid state controls visibility and order\n return [...allColumns.map(withEmptyDash), ...tagColumns]\n}\n\n/** Title is the most important field, so it gets a wide floor everywhere but a phone. */\nfunction isPhoneViewport(): boolean {\n return (\n typeof window !== 'undefined' && window.matchMedia?.('(max-width: 480px)')?.matches === true\n )\n}\n\n/** Columns whose empty values read as a muted em dash (never a blank cell or an error-like word). */\nconst EMPTY_DASH_COLUMNS: ReadonlySet<string> = new Set([\n 'doc_nature',\n 'doc_author',\n 'doc_subject',\n 'doc_date',\n 'n_pages',\n 'created_at',\n 'updated_at',\n 'file_name',\n 'file_type',\n 'sender',\n])\n\n/** The AI abstract step writes this literal when it finds nothing; to a reader it looks like an error. */\nconst NO_VALUE = /^\\s*(not found|n\\/a)\\.?\\s*$/i\n\nfunction isEmptyCell(value: unknown): boolean {\n return value === null || value === undefined || value === '' || NO_VALUE.test(String(value))\n}\n\n/**\n * Show an em dash, in the muted text colour, for empty values of the plain data columns. Formatting\n * only: the underlying value (and what the cell editors start from) is untouched.\n */\nfunction withEmptyDash(col: ColDef<Doc>): ColDef<Doc> {\n if (!col.colId || !EMPTY_DASH_COLUMNS.has(col.colId)) return col\n const { valueFormatter, cellClass } = col\n return {\n ...col,\n valueFormatter: (params) => {\n if (col.colId !== 'n_pages' && isEmptyCell(params.value)) return '\u2014'\n return typeof valueFormatter === 'function'\n ? valueFormatter(params)\n : String(params.value ?? '')\n },\n cellClass: (params) => {\n const base = typeof cellClass === 'function' ? cellClass(params) : cellClass\n const classes = Array.isArray(base) ? base : base ? [base] : []\n return isEmptyCell(params.value) ? [...classes, 'cell-empty'] : classes\n },\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useWorkspaceUsers } from "@arbidocs/react";
|
|
4
|
+
import { formatRole } from "../../workspaces/workspace-roles.js";
|
|
5
|
+
const ROLE_ORDER = { owner: 0, collaborator: 1, guest: 2 };
|
|
6
|
+
function memberName(member) {
|
|
7
|
+
const { given_name, family_name, email } = member.user;
|
|
8
|
+
return [given_name, family_name].filter(Boolean).join(" ") || email;
|
|
9
|
+
}
|
|
10
|
+
function sortMembers(members) {
|
|
11
|
+
return [...members].sort(
|
|
12
|
+
(a, b) => (ROLE_ORDER[a.role] ?? 3) - (ROLE_ORDER[b.role] ?? 3) || memberName(a).localeCompare(memberName(b))
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
function WorkspaceMembersList({ workspaceId }) {
|
|
16
|
+
const { data, isLoading, isError } = useWorkspaceUsers(workspaceId);
|
|
17
|
+
if (isLoading) return /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Loading members\u2026" });
|
|
18
|
+
if (isError || !data) return /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Members unavailable" });
|
|
19
|
+
return /* @__PURE__ */ jsx("ul", { className: "max-h-60 space-y-1.5 overflow-y-auto", "data-testid": "sharing-members", children: sortMembers(data).map((m) => /* @__PURE__ */ jsxs("li", { className: "flex items-center justify-between gap-3 text-xs", children: [
|
|
20
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", title: m.user.email, children: memberName(m) }),
|
|
21
|
+
/* @__PURE__ */ jsx(
|
|
22
|
+
"span",
|
|
23
|
+
{
|
|
24
|
+
className: "shrink-0 rounded bg-muted px-1.5 py-0.5 text-muted-foreground",
|
|
25
|
+
"data-role": m.role,
|
|
26
|
+
children: formatRole(m.role)
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
] }, m.user.external_id)) });
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
WorkspaceMembersList
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=WorkspaceMembersList.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/connection/sharing/WorkspaceMembersList.tsx"],
|
|
4
|
+
"sourcesContent": ["import { useWorkspaceUsers, type WorkspaceMember } from '@arbidocs/react'\nimport { formatRole } from '../../workspaces/workspace-roles'\n\nconst ROLE_ORDER: Record<string, number> = { owner: 0, collaborator: 1, guest: 2 }\n\nfunction memberName(member: WorkspaceMember): string {\n const { given_name, family_name, email } = member.user\n return [given_name, family_name].filter(Boolean).join(' ') || email\n}\n\n/** Owners first, then collaborators, then read-only members; alphabetical within a role. */\nfunction sortMembers(members: readonly WorkspaceMember[]): WorkspaceMember[] {\n return [...members].sort(\n (a, b) =>\n (ROLE_ORDER[a.role] ?? 3) - (ROLE_ORDER[b.role] ?? 3) ||\n memberName(a).localeCompare(memberName(b))\n )\n}\n\n/** The workspace's members with their role; mounted only while a card is open, so read on first hover. */\nexport function WorkspaceMembersList({ workspaceId }: { workspaceId?: string | null }) {\n const { data, isLoading, isError } = useWorkspaceUsers(workspaceId)\n if (isLoading) return <p className=\"text-xs text-muted-foreground\">Loading members\u2026</p>\n if (isError || !data) return <p className=\"text-xs text-muted-foreground\">Members unavailable</p>\n return (\n <ul className=\"max-h-60 space-y-1.5 overflow-y-auto\" data-testid=\"sharing-members\">\n {sortMembers(data).map((m) => (\n <li key={m.user.external_id} className=\"flex items-center justify-between gap-3 text-xs\">\n <span className=\"truncate\" title={m.user.email}>\n {memberName(m)}\n </span>\n <span\n className=\"shrink-0 rounded bg-muted px-1.5 py-0.5 text-muted-foreground\"\n data-role={m.role}\n >\n {formatRole(m.role)}\n </span>\n </li>\n ))}\n </ul>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAsBwB,cAKhB,YALgB;AAtBxB,SAAS,yBAA+C;AACxD,SAAS,kBAAkB;AAE3B,MAAM,aAAqC,EAAE,OAAO,GAAG,cAAc,GAAG,OAAO,EAAE;AAEjF,SAAS,WAAW,QAAiC;AACnD,QAAM,EAAE,YAAY,aAAa,MAAM,IAAI,OAAO;AAClD,SAAO,CAAC,YAAY,WAAW,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG,KAAK;AAChE;AAGA,SAAS,YAAY,SAAwD;AAC3E,SAAO,CAAC,GAAG,OAAO,EAAE;AAAA,IAClB,CAAC,GAAG,OACD,WAAW,EAAE,IAAI,KAAK,MAAM,WAAW,EAAE,IAAI,KAAK,MACnD,WAAW,CAAC,EAAE,cAAc,WAAW,CAAC,CAAC;AAAA,EAC7C;AACF;AAGO,SAAS,qBAAqB,EAAE,YAAY,GAAoC;AACrF,QAAM,EAAE,MAAM,WAAW,QAAQ,IAAI,kBAAkB,WAAW;AAClE,MAAI,UAAW,QAAO,oBAAC,OAAE,WAAU,iCAAgC,mCAAgB;AACnF,MAAI,WAAW,CAAC,KAAM,QAAO,oBAAC,OAAE,WAAU,iCAAgC,iCAAmB;AAC7F,SACE,oBAAC,QAAG,WAAU,wCAAuC,eAAY,mBAC9D,sBAAY,IAAI,EAAE,IAAI,CAAC,MACtB,qBAAC,QAA4B,WAAU,mDACrC;AAAA,wBAAC,UAAK,WAAU,YAAW,OAAO,EAAE,KAAK,OACtC,qBAAW,CAAC,GACf;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,aAAW,EAAE;AAAA,QAEZ,qBAAW,EAAE,IAAI;AAAA;AAAA,IACpB;AAAA,OATO,EAAE,KAAK,WAUhB,CACD,GACH;AAEJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|