@unofficialbox/box-open-elements 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -73
- package/dist/components/collections/table.d.ts +113 -3
- package/dist/components/collections/table.js +540 -37
- package/dist/components/feedback/due-badge.d.ts +38 -0
- package/dist/components/feedback/due-badge.js +168 -0
- package/dist/components/feedback/due-types.d.ts +32 -0
- package/dist/components/feedback/due-types.js +91 -0
- package/dist/components/feedback/progress-steps.d.ts +51 -5
- package/dist/components/feedback/progress-steps.js +200 -22
- package/dist/components/feedback/stage-path.d.ts +44 -0
- package/dist/components/feedback/stage-path.js +235 -0
- package/dist/components/forms/select.d.ts +21 -0
- package/dist/components/forms/select.js +100 -3
- package/dist/components/forms/text-field.d.ts +21 -0
- package/dist/components/forms/text-field.js +100 -1
- package/dist/components/layout/nav-sidebar.d.ts +14 -0
- package/dist/components/layout/nav-sidebar.js +49 -0
- package/dist/components/layout/split-view.d.ts +14 -0
- package/dist/components/layout/split-view.js +80 -1
- package/dist/components/overlays/command-palette.d.ts +62 -0
- package/dist/components/overlays/command-palette.js +537 -0
- package/dist/components/overlays/command-types.d.ts +90 -0
- package/dist/components/overlays/command-types.js +259 -0
- package/dist/components/overlays/drawer.d.ts +18 -0
- package/dist/components/overlays/drawer.js +135 -9
- package/dist/components/overlays/shortcuts-overlay.d.ts +48 -0
- package/dist/components/overlays/shortcuts-overlay.js +385 -0
- package/dist/components/overlays/tooltip.js +1 -1
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.js +2 -0
- package/dist/core/offset-window.d.ts +70 -0
- package/dist/core/offset-window.js +134 -0
- package/dist/core/virtualize.d.ts +71 -0
- package/dist/core/virtualize.js +67 -0
- package/dist/element-maps.d.ts +292 -0
- package/dist/element-maps.js +1 -0
- package/dist/entries/activity-density.d.ts +1 -0
- package/dist/entries/activity-density.js +1 -0
- package/dist/entries/agent-chat.d.ts +1 -0
- package/dist/entries/agent-chat.js +1 -0
- package/dist/entries/audit-log.d.ts +1 -0
- package/dist/entries/audit-log.js +1 -0
- package/dist/entries/command-palette.d.ts +1 -0
- package/dist/entries/command-palette.js +1 -0
- package/dist/entries/compare-view.d.ts +1 -0
- package/dist/entries/compare-view.js +1 -0
- package/dist/entries/diff-viewer.d.ts +1 -0
- package/dist/entries/diff-viewer.js +1 -0
- package/dist/entries/due-badge.d.ts +1 -0
- package/dist/entries/due-badge.js +1 -0
- package/dist/entries/form-wizard.d.ts +1 -0
- package/dist/entries/form-wizard.js +1 -0
- package/dist/entries/lineage-graph.d.ts +1 -0
- package/dist/entries/lineage-graph.js +1 -0
- package/dist/entries/notification-bell.d.ts +1 -0
- package/dist/entries/notification-bell.js +1 -0
- package/dist/entries/notification-inbox.d.ts +1 -0
- package/dist/entries/notification-inbox.js +1 -0
- package/dist/entries/provenance-strip.d.ts +1 -0
- package/dist/entries/provenance-strip.js +1 -0
- package/dist/entries/run-trace.d.ts +1 -0
- package/dist/entries/run-trace.js +1 -0
- package/dist/entries/shortcuts-overlay.d.ts +1 -0
- package/dist/entries/shortcuts-overlay.js +1 -0
- package/dist/entries/signature-ceremony.d.ts +1 -0
- package/dist/entries/signature-ceremony.js +1 -0
- package/dist/entries/stage-path.d.ts +1 -0
- package/dist/entries/stage-path.js +1 -0
- package/dist/entries/timeline.d.ts +1 -0
- package/dist/entries/timeline.js +1 -0
- package/dist/entries/version-graph.d.ts +1 -0
- package/dist/entries/version-graph.js +1 -0
- package/dist/entries/version-list.d.ts +1 -0
- package/dist/entries/version-list.js +1 -0
- package/dist/entries/wizard-summary.d.ts +1 -0
- package/dist/entries/wizard-summary.js +1 -0
- package/dist/entries/work-queue.d.ts +1 -0
- package/dist/entries/work-queue.js +1 -0
- package/dist/entries/workload-board.d.ts +1 -0
- package/dist/entries/workload-board.js +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +21 -0
- package/dist/patterns/agent-chat/agent-chat.d.ts +75 -0
- package/dist/patterns/agent-chat/agent-chat.js +820 -0
- package/dist/patterns/agent-chat/controller.d.ts +27 -0
- package/dist/patterns/agent-chat/controller.js +182 -0
- package/dist/patterns/agent-chat/index.d.ts +3 -0
- package/dist/patterns/agent-chat/index.js +6 -0
- package/dist/patterns/agent-chat/types.d.ts +109 -0
- package/dist/patterns/agent-chat/types.js +1 -0
- package/dist/patterns/audit/activity-density.d.ts +68 -0
- package/dist/patterns/audit/activity-density.js +428 -0
- package/dist/patterns/audit/audit-log.d.ts +182 -0
- package/dist/patterns/audit/audit-log.js +1082 -0
- package/dist/patterns/audit/index.d.ts +3 -0
- package/dist/patterns/audit/index.js +7 -0
- package/dist/patterns/audit/shared.d.ts +10 -0
- package/dist/patterns/audit/shared.js +50 -0
- package/dist/patterns/audit/types.d.ts +143 -0
- package/dist/patterns/audit/types.js +327 -0
- package/dist/patterns/audit/window.d.ts +116 -0
- package/dist/patterns/audit/window.js +132 -0
- package/dist/patterns/content-explorer/actions/controller.js +12 -2
- package/dist/patterns/content-explorer/box-transport.js +117 -25
- package/dist/patterns/content-explorer/content-explorer.d.ts +13 -1
- package/dist/patterns/content-explorer/content-explorer.js +36 -3
- package/dist/patterns/content-explorer/contracts.d.ts +24 -1
- package/dist/patterns/content-explorer/contracts.js +68 -11
- package/dist/patterns/content-explorer/controller.d.ts +16 -1
- package/dist/patterns/content-explorer/controller.js +130 -4
- package/dist/patterns/content-explorer/navigation/controller.js +11 -9
- package/dist/patterns/content-explorer/types.d.ts +58 -1
- package/dist/patterns/content-picker/content-picker.d.ts +71 -0
- package/dist/patterns/content-picker/content-picker.js +747 -0
- package/dist/patterns/content-picker/controller.d.ts +38 -0
- package/dist/patterns/content-picker/controller.js +141 -0
- package/dist/patterns/content-picker/index.d.ts +3 -0
- package/dist/patterns/content-picker/index.js +6 -0
- package/dist/patterns/content-picker/types.d.ts +52 -0
- package/dist/patterns/content-picker/types.js +26 -0
- package/dist/patterns/content-sidebar/content-sidebar.d.ts +43 -0
- package/dist/patterns/content-sidebar/content-sidebar.js +293 -0
- package/dist/patterns/content-sidebar/index.d.ts +2 -0
- package/dist/patterns/content-sidebar/index.js +4 -0
- package/dist/patterns/content-sidebar/types.d.ts +18 -0
- package/dist/patterns/content-sidebar/types.js +20 -0
- package/dist/patterns/content-uploader/box-transport.d.ts +14 -0
- package/dist/patterns/content-uploader/box-transport.js +54 -0
- package/dist/patterns/content-uploader/content-uploader.d.ts +62 -0
- package/dist/patterns/content-uploader/content-uploader.js +559 -0
- package/dist/patterns/content-uploader/controller.d.ts +39 -0
- package/dist/patterns/content-uploader/controller.js +227 -0
- package/dist/patterns/content-uploader/index.d.ts +4 -0
- package/dist/patterns/content-uploader/index.js +8 -0
- package/dist/patterns/content-uploader/types.d.ts +120 -0
- package/dist/patterns/content-uploader/types.js +38 -0
- package/dist/patterns/diff/compare-view.d.ts +47 -0
- package/dist/patterns/diff/compare-view.js +276 -0
- package/dist/patterns/diff/diff-viewer.d.ts +42 -0
- package/dist/patterns/diff/diff-viewer.js +434 -0
- package/dist/patterns/diff/engine.d.ts +22 -0
- package/dist/patterns/diff/engine.js +276 -0
- package/dist/patterns/diff/index.d.ts +4 -0
- package/dist/patterns/diff/index.js +8 -0
- package/dist/patterns/diff/types.d.ts +69 -0
- package/dist/patterns/diff/types.js +24 -0
- package/dist/patterns/form-wizard/controller.d.ts +39 -0
- package/dist/patterns/form-wizard/controller.js +157 -0
- package/dist/patterns/form-wizard/form-wizard.d.ts +63 -0
- package/dist/patterns/form-wizard/form-wizard.js +468 -0
- package/dist/patterns/form-wizard/index.d.ts +4 -0
- package/dist/patterns/form-wizard/index.js +8 -0
- package/dist/patterns/form-wizard/types.d.ts +109 -0
- package/dist/patterns/form-wizard/types.js +83 -0
- package/dist/patterns/form-wizard/wizard-summary.d.ts +62 -0
- package/dist/patterns/form-wizard/wizard-summary.js +324 -0
- package/dist/patterns/internal/safe-href.d.ts +17 -0
- package/dist/patterns/internal/safe-href.js +17 -0
- package/dist/patterns/lineage/index.d.ts +3 -0
- package/dist/patterns/lineage/index.js +6 -0
- package/dist/patterns/lineage/lineage-graph.d.ts +30 -0
- package/dist/patterns/lineage/lineage-graph.js +434 -0
- package/dist/patterns/lineage/provenance-strip.d.ts +22 -0
- package/dist/patterns/lineage/provenance-strip.js +194 -0
- package/dist/patterns/lineage/types.d.ts +53 -0
- package/dist/patterns/lineage/types.js +49 -0
- package/dist/patterns/notifications/index.d.ts +3 -0
- package/dist/patterns/notifications/index.js +6 -0
- package/dist/patterns/notifications/notification-bell.d.ts +38 -0
- package/dist/patterns/notifications/notification-bell.js +198 -0
- package/dist/patterns/notifications/notification-inbox.d.ts +46 -0
- package/dist/patterns/notifications/notification-inbox.js +537 -0
- package/dist/patterns/notifications/types.d.ts +61 -0
- package/dist/patterns/notifications/types.js +108 -0
- package/dist/patterns/preview/annotation-inspector.d.ts +25 -6
- package/dist/patterns/preview/annotation-inspector.js +239 -94
- package/dist/patterns/preview/annotation-thread.d.ts +25 -5
- package/dist/patterns/preview/annotation-thread.js +233 -82
- package/dist/patterns/preview/annotation-toolbar.d.ts +20 -6
- package/dist/patterns/preview/annotation-toolbar.js +162 -96
- package/dist/patterns/preview/content-preview-adapter.d.ts +10 -1
- package/dist/patterns/preview/content-preview-adapter.js +26 -1
- package/dist/patterns/preview/preview-element.d.ts +15 -2
- package/dist/patterns/preview/preview-element.js +223 -52
- package/dist/patterns/preview/provider-adapter.d.ts +47 -0
- package/dist/patterns/preview/provider-adapter.js +17 -0
- package/dist/patterns/run/index.d.ts +2 -0
- package/dist/patterns/run/index.js +4 -0
- package/dist/patterns/run/run-trace.d.ts +40 -0
- package/dist/patterns/run/run-trace.js +441 -0
- package/dist/patterns/run/types.d.ts +66 -0
- package/dist/patterns/run/types.js +152 -0
- package/dist/patterns/signature/index.d.ts +2 -0
- package/dist/patterns/signature/index.js +4 -0
- package/dist/patterns/signature/signature-ceremony.d.ts +33 -0
- package/dist/patterns/signature/signature-ceremony.js +317 -0
- package/dist/patterns/signature/types.d.ts +68 -0
- package/dist/patterns/signature/types.js +89 -0
- package/dist/patterns/timeline/index.d.ts +2 -0
- package/dist/patterns/timeline/index.js +4 -0
- package/dist/patterns/timeline/timeline.d.ts +27 -0
- package/dist/patterns/timeline/timeline.js +430 -0
- package/dist/patterns/timeline/types.d.ts +36 -0
- package/dist/patterns/timeline/types.js +27 -0
- package/dist/patterns/versions/graph-layout.d.ts +43 -0
- package/dist/patterns/versions/graph-layout.js +159 -0
- package/dist/patterns/versions/index.d.ts +4 -0
- package/dist/patterns/versions/index.js +8 -0
- package/dist/patterns/versions/shared.d.ts +4 -0
- package/dist/patterns/versions/shared.js +13 -0
- package/dist/patterns/versions/types.d.ts +37 -0
- package/dist/patterns/versions/types.js +39 -0
- package/dist/patterns/versions/version-graph.d.ts +36 -0
- package/dist/patterns/versions/version-graph.js +438 -0
- package/dist/patterns/versions/version-list.d.ts +39 -0
- package/dist/patterns/versions/version-list.js +407 -0
- package/dist/patterns/work-queue/controller.d.ts +26 -0
- package/dist/patterns/work-queue/controller.js +150 -0
- package/dist/patterns/work-queue/index.d.ts +4 -0
- package/dist/patterns/work-queue/index.js +7 -0
- package/dist/patterns/work-queue/types.d.ts +128 -0
- package/dist/patterns/work-queue/types.js +45 -0
- package/dist/patterns/work-queue/work-queue.d.ts +48 -0
- package/dist/patterns/work-queue/work-queue.js +534 -0
- package/dist/patterns/work-queue/workload-board.d.ts +53 -0
- package/dist/patterns/work-queue/workload-board.js +608 -0
- package/package.json +89 -5
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windowing plan for the grouped audit log.
|
|
3
|
+
*
|
|
4
|
+
* `box-table` can refuse to window expandable rows because a flat table has an
|
|
5
|
+
* alternative: render everything. A ten-thousand-event audit log does not —
|
|
6
|
+
* grouping is the whole point of the surface, and grouped rows are exactly what
|
|
7
|
+
* fixed-height windowing cannot describe. So this is the case that needs the
|
|
8
|
+
* cumulative-offset engine (`src/core/offset-window.ts`) rather than an excuse.
|
|
9
|
+
*
|
|
10
|
+
* The log is windowed as one flat sequence of rows — each group contributes a
|
|
11
|
+
* heading row, then one row per event while it is expanded — because a single
|
|
12
|
+
* day's section can hold thousands of events on its own. Windowing whole groups
|
|
13
|
+
* would leave that case exactly as slow as it is now.
|
|
14
|
+
*
|
|
15
|
+
* The rendered DOM is still nested, so the flat window has to be turned back
|
|
16
|
+
* into sections. That is what `planAuditWindow` does, and the reason it exists
|
|
17
|
+
* as a pure function is the usual one: jsdom has no layout, so a DOM-level test
|
|
18
|
+
* of a windowed surface can only ever observe zeroes.
|
|
19
|
+
*
|
|
20
|
+
* ## The straddling group
|
|
21
|
+
*
|
|
22
|
+
* A group whose heading has scrolled above the viewport still needs its heading
|
|
23
|
+
* rendered: `[part="group-body"]` is `aria-labelledby` its toggle, and a
|
|
24
|
+
* section that arrives without its own heading is both unlabelled and
|
|
25
|
+
* impossible to collapse. So an intersecting group always renders its heading —
|
|
26
|
+
* which means rendering one heading the offset index has already accounted for
|
|
27
|
+
* in `paddingTop`.
|
|
28
|
+
*
|
|
29
|
+
* At most one group can straddle the top edge, so that is a single correction:
|
|
30
|
+
* take the heading's height back out of the top spacer. The events below then
|
|
31
|
+
* land where the scrollbar says they should, and the reclaimed heading sits
|
|
32
|
+
* directly above them.
|
|
33
|
+
*/
|
|
34
|
+
import { buildOffsetIndex, resolveOffsetWindow } from "../../core/offset-window.js";
|
|
35
|
+
/**
|
|
36
|
+
* The log as a flat row sequence. A collapsed group contributes its heading
|
|
37
|
+
* only — which is exactly why the heights vary and the fixed-height engine
|
|
38
|
+
* cannot describe this surface.
|
|
39
|
+
*/
|
|
40
|
+
export const flattenAuditRows = (groups, collapsedKeys) => {
|
|
41
|
+
const rows = [];
|
|
42
|
+
for (let groupIndex = 0; groupIndex < groups.length; groupIndex++) {
|
|
43
|
+
const group = groups[groupIndex];
|
|
44
|
+
rows.push({ kind: "heading", groupIndex });
|
|
45
|
+
if (collapsedKeys.has(group.key))
|
|
46
|
+
continue;
|
|
47
|
+
for (let eventIndex = 0; eventIndex < group.events.length; eventIndex++) {
|
|
48
|
+
rows.push({ kind: "event", groupIndex, eventIndex });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return rows;
|
|
52
|
+
};
|
|
53
|
+
export const auditRowHeights = (rows, heights) => rows.map(row => (row.kind === "heading" ? heights.heading : heights.event));
|
|
54
|
+
/**
|
|
55
|
+
* The scroll offset to plan at, given where the scroller actually is.
|
|
56
|
+
*
|
|
57
|
+
* Without a measured `contentHeight` this is the raw offset. With one, it is
|
|
58
|
+
* the same *fraction* of the estimated range as the reader is through the real
|
|
59
|
+
* one — which is what makes the two ends agree.
|
|
60
|
+
*/
|
|
61
|
+
const mapScrollToEstimate = (viewport, totalHeight) => {
|
|
62
|
+
const { contentHeight, scrollTop, viewportHeight } = viewport;
|
|
63
|
+
if (contentHeight === undefined || !Number.isFinite(contentHeight))
|
|
64
|
+
return scrollTop;
|
|
65
|
+
const realRange = contentHeight - viewportHeight;
|
|
66
|
+
const estimatedRange = totalHeight - viewportHeight;
|
|
67
|
+
// Nothing scrolls, or the estimate already agrees closely enough that the
|
|
68
|
+
// mapping would only add rounding noise.
|
|
69
|
+
if (realRange <= 0 || estimatedRange <= 0)
|
|
70
|
+
return scrollTop;
|
|
71
|
+
const fraction = Math.min(Math.max(0, scrollTop / realRange), 1);
|
|
72
|
+
return fraction * estimatedRange;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* The offset index for a row set, so a caller can build it once and reuse it
|
|
76
|
+
* across scroll frames. The index only changes when the rows or the heights do.
|
|
77
|
+
*/
|
|
78
|
+
export const buildAuditIndex = (rows, heights) => buildOffsetIndex(auditRowHeights(rows, heights));
|
|
79
|
+
/**
|
|
80
|
+
* Which sections to render, and the spacer heights around them.
|
|
81
|
+
*
|
|
82
|
+
* Returns an empty plan for an empty window rather than a plan with no groups
|
|
83
|
+
* and stale padding, so a caller can treat "nothing to render" uniformly.
|
|
84
|
+
*/
|
|
85
|
+
export const planAuditWindow = (rows, heights, viewport,
|
|
86
|
+
/**
|
|
87
|
+
* A prebuilt index for these rows and heights, from `buildAuditIndex`.
|
|
88
|
+
*
|
|
89
|
+
* Optional purely for callers that plan once. The scroll path should pass one:
|
|
90
|
+
* building the index is O(n), and rebuilding it per frame would put the O(n)
|
|
91
|
+
* back that the binary search exists to remove.
|
|
92
|
+
*/
|
|
93
|
+
prebuilt) => {
|
|
94
|
+
const index = prebuilt ?? buildAuditIndex(rows, heights);
|
|
95
|
+
const window = resolveOffsetWindow(index, {
|
|
96
|
+
...viewport,
|
|
97
|
+
scrollTop: mapScrollToEstimate(viewport, index.totalHeight),
|
|
98
|
+
});
|
|
99
|
+
const plan = {
|
|
100
|
+
groups: [],
|
|
101
|
+
paddingTop: window.paddingTop,
|
|
102
|
+
paddingBottom: window.paddingBottom,
|
|
103
|
+
totalHeight: window.totalHeight,
|
|
104
|
+
window,
|
|
105
|
+
};
|
|
106
|
+
if (window.endIndex <= window.startIndex)
|
|
107
|
+
return plan;
|
|
108
|
+
let current = null;
|
|
109
|
+
for (let rowIndex = window.startIndex; rowIndex < window.endIndex; rowIndex++) {
|
|
110
|
+
const row = rows[rowIndex];
|
|
111
|
+
if (!current || current.groupIndex !== row.groupIndex) {
|
|
112
|
+
current = {
|
|
113
|
+
groupIndex: row.groupIndex,
|
|
114
|
+
eventStart: row.kind === "event" ? row.eventIndex : 0,
|
|
115
|
+
eventEnd: row.kind === "event" ? row.eventIndex : 0,
|
|
116
|
+
headingInWindow: row.kind === "heading",
|
|
117
|
+
};
|
|
118
|
+
plan.groups.push(current);
|
|
119
|
+
}
|
|
120
|
+
if (row.kind === "event") {
|
|
121
|
+
current.eventEnd = row.eventIndex + 1;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// The straddling group: its heading is above the window but will be rendered,
|
|
125
|
+
// so give its height back from the top spacer. Only the first planned group
|
|
126
|
+
// can be in this position — every later one starts at its own heading.
|
|
127
|
+
const first = plan.groups[0];
|
|
128
|
+
if (first && !first.headingInWindow) {
|
|
129
|
+
plan.paddingTop = Math.max(0, plan.paddingTop - heights.heading);
|
|
130
|
+
}
|
|
131
|
+
return plan;
|
|
132
|
+
};
|
|
@@ -25,12 +25,22 @@ export class ExplorerActionsController extends Controller {
|
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
27
|
const action = this.getItemActions(itemId).find(entry => entry.id === actionId);
|
|
28
|
-
if (!action) {
|
|
28
|
+
if (!action || action.disabled) {
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
31
|
this.emit("itemActionInvoked", { action, item });
|
|
32
32
|
}
|
|
33
33
|
resolveActionsForItem(item) {
|
|
34
|
-
return this.itemActions
|
|
34
|
+
return this.itemActions
|
|
35
|
+
.filter(action => (action.itemTypes ? action.itemTypes.includes(item.type) : true))
|
|
36
|
+
.map(action => {
|
|
37
|
+
if (!action.requiresPermission) {
|
|
38
|
+
return action;
|
|
39
|
+
}
|
|
40
|
+
// Only an explicit `false` denies: items without permission data keep
|
|
41
|
+
// the action enabled rather than guessing.
|
|
42
|
+
const allowed = item.permissions?.[action.requiresPermission] !== false;
|
|
43
|
+
return allowed ? action : { ...action, disabled: true };
|
|
44
|
+
});
|
|
35
45
|
}
|
|
36
46
|
}
|
|
@@ -123,29 +123,48 @@ const resolveFetch = (providedFetch) => {
|
|
|
123
123
|
}
|
|
124
124
|
return globalThis.fetch.bind(globalThis);
|
|
125
125
|
};
|
|
126
|
-
|
|
126
|
+
/** Box folder-items sort param values match our vocabulary directly. */
|
|
127
|
+
const applySortParams = (url, sortBy, sortDirection) => {
|
|
128
|
+
if (sortBy) {
|
|
129
|
+
url.searchParams.set("sort", sortBy);
|
|
130
|
+
url.searchParams.set("direction", sortDirection ?? "ASC");
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
const createFolderItemsUrl = (apiBaseUrl, folderId, fields, request) => {
|
|
127
134
|
const url = new URL(`${apiBaseUrl.replace(/\/$/, "")}/folders/${folderId}/items`);
|
|
128
135
|
url.searchParams.set("fields", fields.join(","));
|
|
129
|
-
if (typeof limit === "number") {
|
|
130
|
-
url.searchParams.set("limit", String(limit));
|
|
136
|
+
if (typeof request.limit === "number") {
|
|
137
|
+
url.searchParams.set("limit", String(request.limit));
|
|
131
138
|
}
|
|
132
|
-
if (typeof offset === "number") {
|
|
133
|
-
url.searchParams.set("offset", String(offset));
|
|
139
|
+
if (typeof request.offset === "number") {
|
|
140
|
+
url.searchParams.set("offset", String(request.offset));
|
|
134
141
|
}
|
|
142
|
+
applySortParams(url, request.sortBy, request.sortDirection);
|
|
143
|
+
return url.toString();
|
|
144
|
+
};
|
|
145
|
+
const createFolderInfoUrl = (apiBaseUrl, folderId) => {
|
|
146
|
+
const url = new URL(`${apiBaseUrl.replace(/\/$/, "")}/folders/${folderId}`);
|
|
147
|
+
url.searchParams.set("fields", "id,name,path_collection");
|
|
135
148
|
return url.toString();
|
|
136
149
|
};
|
|
137
|
-
const createSearchUrl = (apiBaseUrl,
|
|
150
|
+
const createSearchUrl = (apiBaseUrl, fields, request) => {
|
|
138
151
|
const url = new URL(`${apiBaseUrl.replace(/\/$/, "")}/search`);
|
|
139
|
-
url.searchParams.set("query", query);
|
|
152
|
+
url.searchParams.set("query", request.query);
|
|
140
153
|
url.searchParams.set("fields", fields.join(","));
|
|
141
|
-
if (ancestorFolderId) {
|
|
142
|
-
url.searchParams.set("ancestor_folder_ids", ancestorFolderId);
|
|
154
|
+
if (request.ancestorFolderId) {
|
|
155
|
+
url.searchParams.set("ancestor_folder_ids", request.ancestorFolderId);
|
|
143
156
|
}
|
|
144
|
-
if (typeof limit === "number") {
|
|
145
|
-
url.searchParams.set("limit", String(limit));
|
|
157
|
+
if (typeof request.limit === "number") {
|
|
158
|
+
url.searchParams.set("limit", String(request.limit));
|
|
146
159
|
}
|
|
147
|
-
if (typeof offset === "number") {
|
|
148
|
-
url.searchParams.set("offset", String(offset));
|
|
160
|
+
if (typeof request.offset === "number") {
|
|
161
|
+
url.searchParams.set("offset", String(request.offset));
|
|
162
|
+
}
|
|
163
|
+
// Box search only sorts by modified date (or relevance, its default); name
|
|
164
|
+
// and size sorts stay client concerns there, so only "date" maps through.
|
|
165
|
+
if (request.sortBy === "date") {
|
|
166
|
+
url.searchParams.set("sort", "modified_at");
|
|
167
|
+
url.searchParams.set("direction", (request.sortDirection ?? "DESC") === "ASC" ? "ASC" : "DESC");
|
|
149
168
|
}
|
|
150
169
|
return url.toString();
|
|
151
170
|
};
|
|
@@ -178,13 +197,14 @@ export const createBoxExplorerTransport = (options = {}) => {
|
|
|
178
197
|
const apiBaseUrl = options.apiBaseUrl ?? DEFAULT_API_BASE_URL;
|
|
179
198
|
const fields = options.fields ?? DEFAULT_FIELDS;
|
|
180
199
|
const fetchImpl = resolveFetch(options.fetch);
|
|
200
|
+
const requestHeaders = (request) => ({
|
|
201
|
+
Authorization: `Bearer ${request.token}`,
|
|
202
|
+
"Accept-Language": request.language ?? "en-US",
|
|
203
|
+
});
|
|
181
204
|
return {
|
|
182
205
|
async loadFolderItems(request) {
|
|
183
|
-
const response = await fetchImpl(createFolderItemsUrl(apiBaseUrl, request.folderId, fields, request
|
|
184
|
-
headers:
|
|
185
|
-
Authorization: `Bearer ${request.token}`,
|
|
186
|
-
"Accept-Language": request.language ?? "en-US",
|
|
187
|
-
},
|
|
206
|
+
const response = await fetchImpl(createFolderItemsUrl(apiBaseUrl, request.folderId, fields, request), {
|
|
207
|
+
headers: requestHeaders(request),
|
|
188
208
|
method: "GET",
|
|
189
209
|
signal: request.signal,
|
|
190
210
|
});
|
|
@@ -194,9 +214,34 @@ export const createBoxExplorerTransport = (options = {}) => {
|
|
|
194
214
|
const payload = (await response.json());
|
|
195
215
|
const rawEntries = payload.entries ?? [];
|
|
196
216
|
const { entries } = normalizeItems(rawEntries);
|
|
197
|
-
|
|
217
|
+
// The real `GET /folders/:id/items` payload carries no folder metadata
|
|
218
|
+
// (no id/name/path_collection), which would degrade the folder header
|
|
219
|
+
// and breadcrumbs to raw ids. When the first page lacks it, fetch
|
|
220
|
+
// `GET /folders/:id` for the metadata — best-effort: a failed info
|
|
221
|
+
// request keeps the listing usable rather than failing it.
|
|
222
|
+
let folderSource = payload;
|
|
223
|
+
const missingMetadata = payload.name === undefined || payload.id === undefined || payload.path_collection === undefined;
|
|
224
|
+
if (!request.offset && missingMetadata) {
|
|
225
|
+
const info = await Promise.resolve()
|
|
226
|
+
.then(() => fetchImpl(createFolderInfoUrl(apiBaseUrl, request.folderId), {
|
|
227
|
+
headers: requestHeaders(request),
|
|
228
|
+
method: "GET",
|
|
229
|
+
signal: request.signal,
|
|
230
|
+
}))
|
|
231
|
+
.then(infoResponse => infoResponse?.ok ? infoResponse.json() : null)
|
|
232
|
+
.catch(() => null);
|
|
233
|
+
if (info) {
|
|
234
|
+
folderSource = {
|
|
235
|
+
...payload,
|
|
236
|
+
id: payload.id ?? info.id,
|
|
237
|
+
name: payload.name ?? info.name,
|
|
238
|
+
path_collection: payload.path_collection ?? info.path_collection,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const folder = normalizeFolder(folderSource, request.folderId);
|
|
198
243
|
return {
|
|
199
|
-
breadcrumbs: normalizeBreadcrumbs(
|
|
244
|
+
breadcrumbs: normalizeBreadcrumbs(folderSource, folder),
|
|
200
245
|
folder,
|
|
201
246
|
folderId: request.folderId,
|
|
202
247
|
items: entries,
|
|
@@ -204,11 +249,8 @@ export const createBoxExplorerTransport = (options = {}) => {
|
|
|
204
249
|
};
|
|
205
250
|
},
|
|
206
251
|
async searchItems(request) {
|
|
207
|
-
const response = await fetchImpl(createSearchUrl(apiBaseUrl,
|
|
208
|
-
headers:
|
|
209
|
-
Authorization: `Bearer ${request.token}`,
|
|
210
|
-
"Accept-Language": request.language ?? "en-US",
|
|
211
|
-
},
|
|
252
|
+
const response = await fetchImpl(createSearchUrl(apiBaseUrl, fields, request), {
|
|
253
|
+
headers: requestHeaders(request),
|
|
212
254
|
method: "GET",
|
|
213
255
|
signal: request.signal,
|
|
214
256
|
});
|
|
@@ -225,5 +267,55 @@ export const createBoxExplorerTransport = (options = {}) => {
|
|
|
225
267
|
pagination: toPagination(payload, rawEntries.length, request),
|
|
226
268
|
};
|
|
227
269
|
},
|
|
270
|
+
async createFolder(request) {
|
|
271
|
+
const response = await fetchImpl(`${apiBaseUrl.replace(/\/$/, "")}/folders`, {
|
|
272
|
+
headers: { ...requestHeaders(request), "Content-Type": "application/json" },
|
|
273
|
+
method: "POST",
|
|
274
|
+
body: JSON.stringify({ name: request.name, parent: { id: request.parentFolderId } }),
|
|
275
|
+
signal: request.signal,
|
|
276
|
+
});
|
|
277
|
+
if (!response.ok) {
|
|
278
|
+
throw new Error(await getErrorMessage(response));
|
|
279
|
+
}
|
|
280
|
+
const payload = (await response.json());
|
|
281
|
+
const item = normalizeBoxExplorerItem(payload);
|
|
282
|
+
if (!item) {
|
|
283
|
+
throw new Error("Box API returned an unsupported item for the created folder");
|
|
284
|
+
}
|
|
285
|
+
return item;
|
|
286
|
+
},
|
|
287
|
+
async renameItem(request) {
|
|
288
|
+
const segment = request.itemType === "folder" ? "folders" : request.itemType === "web_link" ? "web_links" : "files";
|
|
289
|
+
const response = await fetchImpl(`${apiBaseUrl.replace(/\/$/, "")}/${segment}/${request.itemId}`, {
|
|
290
|
+
headers: { ...requestHeaders(request), "Content-Type": "application/json" },
|
|
291
|
+
method: "PUT",
|
|
292
|
+
body: JSON.stringify({ name: request.name }),
|
|
293
|
+
signal: request.signal,
|
|
294
|
+
});
|
|
295
|
+
if (!response.ok) {
|
|
296
|
+
throw new Error(await getErrorMessage(response));
|
|
297
|
+
}
|
|
298
|
+
const payload = (await response.json());
|
|
299
|
+
const item = normalizeBoxExplorerItem(payload);
|
|
300
|
+
if (!item) {
|
|
301
|
+
throw new Error("Box API returned an unsupported item for the rename");
|
|
302
|
+
}
|
|
303
|
+
return item;
|
|
304
|
+
},
|
|
305
|
+
async deleteItem(request) {
|
|
306
|
+
const segment = request.itemType === "folder" ? "folders" : request.itemType === "web_link" ? "web_links" : "files";
|
|
307
|
+
const url = new URL(`${apiBaseUrl.replace(/\/$/, "")}/${segment}/${request.itemId}`);
|
|
308
|
+
if (request.itemType === "folder") {
|
|
309
|
+
url.searchParams.set("recursive", "true");
|
|
310
|
+
}
|
|
311
|
+
const response = await fetchImpl(url.toString(), {
|
|
312
|
+
headers: requestHeaders(request),
|
|
313
|
+
method: "DELETE",
|
|
314
|
+
signal: request.signal,
|
|
315
|
+
});
|
|
316
|
+
if (!response.ok && response.status !== 204) {
|
|
317
|
+
throw new Error(await getErrorMessage(response));
|
|
318
|
+
}
|
|
319
|
+
},
|
|
228
320
|
};
|
|
229
321
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ContentExplorerController } from "./controller.js";
|
|
2
|
+
import { type ExplorerItemAction, type ExplorerItem, type ExplorerItemGesture, type ExplorerSelectionMode, type ExplorerSortState, type ExplorerState, type ExplorerTransport } from "./types.js";
|
|
2
3
|
import { BaseElement } from "../../core/index.js";
|
|
3
4
|
export interface ContentExplorerTemplateContext {
|
|
4
5
|
element: ContentExplorer;
|
|
@@ -73,6 +74,16 @@ export declare class ContentExplorer extends BaseElement {
|
|
|
73
74
|
toggleSelection(itemId: string): void;
|
|
74
75
|
clearSelection(): void;
|
|
75
76
|
invokeItemAction(itemId: string, actionId: string): void;
|
|
77
|
+
setSort(sort: ExplorerSortState | null): Promise<void>;
|
|
78
|
+
createFolder(name: string): Promise<ExplorerItem | null>;
|
|
79
|
+
renameItem(itemId: string, name: string): Promise<ExplorerItem | null>;
|
|
80
|
+
deleteItem(itemId: string): Promise<boolean>;
|
|
81
|
+
/**
|
|
82
|
+
* The live session controller, for pairing the shell with the standalone
|
|
83
|
+
* adapter elements (`box-explorer-toolbar`, `box-explorer-breadcrumbs`, …)
|
|
84
|
+
* or host-chrome bindings. Null until connected.
|
|
85
|
+
*/
|
|
86
|
+
get explorerController(): ContentExplorerController | null;
|
|
76
87
|
private updateStringAttribute;
|
|
77
88
|
private scheduleStart;
|
|
78
89
|
private startController;
|
|
@@ -82,4 +93,5 @@ export declare class ContentExplorer extends BaseElement {
|
|
|
82
93
|
private getFocusableItemIds;
|
|
83
94
|
protected renderTemplate(): void;
|
|
84
95
|
protected update(): void;
|
|
96
|
+
private isFocusInsideHost;
|
|
85
97
|
}
|
|
@@ -386,6 +386,26 @@ export class ContentExplorer extends BaseElement {
|
|
|
386
386
|
invokeItemAction(itemId, actionId) {
|
|
387
387
|
this.controller?.invokeItemAction(itemId, actionId);
|
|
388
388
|
}
|
|
389
|
+
async setSort(sort) {
|
|
390
|
+
await this.controller?.setSort(sort);
|
|
391
|
+
}
|
|
392
|
+
async createFolder(name) {
|
|
393
|
+
return (await this.controller?.createFolder(name)) ?? null;
|
|
394
|
+
}
|
|
395
|
+
async renameItem(itemId, name) {
|
|
396
|
+
return (await this.controller?.renameItem(itemId, name)) ?? null;
|
|
397
|
+
}
|
|
398
|
+
async deleteItem(itemId) {
|
|
399
|
+
return (await this.controller?.deleteItem(itemId)) ?? false;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* The live session controller, for pairing the shell with the standalone
|
|
403
|
+
* adapter elements (`box-explorer-toolbar`, `box-explorer-breadcrumbs`, …)
|
|
404
|
+
* or host-chrome bindings. Null until connected.
|
|
405
|
+
*/
|
|
406
|
+
get explorerController() {
|
|
407
|
+
return this.controller;
|
|
408
|
+
}
|
|
389
409
|
updateStringAttribute(name, value) {
|
|
390
410
|
if (value === null || value === "") {
|
|
391
411
|
this.removeAttribute(name);
|
|
@@ -564,6 +584,7 @@ export class ContentExplorer extends BaseElement {
|
|
|
564
584
|
"data-item-id": item.id,
|
|
565
585
|
part: "item-action",
|
|
566
586
|
type: "button",
|
|
587
|
+
...(action.disabled ? { disabled: "", "aria-disabled": "true" } : {}),
|
|
567
588
|
});
|
|
568
589
|
return this.templatesValue.renderItemAction
|
|
569
590
|
? this.templatesValue.renderItemAction({
|
|
@@ -594,7 +615,7 @@ export class ContentExplorer extends BaseElement {
|
|
|
594
615
|
itemButtonAttributes,
|
|
595
616
|
state,
|
|
596
617
|
})
|
|
597
|
-
: `<li data-item-id="${item.id}" role="presentation">
|
|
618
|
+
: `<li data-item-id="${escapeHtml(item.id)}" role="presentation">
|
|
598
619
|
<button ${itemButtonAttributes}><span part="item-name">${escapeHtml(item.name)}</span>${meta ? `<span part="item-meta">${escapeHtml(meta)}</span>` : ""}</button>
|
|
599
620
|
${actions ? `<div part="item-actions">${actions}</div>` : ""}
|
|
600
621
|
</li>`;
|
|
@@ -615,13 +636,15 @@ export class ContentExplorer extends BaseElement {
|
|
|
615
636
|
if (!host) {
|
|
616
637
|
return;
|
|
617
638
|
}
|
|
639
|
+
// Sample focus BEFORE the rebuild wipes the focused node.
|
|
640
|
+
const hadFocusInside = this.isFocusInsideHost();
|
|
618
641
|
host.innerHTML = `
|
|
619
642
|
<section aria-busy="${state?.loading ? "true" : "false"}">
|
|
620
643
|
${folderMarkup}
|
|
621
644
|
<p part="status" data-status="${statusText}" role="status" aria-live="polite">${statusText}</p>
|
|
622
645
|
${errorMarkup}
|
|
623
646
|
<button type="button" part="refresh" aria-label="Refresh items" ${refreshDisabled}>Refresh</button>
|
|
624
|
-
<ul part="items" role="listbox" aria-label="Explorer items">${itemsMarkup}</ul>
|
|
647
|
+
<ul part="items" role="listbox" aria-label="Explorer items" aria-multiselectable="${(this.selectionMode ?? "multiple") === "multiple" ? "true" : "false"}">${itemsMarkup}</ul>
|
|
625
648
|
${loadMoreMarkup ? `<div part="load-more-region">${loadMoreMarkup}</div>` : ""}
|
|
626
649
|
</section>
|
|
627
650
|
`;
|
|
@@ -727,13 +750,23 @@ export class ContentExplorer extends BaseElement {
|
|
|
727
750
|
}
|
|
728
751
|
});
|
|
729
752
|
});
|
|
730
|
-
|
|
753
|
+
// Restore focus only when it already lived inside this element before the
|
|
754
|
+
// rebuild — an unrelated state update (loading tick, selection change from
|
|
755
|
+
// the API) must never steal focus from elsewhere on the page.
|
|
756
|
+
if (this.focusItemId && hadFocusInside) {
|
|
731
757
|
queueMicrotask(() => {
|
|
732
758
|
const target = Array.from(this.shadowRoot?.querySelectorAll('[part="item"]') ?? []).find(node => node.dataset.itemId === this.focusItemId);
|
|
733
759
|
target?.focus();
|
|
734
760
|
});
|
|
735
761
|
}
|
|
736
762
|
}
|
|
763
|
+
isFocusInsideHost() {
|
|
764
|
+
const active = document.activeElement;
|
|
765
|
+
if (!active) {
|
|
766
|
+
return false;
|
|
767
|
+
}
|
|
768
|
+
return active === this || (this.shadowRoot?.contains(active) ?? false);
|
|
769
|
+
}
|
|
737
770
|
}
|
|
738
771
|
SearchResultsHeader.register();
|
|
739
772
|
ContentExplorer.register();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExplorerFetchLike, ExplorerSearchResult, ExplorerTransport, ExplorerTransportResult } from "./types.js";
|
|
1
|
+
import type { ExplorerFetchLike, ExplorerItem, ExplorerSearchResult, ExplorerSortBy, ExplorerSortDirection, ExplorerTransport, ExplorerTransportResult } from "./types.js";
|
|
2
2
|
export interface ContentExplorerRequestContext {
|
|
3
3
|
locale?: string;
|
|
4
4
|
requestId?: string;
|
|
@@ -8,6 +8,8 @@ export interface ContentExplorerListFolderInput {
|
|
|
8
8
|
folderId: string;
|
|
9
9
|
limit?: number;
|
|
10
10
|
offset?: number;
|
|
11
|
+
sortBy?: ExplorerSortBy;
|
|
12
|
+
sortDirection?: ExplorerSortDirection;
|
|
11
13
|
context?: ContentExplorerRequestContext;
|
|
12
14
|
}
|
|
13
15
|
export interface ContentExplorerSearchInput {
|
|
@@ -15,11 +17,32 @@ export interface ContentExplorerSearchInput {
|
|
|
15
17
|
ancestorFolderId?: string;
|
|
16
18
|
limit?: number;
|
|
17
19
|
offset?: number;
|
|
20
|
+
sortBy?: ExplorerSortBy;
|
|
21
|
+
sortDirection?: ExplorerSortDirection;
|
|
22
|
+
context?: ContentExplorerRequestContext;
|
|
23
|
+
}
|
|
24
|
+
export interface ContentExplorerCreateFolderInput {
|
|
25
|
+
parentFolderId: string;
|
|
26
|
+
name: string;
|
|
27
|
+
context?: ContentExplorerRequestContext;
|
|
28
|
+
}
|
|
29
|
+
export interface ContentExplorerRenameItemInput {
|
|
30
|
+
itemId: string;
|
|
31
|
+
itemType: ExplorerItem["type"];
|
|
32
|
+
name: string;
|
|
33
|
+
context?: ContentExplorerRequestContext;
|
|
34
|
+
}
|
|
35
|
+
export interface ContentExplorerDeleteItemInput {
|
|
36
|
+
itemId: string;
|
|
37
|
+
itemType: ExplorerItem["type"];
|
|
18
38
|
context?: ContentExplorerRequestContext;
|
|
19
39
|
}
|
|
20
40
|
export interface ContentExplorerDataSource {
|
|
21
41
|
listFolderItems(input: ContentExplorerListFolderInput): Promise<ExplorerTransportResult>;
|
|
22
42
|
search?(input: ContentExplorerSearchInput): Promise<ExplorerSearchResult>;
|
|
43
|
+
createFolder?(input: ContentExplorerCreateFolderInput): Promise<ExplorerItem>;
|
|
44
|
+
renameItem?(input: ContentExplorerRenameItemInput): Promise<ExplorerItem>;
|
|
45
|
+
deleteItem?(input: ContentExplorerDeleteItemInput): Promise<void>;
|
|
23
46
|
}
|
|
24
47
|
export interface ContentExplorerHttpDataSourceOptions {
|
|
25
48
|
baseUrl?: string;
|
|
@@ -13,15 +13,22 @@ const resolveUrlBase = () => {
|
|
|
13
13
|
}
|
|
14
14
|
return "http://localhost";
|
|
15
15
|
};
|
|
16
|
-
const
|
|
17
|
-
const root = baseUrl.replace(/\/$/, "");
|
|
18
|
-
const url = new URL(`${root}/folders/${encodeURIComponent(input.folderId)}/items`, resolveUrlBase());
|
|
16
|
+
const applyPageAndSortParams = (url, input) => {
|
|
19
17
|
if (typeof input.limit === "number") {
|
|
20
18
|
url.searchParams.set("limit", String(input.limit));
|
|
21
19
|
}
|
|
22
20
|
if (typeof input.offset === "number") {
|
|
23
21
|
url.searchParams.set("offset", String(input.offset));
|
|
24
22
|
}
|
|
23
|
+
if (input.sortBy) {
|
|
24
|
+
url.searchParams.set("sortBy", input.sortBy);
|
|
25
|
+
url.searchParams.set("sortDirection", input.sortDirection ?? "ASC");
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const defaultListFolderUrl = (baseUrl, input) => {
|
|
29
|
+
const root = baseUrl.replace(/\/$/, "");
|
|
30
|
+
const url = new URL(`${root}/folders/${encodeURIComponent(input.folderId)}/items`, resolveUrlBase());
|
|
31
|
+
applyPageAndSortParams(url, input);
|
|
25
32
|
return url.toString();
|
|
26
33
|
};
|
|
27
34
|
const defaultSearchUrl = (baseUrl, input) => {
|
|
@@ -31,12 +38,7 @@ const defaultSearchUrl = (baseUrl, input) => {
|
|
|
31
38
|
if (input.ancestorFolderId) {
|
|
32
39
|
url.searchParams.set("ancestorFolderId", input.ancestorFolderId);
|
|
33
40
|
}
|
|
34
|
-
|
|
35
|
-
url.searchParams.set("limit", String(input.limit));
|
|
36
|
-
}
|
|
37
|
-
if (typeof input.offset === "number") {
|
|
38
|
-
url.searchParams.set("offset", String(input.offset));
|
|
39
|
-
}
|
|
41
|
+
applyPageAndSortParams(url, input);
|
|
40
42
|
return url.toString();
|
|
41
43
|
};
|
|
42
44
|
const getErrorMessage = async (response) => {
|
|
@@ -58,6 +60,8 @@ export const createExplorerTransportFromDataSource = (dataSource) => {
|
|
|
58
60
|
folderId: request.folderId,
|
|
59
61
|
limit: request.limit,
|
|
60
62
|
offset: request.offset,
|
|
63
|
+
sortBy: request.sortBy,
|
|
64
|
+
sortDirection: request.sortDirection,
|
|
61
65
|
context: {
|
|
62
66
|
locale: request.language,
|
|
63
67
|
signal: request.signal,
|
|
@@ -72,35 +76,69 @@ export const createExplorerTransportFromDataSource = (dataSource) => {
|
|
|
72
76
|
ancestorFolderId: request.ancestorFolderId,
|
|
73
77
|
limit: request.limit,
|
|
74
78
|
offset: request.offset,
|
|
79
|
+
sortBy: request.sortBy,
|
|
80
|
+
sortDirection: request.sortDirection,
|
|
75
81
|
context: {
|
|
76
82
|
locale: request.language,
|
|
77
83
|
signal: request.signal,
|
|
78
84
|
},
|
|
79
85
|
});
|
|
80
86
|
}
|
|
87
|
+
if (dataSource.createFolder) {
|
|
88
|
+
const createFolder = dataSource.createFolder.bind(dataSource);
|
|
89
|
+
transport.createFolder = request => createFolder({
|
|
90
|
+
parentFolderId: request.parentFolderId,
|
|
91
|
+
name: request.name,
|
|
92
|
+
context: { locale: request.language, signal: request.signal },
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (dataSource.renameItem) {
|
|
96
|
+
const renameItem = dataSource.renameItem.bind(dataSource);
|
|
97
|
+
transport.renameItem = request => renameItem({
|
|
98
|
+
itemId: request.itemId,
|
|
99
|
+
itemType: request.itemType,
|
|
100
|
+
name: request.name,
|
|
101
|
+
context: { locale: request.language, signal: request.signal },
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
if (dataSource.deleteItem) {
|
|
105
|
+
const deleteItem = dataSource.deleteItem.bind(dataSource);
|
|
106
|
+
transport.deleteItem = request => deleteItem({
|
|
107
|
+
itemId: request.itemId,
|
|
108
|
+
itemType: request.itemType,
|
|
109
|
+
context: { locale: request.language, signal: request.signal },
|
|
110
|
+
});
|
|
111
|
+
}
|
|
81
112
|
return transport;
|
|
82
113
|
};
|
|
83
114
|
export const createHttpContentExplorerDataSource = (options = {}) => {
|
|
84
115
|
const fetchImpl = resolveFetch(options.fetch);
|
|
85
116
|
const baseUrl = options.baseUrl ?? "/api/content-explorer";
|
|
86
|
-
const
|
|
117
|
+
const request = async (url, input, init = { method: "GET" }) => {
|
|
87
118
|
const response = await fetchImpl(url, {
|
|
88
|
-
method:
|
|
119
|
+
method: init.method,
|
|
89
120
|
credentials: "same-origin",
|
|
90
121
|
cache: "no-store",
|
|
91
122
|
headers: {
|
|
92
123
|
accept: "application/json",
|
|
124
|
+
...(init.body !== undefined ? { "content-type": "application/json" } : {}),
|
|
93
125
|
...(input.context?.locale ? { "accept-language": input.context.locale } : {}),
|
|
94
126
|
...(input.context?.requestId ? { "x-request-id": input.context.requestId } : {}),
|
|
95
127
|
...options.headers,
|
|
96
128
|
},
|
|
129
|
+
...(init.body !== undefined ? { body: JSON.stringify(init.body) } : {}),
|
|
97
130
|
signal: input.context?.signal,
|
|
98
131
|
});
|
|
99
132
|
if (!response.ok) {
|
|
100
133
|
throw new Error(await getErrorMessage(response));
|
|
101
134
|
}
|
|
135
|
+
return response;
|
|
136
|
+
};
|
|
137
|
+
const requestJson = async (url, input, init) => {
|
|
138
|
+
const response = await request(url, input, init);
|
|
102
139
|
return (await response.json());
|
|
103
140
|
};
|
|
141
|
+
const root = baseUrl.replace(/\/$/, "");
|
|
104
142
|
return {
|
|
105
143
|
async listFolderItems(input) {
|
|
106
144
|
const url = options.buildListFolderUrl?.(input) ?? defaultListFolderUrl(baseUrl, input);
|
|
@@ -110,5 +148,24 @@ export const createHttpContentExplorerDataSource = (options = {}) => {
|
|
|
110
148
|
const url = options.buildSearchUrl?.(input) ?? defaultSearchUrl(baseUrl, input);
|
|
111
149
|
return requestJson(url, input);
|
|
112
150
|
},
|
|
151
|
+
async createFolder(input) {
|
|
152
|
+
const url = new URL(`${root}/folders`, resolveUrlBase()).toString();
|
|
153
|
+
return requestJson(url, input, {
|
|
154
|
+
method: "POST",
|
|
155
|
+
body: { parentFolderId: input.parentFolderId, name: input.name },
|
|
156
|
+
});
|
|
157
|
+
},
|
|
158
|
+
async renameItem(input) {
|
|
159
|
+
const url = new URL(`${root}/items/${encodeURIComponent(input.itemId)}`, resolveUrlBase()).toString();
|
|
160
|
+
return requestJson(url, input, {
|
|
161
|
+
method: "PUT",
|
|
162
|
+
body: { itemType: input.itemType, name: input.name },
|
|
163
|
+
});
|
|
164
|
+
},
|
|
165
|
+
async deleteItem(input) {
|
|
166
|
+
const url = new URL(`${root}/items/${encodeURIComponent(input.itemId)}`, resolveUrlBase());
|
|
167
|
+
url.searchParams.set("itemType", input.itemType);
|
|
168
|
+
await request(url.toString(), input, { method: "DELETE" });
|
|
169
|
+
},
|
|
113
170
|
};
|
|
114
171
|
};
|