@mmlogic/components 0.3.9 → 0.3.10
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/cjs/loader.cjs.js +1 -1
- package/dist/cjs/mosterdcomponents.cjs.js +1 -1
- package/dist/cjs/{mrd-boolean-field_22.cjs.entry.js → mrd-boolean-field_23.cjs.entry.js} +591 -36
- package/dist/collection/collection-manifest.json +1 -0
- package/dist/collection/components/mrd-document-container/mrd-document-container.js +375 -0
- package/dist/collection/components/mrd-document-container/mrd-document-container.scss +79 -0
- package/dist/collection/components/mrd-document-list/mrd-document-list.js +562 -11
- package/dist/collection/components/mrd-document-list/mrd-document-list.scss +55 -0
- package/dist/collection/components/mrd-document-view/mrd-document-view.js +1 -1
- package/dist/collection/components/mrd-email-field/mrd-email-field.js +1 -1
- package/dist/collection/components/mrd-file-field/mrd-file-field.js +1 -1
- package/dist/collection/components/mrd-image-field/mrd-image-field.js +1 -1
- package/dist/collection/components/mrd-layout-section/mrd-layout-section.js +151 -15
- package/dist/collection/components/mrd-layout-section/mrd-layout-section.scss +0 -44
- package/dist/collection/components/mrd-longtext-field/mrd-longtext-field.js +1 -1
- package/dist/collection/components/mrd-number-field/mrd-number-field.js +1 -1
- package/dist/collection/components/mrd-secret-field/mrd-secret-field.js +2 -2
- package/dist/collection/components/mrd-text-field/mrd-text-field.js +1 -1
- package/dist/collection/components/mrd-textarea-field/mrd-textarea-field.js +1 -1
- package/dist/collection/components/mrd-time-field/mrd-time-field.js +1 -1
- package/dist/collection/dev/app.js +47 -0
- package/dist/collection/utils/i18n.js +10 -0
- package/dist/components/i18n.js +1 -1
- package/dist/components/mrd-document-container.d.ts +11 -0
- package/dist/components/mrd-document-container.js +1 -0
- package/dist/components/mrd-document-container2.js +1 -0
- package/dist/components/mrd-document-list2.js +1 -1
- package/dist/components/mrd-document-view2.js +1 -1
- package/dist/components/mrd-email-field2.js +1 -1
- package/dist/components/mrd-file-field2.js +1 -1
- package/dist/components/mrd-image-field2.js +1 -1
- package/dist/components/mrd-layout-section.js +1 -1
- package/dist/components/mrd-longtext-field2.js +1 -1
- package/dist/components/mrd-number-field2.js +1 -1
- package/dist/components/mrd-secret-field2.js +1 -1
- package/dist/components/mrd-text-field2.js +1 -1
- package/dist/components/mrd-textarea-field2.js +1 -1
- package/dist/components/mrd-time-field2.js +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/esm/mosterdcomponents.js +1 -1
- package/dist/esm/{mrd-boolean-field_22.entry.js → mrd-boolean-field_23.entry.js} +591 -37
- package/dist/mosterdcomponents/cell-renderer-B-AJDBcj.js.map +1 -0
- package/dist/mosterdcomponents/client-layout-ChqRA3AF.js.map +1 -0
- package/dist/mosterdcomponents/format-BAfsQfy1.js.map +1 -0
- package/dist/mosterdcomponents/i18n-s1XrKcD2.js.map +1 -0
- package/dist/mosterdcomponents/index-DkY-NQj0.js.map +1 -0
- package/dist/mosterdcomponents/index-DwF_Sp__.js.map +1 -0
- package/dist/mosterdcomponents/index.esm.js.map +1 -0
- package/dist/mosterdcomponents/mosterdcomponents.esm.js +1 -1
- package/dist/mosterdcomponents/mosterdcomponents.esm.js.map +1 -0
- package/dist/mosterdcomponents/p-69452249.entry.js +1 -0
- package/dist/mosterdcomponents/quill-C9pgw_k-.js.map +1 -0
- package/dist/mosterdcomponents/validation-Br9k2Ztw.js.map +1 -0
- package/dist/types/components/mrd-document-container/mrd-document-container.d.ts +94 -0
- package/dist/types/components/mrd-document-list/mrd-document-list.d.ts +102 -0
- package/dist/types/components/mrd-layout-section/mrd-layout-section.d.ts +29 -5
- package/dist/types/components.d.ts +217 -0
- package/package.json +1 -1
- package/dist/mosterdcomponents/p-b547291b.entry.js +0 -1
|
@@ -30,11 +30,168 @@ export class MrdDocumentList {
|
|
|
30
30
|
this.rootLoading = true;
|
|
31
31
|
/** True when the single-container level is collapsed away (dossier context). */
|
|
32
32
|
this.singleContainer = false;
|
|
33
|
+
/** Currently selected node id — the target for "New folder" and (later) drops. */
|
|
34
|
+
this.selectedId = null;
|
|
35
|
+
/** Node id currently highlighted as a drag-drop target (null = none). */
|
|
36
|
+
this.dropTargetId = null;
|
|
37
|
+
/** Node id showing an "uploading…" indicator while a dropped file is processed. */
|
|
38
|
+
this.uploadingNodeId = null;
|
|
33
39
|
this.nodeIndex = new Map();
|
|
40
|
+
// ── New-folder editing state ───────────────────────────────────────────────
|
|
41
|
+
/** The pending node whose inline name input is open (null = not creating). */
|
|
42
|
+
this.pendingNode = null;
|
|
43
|
+
/** The node the pending folder is being created under. */
|
|
44
|
+
this.pendingParent = null;
|
|
45
|
+
/** The container the pending folder belongs to. */
|
|
46
|
+
this.pendingContainer = null;
|
|
47
|
+
/** Current text in the inline name input. */
|
|
48
|
+
this.pendingName = '';
|
|
49
|
+
/** Ref to the inline input, so we can focus it after it mounts. */
|
|
50
|
+
this.pendingInputEl = null;
|
|
51
|
+
/** Set when a fresh input needs focus on the next render. */
|
|
52
|
+
this.pendingNeedsFocus = false;
|
|
53
|
+
/** Monotonic counter for unique temporary pending-node ids. */
|
|
54
|
+
this.newFolderSeq = 0;
|
|
55
|
+
/** Last emitted canCreateFolder value, to only emit mrdCanCreate on change. */
|
|
56
|
+
this.lastCanCreate = false;
|
|
57
|
+
// ── Drag & drop (move a document into another folder) ──────────────────────
|
|
58
|
+
/** The document row currently being dragged. */
|
|
59
|
+
this.dragDoc = null;
|
|
60
|
+
/** Id of the node the dragged document currently lives in. */
|
|
61
|
+
this.dragFromId = null;
|
|
62
|
+
/** External file dropped onto a node, awaiting its upload to complete. */
|
|
63
|
+
this.pendingUpload = null;
|
|
64
|
+
/** The optimistic document row awaiting its self href from the create POST. */
|
|
65
|
+
this.lastOptimisticDoc = null;
|
|
66
|
+
this.startCreateFolder = () => {
|
|
67
|
+
const resolved = this.resolveTarget();
|
|
68
|
+
if (!resolved || this.pendingNode)
|
|
69
|
+
return;
|
|
70
|
+
const { target, container } = resolved;
|
|
71
|
+
const tempId = `${container.id}/__new__${this.newFolderSeq++}`;
|
|
72
|
+
const node = this.makeFolderNode(tempId, '', container.id);
|
|
73
|
+
node.pending = true;
|
|
74
|
+
node.editing = true;
|
|
75
|
+
target.children = [...target.children, node];
|
|
76
|
+
target.open = true;
|
|
77
|
+
this.nodeIndex.set(node.id, node);
|
|
78
|
+
this.pendingNode = node;
|
|
79
|
+
this.pendingParent = target;
|
|
80
|
+
this.pendingContainer = container;
|
|
81
|
+
this.pendingName = '';
|
|
82
|
+
this.pendingNeedsFocus = true;
|
|
83
|
+
this.bump();
|
|
84
|
+
};
|
|
85
|
+
this.onPendingInput = (e) => {
|
|
86
|
+
var _a;
|
|
87
|
+
this.pendingName = e.target.value;
|
|
88
|
+
if ((_a = this.pendingNode) === null || _a === void 0 ? void 0 : _a.error) {
|
|
89
|
+
this.pendingNode.error = undefined;
|
|
90
|
+
this.bump();
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
this.onPendingKeyDown = (e) => {
|
|
94
|
+
if (e.key === 'Enter') {
|
|
95
|
+
e.preventDefault();
|
|
96
|
+
this.commitFolder();
|
|
97
|
+
}
|
|
98
|
+
else if (e.key === 'Escape') {
|
|
99
|
+
e.preventDefault();
|
|
100
|
+
this.cancelFolder();
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
// ── Drag & drop: move a document into another folder / container ────────────
|
|
104
|
+
this.onDocDragStart = (e, doc, node) => {
|
|
105
|
+
var _a, _b, _c;
|
|
106
|
+
this.dragDoc = doc;
|
|
107
|
+
this.dragFromId = node.id;
|
|
108
|
+
if (e.dataTransfer) {
|
|
109
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
110
|
+
// A payload is required for a drag to start in some browsers.
|
|
111
|
+
e.dataTransfer.setData('text/plain', (_c = (_b = (_a = doc === null || doc === void 0 ? void 0 : doc._links) === null || _a === void 0 ? void 0 : _a.self) === null || _b === void 0 ? void 0 : _b.href) !== null && _c !== void 0 ? _c : '');
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
this.onDocDragEnd = () => {
|
|
115
|
+
this.dragDoc = null;
|
|
116
|
+
this.dragFromId = null;
|
|
117
|
+
if (this.dropTargetId !== null)
|
|
118
|
+
this.dropTargetId = null;
|
|
119
|
+
};
|
|
120
|
+
/** A folder/container node (or a document inside it) accepts an internal move
|
|
121
|
+
* or an external file drop. Always claims the event so it never bubbles to the
|
|
122
|
+
* root drop-zone; a different node is highlighted (its own folder = no-op). */
|
|
123
|
+
this.onNodeDragOver = (e, node) => {
|
|
124
|
+
const file = this.isFileDrag(e);
|
|
125
|
+
if (!this.dragDoc && !file)
|
|
126
|
+
return;
|
|
127
|
+
e.preventDefault();
|
|
128
|
+
e.stopPropagation();
|
|
129
|
+
if (e.dataTransfer)
|
|
130
|
+
e.dataTransfer.dropEffect = file ? 'copy' : 'move';
|
|
131
|
+
const target = !file && node.id === this.dragFromId ? null : node.id;
|
|
132
|
+
if (this.dropTargetId !== target)
|
|
133
|
+
this.dropTargetId = target;
|
|
134
|
+
};
|
|
135
|
+
this.onNodeDragLeave = (node) => {
|
|
136
|
+
if (this.dropTargetId === node.id)
|
|
137
|
+
this.dropTargetId = null;
|
|
138
|
+
};
|
|
139
|
+
this.onNodeDrop = (e, node, container) => {
|
|
140
|
+
if (this.isFileDrag(e)) {
|
|
141
|
+
e.preventDefault();
|
|
142
|
+
e.stopPropagation();
|
|
143
|
+
this.startFileUpload(e, node, container);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (!this.dragDoc)
|
|
147
|
+
return;
|
|
148
|
+
e.preventDefault();
|
|
149
|
+
e.stopPropagation();
|
|
150
|
+
this.moveDocument(node, container);
|
|
151
|
+
this.onDocDragEnd();
|
|
152
|
+
};
|
|
153
|
+
/** Root-zone drop (dossier context): target the single container's root. */
|
|
154
|
+
this.onRootDragOver = (e) => {
|
|
155
|
+
if (!this.singleContainer)
|
|
156
|
+
return;
|
|
157
|
+
const file = this.isFileDrag(e);
|
|
158
|
+
if (!this.dragDoc && !file)
|
|
159
|
+
return;
|
|
160
|
+
e.preventDefault();
|
|
161
|
+
if (e.dataTransfer)
|
|
162
|
+
e.dataTransfer.dropEffect = file ? 'copy' : 'move';
|
|
163
|
+
};
|
|
164
|
+
this.onRootDrop = (e) => {
|
|
165
|
+
if (!this.singleContainer)
|
|
166
|
+
return;
|
|
167
|
+
const only = this.containers[0];
|
|
168
|
+
if (this.isFileDrag(e)) {
|
|
169
|
+
e.preventDefault();
|
|
170
|
+
this.startFileUpload(e, only, only);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (!this.dragDoc)
|
|
174
|
+
return;
|
|
175
|
+
e.preventDefault();
|
|
176
|
+
this.moveDocument(only, only);
|
|
177
|
+
this.onDocDragEnd();
|
|
178
|
+
};
|
|
34
179
|
}
|
|
35
180
|
componentDidLoad() {
|
|
36
181
|
this.emitContainerDistinct();
|
|
37
182
|
}
|
|
183
|
+
componentDidRender() {
|
|
184
|
+
if (this.pendingNeedsFocus && this.pendingInputEl) {
|
|
185
|
+
this.pendingInputEl.focus();
|
|
186
|
+
this.pendingInputEl.select();
|
|
187
|
+
this.pendingNeedsFocus = false;
|
|
188
|
+
}
|
|
189
|
+
const can = this.canCreateFolder;
|
|
190
|
+
if (can !== this.lastCanCreate) {
|
|
191
|
+
this.lastCanCreate = can;
|
|
192
|
+
this.mrdCanCreate.emit(can);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
38
195
|
// ── Slot field names ─────────────────────────────────────────────────────
|
|
39
196
|
get containerField() {
|
|
40
197
|
var _a, _b, _c;
|
|
@@ -48,6 +205,10 @@ export class MrdDocumentList {
|
|
|
48
205
|
var _a, _b, _c;
|
|
49
206
|
return (_c = (_b = (_a = this.archetype) === null || _a === void 0 ? void 0 : _a.slots) === null || _b === void 0 ? void 0 : _b.title) !== null && _c !== void 0 ? _c : 'name';
|
|
50
207
|
}
|
|
208
|
+
get fileField() {
|
|
209
|
+
var _a, _b, _c;
|
|
210
|
+
return (_c = (_b = (_a = this.archetype) === null || _a === void 0 ? void 0 : _a.slots) === null || _b === void 0 ? void 0 : _b.file) !== null && _c !== void 0 ? _c : 'file';
|
|
211
|
+
}
|
|
51
212
|
get dateField() {
|
|
52
213
|
var _a, _b;
|
|
53
214
|
return (_b = (_a = this.archetype) === null || _a === void 0 ? void 0 : _a.slots) === null || _b === void 0 ? void 0 : _b.date;
|
|
@@ -200,7 +361,7 @@ export class MrdDocumentList {
|
|
|
200
361
|
acc.push(seg);
|
|
201
362
|
let child = node.children.find(c => c.label === seg);
|
|
202
363
|
if (!child) {
|
|
203
|
-
child = this.makeFolderNode(container.id + '/' + acc.join('/'), seg);
|
|
364
|
+
child = this.makeFolderNode(container.id + '/' + acc.join('/'), seg, container.id);
|
|
204
365
|
node.children.push(child);
|
|
205
366
|
this.nodeIndex.set(child.id, child);
|
|
206
367
|
}
|
|
@@ -236,9 +397,9 @@ export class MrdDocumentList {
|
|
|
236
397
|
open: false, loading: false, foldersLoaded: false, docsLoaded: false, docs: [],
|
|
237
398
|
};
|
|
238
399
|
}
|
|
239
|
-
makeFolderNode(id, label) {
|
|
400
|
+
makeFolderNode(id, label, containerId) {
|
|
240
401
|
return {
|
|
241
|
-
id, kind: 'folder', label,
|
|
402
|
+
id, kind: 'folder', label, containerId,
|
|
242
403
|
hasDocs: false, docCount: 0, totalCount: 0, children: [],
|
|
243
404
|
open: false, loading: false, foldersLoaded: false, docsLoaded: false, docs: [],
|
|
244
405
|
};
|
|
@@ -256,6 +417,7 @@ export class MrdDocumentList {
|
|
|
256
417
|
}
|
|
257
418
|
// ── Interaction ──────────────────────────────────────────────────────────
|
|
258
419
|
toggleContainer(node) {
|
|
420
|
+
this.selectedId = node.id;
|
|
259
421
|
node.open = !node.open;
|
|
260
422
|
if (node.open && !node.foldersLoaded && !node.loading) {
|
|
261
423
|
this.emitFolderDistinct(node);
|
|
@@ -263,12 +425,235 @@ export class MrdDocumentList {
|
|
|
263
425
|
this.bump();
|
|
264
426
|
}
|
|
265
427
|
toggleFolder(container, node) {
|
|
428
|
+
this.selectedId = node.id;
|
|
266
429
|
node.open = !node.open;
|
|
267
430
|
if (node.open && node.hasDocs && !node.docsLoaded && !node.loading) {
|
|
268
431
|
this.emitDocuments(container, node);
|
|
269
432
|
}
|
|
270
433
|
this.bump();
|
|
271
434
|
}
|
|
435
|
+
// ── New folder (client-side pending, materialises on document drop) ─────────
|
|
436
|
+
/** Resolve the node a new folder should be created under, and its container.
|
|
437
|
+
* Uses the current selection; falls back to the single container (dossier). */
|
|
438
|
+
resolveTarget() {
|
|
439
|
+
if (this.selectedId) {
|
|
440
|
+
const n = this.nodeIndex.get(this.selectedId);
|
|
441
|
+
if (n && n.kind === 'container')
|
|
442
|
+
return { target: n, container: n };
|
|
443
|
+
if (n && n.kind === 'folder') {
|
|
444
|
+
const container = n.containerId ? this.nodeIndex.get(n.containerId) : undefined;
|
|
445
|
+
if (container)
|
|
446
|
+
return { target: n, container };
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (this.singleContainer && this.containers[0]) {
|
|
450
|
+
const only = this.containers[0];
|
|
451
|
+
return { target: only, container: only };
|
|
452
|
+
}
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
get canCreateFolder() {
|
|
456
|
+
return !this.pendingNode && this.resolveTarget() !== null;
|
|
457
|
+
}
|
|
458
|
+
/** Start creating a folder under the current target. Called by the host toolbar
|
|
459
|
+
* (mrd-document-container). No-op when there is no valid target. */
|
|
460
|
+
async createFolder() {
|
|
461
|
+
this.startCreateFolder();
|
|
462
|
+
}
|
|
463
|
+
/** Folder path segments of a node relative to its container (derived from its id). */
|
|
464
|
+
segmentsOf(node, container) {
|
|
465
|
+
if (node.kind === 'container')
|
|
466
|
+
return [];
|
|
467
|
+
const prefix = container.id + '/';
|
|
468
|
+
const rest = node.id.startsWith(prefix) ? node.id.slice(prefix.length) : '';
|
|
469
|
+
return rest ? rest.split('/') : [];
|
|
470
|
+
}
|
|
471
|
+
commitFolder() {
|
|
472
|
+
const node = this.pendingNode;
|
|
473
|
+
const parent = this.pendingParent;
|
|
474
|
+
const container = this.pendingContainer;
|
|
475
|
+
if (!node || !parent || !container)
|
|
476
|
+
return;
|
|
477
|
+
const name = this.pendingName.trim();
|
|
478
|
+
if (!name) {
|
|
479
|
+
this.cancelFolder();
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
if (name.includes('/')) {
|
|
483
|
+
node.error = t('folder_no_slash', this.locale);
|
|
484
|
+
this.bump();
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
const dup = parent.children.some(c => c !== node && c.label.toLowerCase() === name.toLowerCase());
|
|
488
|
+
if (dup) {
|
|
489
|
+
node.error = t('folder_duplicate', this.locale);
|
|
490
|
+
this.bump();
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
// Finalise: swap the temp id for the real path-derived id so a later distinct
|
|
494
|
+
// value with the same folder string merges onto this node.
|
|
495
|
+
const segs = [...this.segmentsOf(parent, container), name];
|
|
496
|
+
this.nodeIndex.delete(node.id);
|
|
497
|
+
node.id = container.id + '/' + segs.join('/');
|
|
498
|
+
node.fullPath = '/' + segs.join('/');
|
|
499
|
+
node.label = name;
|
|
500
|
+
node.editing = false;
|
|
501
|
+
node.error = undefined;
|
|
502
|
+
node.open = true;
|
|
503
|
+
this.nodeIndex.set(node.id, node);
|
|
504
|
+
this.selectedId = node.id;
|
|
505
|
+
this.clearPending();
|
|
506
|
+
this.bump();
|
|
507
|
+
}
|
|
508
|
+
cancelFolder() {
|
|
509
|
+
const node = this.pendingNode;
|
|
510
|
+
const parent = this.pendingParent;
|
|
511
|
+
if (node && parent) {
|
|
512
|
+
parent.children = parent.children.filter(c => c !== node);
|
|
513
|
+
this.nodeIndex.delete(node.id);
|
|
514
|
+
}
|
|
515
|
+
this.clearPending();
|
|
516
|
+
this.bump();
|
|
517
|
+
}
|
|
518
|
+
clearPending() {
|
|
519
|
+
this.pendingNode = null;
|
|
520
|
+
this.pendingParent = null;
|
|
521
|
+
this.pendingContainer = null;
|
|
522
|
+
this.pendingName = '';
|
|
523
|
+
this.pendingInputEl = null;
|
|
524
|
+
this.pendingNeedsFocus = false;
|
|
525
|
+
}
|
|
526
|
+
/** True when the drag carries external files (an OS file drop, not an internal move). */
|
|
527
|
+
isFileDrag(e) {
|
|
528
|
+
return !this.dragDoc && !!e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files');
|
|
529
|
+
}
|
|
530
|
+
// ── External file drop → upload → create document ──────────────────────────
|
|
531
|
+
/** Begin an upload: emit mrdUpload; the host uploads and calls setFileReference. */
|
|
532
|
+
startFileUpload(e, node, container) {
|
|
533
|
+
var _a, _b;
|
|
534
|
+
const file = (_b = (_a = e.dataTransfer) === null || _a === void 0 ? void 0 : _a.files) === null || _b === void 0 ? void 0 : _b[0]; // single file per drop
|
|
535
|
+
this.dropTargetId = null;
|
|
536
|
+
if (!file)
|
|
537
|
+
return;
|
|
538
|
+
this.pendingUpload = { file, node, container };
|
|
539
|
+
node.open = true;
|
|
540
|
+
this.uploadingNodeId = node.id;
|
|
541
|
+
this.bump();
|
|
542
|
+
this.mrdUpload.emit({ file });
|
|
543
|
+
}
|
|
544
|
+
/** Strip a single trailing extension for the document title. */
|
|
545
|
+
stripExtension(name) {
|
|
546
|
+
return name.replace(/\.[^./\\]+$/, '');
|
|
547
|
+
}
|
|
548
|
+
/** Host callback with the uploaded file reference. Builds the create payload
|
|
549
|
+
* from the archetype slots and requests the new document. */
|
|
550
|
+
async setFileReference(uri) {
|
|
551
|
+
const up = this.pendingUpload;
|
|
552
|
+
if (!up)
|
|
553
|
+
return;
|
|
554
|
+
const { file, node, container } = up;
|
|
555
|
+
const folder = this.folderStringOf(node, container);
|
|
556
|
+
const values = {
|
|
557
|
+
[this.fileField]: uri,
|
|
558
|
+
[this.titleField]: this.stripExtension(file.name),
|
|
559
|
+
[this.folderField]: folder,
|
|
560
|
+
[this.containerField]: container.href,
|
|
561
|
+
};
|
|
562
|
+
this.mrdCreateObject.emit({ path: this.buildDataPath(), values });
|
|
563
|
+
// Optimistically show the new document (no href until a reload).
|
|
564
|
+
const optimistic = { [this.titleField]: this.stripExtension(file.name), _optimistic: true };
|
|
565
|
+
const wasUnloadedEmpty = !node.docsLoaded && node.docCount === 0;
|
|
566
|
+
node.hasDocs = true;
|
|
567
|
+
node.pending = false;
|
|
568
|
+
node.docCount += 1;
|
|
569
|
+
if (wasUnloadedEmpty) {
|
|
570
|
+
node.docsLoaded = true;
|
|
571
|
+
node.docs = [optimistic];
|
|
572
|
+
}
|
|
573
|
+
else if (node.docsLoaded)
|
|
574
|
+
node.docs = [...node.docs, optimistic];
|
|
575
|
+
this.recomputeContainerTotals(container, +1);
|
|
576
|
+
this.lastOptimisticDoc = optimistic;
|
|
577
|
+
this.pendingUpload = null;
|
|
578
|
+
this.uploadingNodeId = null;
|
|
579
|
+
this.bump();
|
|
580
|
+
}
|
|
581
|
+
/** Host callback with the created document's self href, so the optimistic row
|
|
582
|
+
* becomes navigable without waiting for a reload. */
|
|
583
|
+
async setCreatedHref(href) {
|
|
584
|
+
var _a;
|
|
585
|
+
const doc = this.lastOptimisticDoc;
|
|
586
|
+
if (!doc || !href)
|
|
587
|
+
return;
|
|
588
|
+
doc._links = Object.assign(Object.assign({}, ((_a = doc._links) !== null && _a !== void 0 ? _a : {})), { self: { href } });
|
|
589
|
+
doc._optimistic = false;
|
|
590
|
+
this.lastOptimisticDoc = null;
|
|
591
|
+
this.bump();
|
|
592
|
+
}
|
|
593
|
+
/** Folder string a node represents: the raw distinct value when it holds
|
|
594
|
+
* documents, else derived from its path segments; null for a container root. */
|
|
595
|
+
folderStringOf(node, container) {
|
|
596
|
+
if (node.kind === 'container')
|
|
597
|
+
return null;
|
|
598
|
+
if (node.fullPath != null && node.fullPath !== '')
|
|
599
|
+
return node.fullPath;
|
|
600
|
+
const segs = this.segmentsOf(node, container);
|
|
601
|
+
return segs.length ? '/' + segs.join('/') : null;
|
|
602
|
+
}
|
|
603
|
+
moveDocument(toNode, toContainer) {
|
|
604
|
+
var _a, _b;
|
|
605
|
+
const doc = this.dragDoc;
|
|
606
|
+
const fromNode = this.dragFromId ? this.nodeIndex.get(this.dragFromId) : undefined;
|
|
607
|
+
if (!doc || !fromNode)
|
|
608
|
+
return;
|
|
609
|
+
const fromContainer = fromNode.kind === 'container'
|
|
610
|
+
? fromNode
|
|
611
|
+
: (fromNode.containerId ? this.nodeIndex.get(fromNode.containerId) : undefined);
|
|
612
|
+
if (!fromContainer)
|
|
613
|
+
return;
|
|
614
|
+
const targetFolder = this.folderStringOf(toNode, toContainer);
|
|
615
|
+
const sourceFolder = this.folderStringOf(fromNode, fromContainer);
|
|
616
|
+
const sameContainer = toContainer === fromContainer;
|
|
617
|
+
// No-op: dropped back onto its own folder.
|
|
618
|
+
if (toNode === fromNode || (sameContainer && targetFolder === sourceFolder))
|
|
619
|
+
return;
|
|
620
|
+
// ── Optimistic tree update (fire-and-forget; no refetch) ──
|
|
621
|
+
fromNode.docs = fromNode.docs.filter(d => d !== doc);
|
|
622
|
+
fromNode.docCount = Math.max(0, fromNode.docCount - 1);
|
|
623
|
+
// A now-empty folder should stop showing an expand chevron (it disappears on
|
|
624
|
+
// the next reload anyway, since it is derived from document folder strings).
|
|
625
|
+
if (fromNode.docsLoaded)
|
|
626
|
+
fromNode.hasDocs = fromNode.docs.length > 0;
|
|
627
|
+
toNode.hasDocs = true;
|
|
628
|
+
toNode.pending = false; // a pending folder is now materialised
|
|
629
|
+
toNode.docCount += 1;
|
|
630
|
+
if (toNode.kind === 'folder')
|
|
631
|
+
toNode.fullPath = targetFolder !== null && targetFolder !== void 0 ? targetFolder : '';
|
|
632
|
+
if (toNode.docsLoaded)
|
|
633
|
+
toNode.docs = [...toNode.docs, doc];
|
|
634
|
+
this.recomputeContainerTotals(fromContainer, -1);
|
|
635
|
+
if (!sameContainer)
|
|
636
|
+
this.recomputeContainerTotals(toContainer, +1);
|
|
637
|
+
// ── Emit the generic update; host PATCHes the document ──
|
|
638
|
+
const href = (_b = (_a = doc === null || doc === void 0 ? void 0 : doc._links) === null || _a === void 0 ? void 0 : _a.self) === null || _b === void 0 ? void 0 : _b.href;
|
|
639
|
+
if (href) {
|
|
640
|
+
const values = { [this.folderField]: targetFolder };
|
|
641
|
+
if (!sameContainer)
|
|
642
|
+
values[this.containerField] = toContainer.href;
|
|
643
|
+
this.mrdUpdateObject.emit({ href, values });
|
|
644
|
+
}
|
|
645
|
+
this.bump();
|
|
646
|
+
}
|
|
647
|
+
/** Recompute a container's subtree totals from docCounts when its folders are
|
|
648
|
+
* loaded; otherwise just nudge the aggregate badge by the delta. */
|
|
649
|
+
recomputeContainerTotals(container, delta) {
|
|
650
|
+
if (container.foldersLoaded) {
|
|
651
|
+
this.computeTotals(container);
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
container.totalCount = Math.max(0, container.totalCount + delta);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
272
657
|
openDoc(doc) {
|
|
273
658
|
var _a, _b, _c, _d;
|
|
274
659
|
const href = (_b = (_a = doc === null || doc === void 0 ? void 0 : doc._links) === null || _a === void 0 ? void 0 : _a.self) === null || _b === void 0 ? void 0 : _b.href;
|
|
@@ -291,12 +676,12 @@ export class MrdDocumentList {
|
|
|
291
676
|
indent(depth) {
|
|
292
677
|
return h("span", { class: "mrd-document-list__indent", style: { width: `${depth * 18}px` } });
|
|
293
678
|
}
|
|
294
|
-
renderDocRow(doc, depth) {
|
|
679
|
+
renderDocRow(doc, node, container, depth) {
|
|
295
680
|
var _a, _b, _c, _d, _e;
|
|
296
681
|
const label = (_b = (_a = doc === null || doc === void 0 ? void 0 : doc[this.titleField]) !== null && _a !== void 0 ? _a : doc === null || doc === void 0 ? void 0 : doc.name) !== null && _b !== void 0 ? _b : '';
|
|
297
682
|
const dateRaw = this.dateField ? doc === null || doc === void 0 ? void 0 : doc[this.dateField] : undefined;
|
|
298
683
|
const date = dateRaw ? formatDate(dateRaw, this.locale) : '';
|
|
299
|
-
return (h("div", { class: "mrd-document-list__row mrd-document-list__row--doc", key: (_e = (_d = (_c = doc === null || doc === void 0 ? void 0 : doc._links) === null || _c === void 0 ? void 0 : _c.self) === null || _d === void 0 ? void 0 : _d.href) !== null && _e !== void 0 ? _e : label, onClick: () => this.openDoc(doc) }, this.indent(depth), this.renderChevron(false, true), this.renderDocIcon(), h("span", { class: "mrd-document-list__label" }, label), date && h("span", { class: "mrd-document-list__date" }, date)));
|
|
684
|
+
return (h("div", { class: "mrd-document-list__row mrd-document-list__row--doc", key: (_e = (_d = (_c = doc === null || doc === void 0 ? void 0 : doc._links) === null || _c === void 0 ? void 0 : _c.self) === null || _d === void 0 ? void 0 : _d.href) !== null && _e !== void 0 ? _e : label, draggable: true, onClick: () => this.openDoc(doc), onDragStart: (e) => this.onDocDragStart(e, doc, node), onDragEnd: this.onDocDragEnd, onDragOver: (e) => this.onNodeDragOver(e, node), onDragLeave: () => this.onNodeDragLeave(node), onDrop: (e) => this.onNodeDrop(e, node, container) }, this.indent(depth), this.renderChevron(false, true), this.renderDocIcon(), h("span", { class: "mrd-document-list__label" }, label), date && h("span", { class: "mrd-document-list__date" }, date)));
|
|
300
685
|
}
|
|
301
686
|
renderLoadingRow(depth) {
|
|
302
687
|
return (h("div", { class: "mrd-document-list__loading", key: "loading" }, this.indent(depth), h("span", { class: "mrd-document-list__spinner" }), t('loading', this.locale)));
|
|
@@ -307,7 +692,7 @@ export class MrdDocumentList {
|
|
|
307
692
|
if (node.hasDocs) {
|
|
308
693
|
if (node.docsLoaded) {
|
|
309
694
|
for (const doc of node.docs)
|
|
310
|
-
out.push(this.renderDocRow(doc, depth));
|
|
695
|
+
out.push(this.renderDocRow(doc, node, container, depth));
|
|
311
696
|
}
|
|
312
697
|
else if (node.loading) {
|
|
313
698
|
out.push(this.renderLoadingRow(depth));
|
|
@@ -317,14 +702,34 @@ export class MrdDocumentList {
|
|
|
317
702
|
for (const child of node.children) {
|
|
318
703
|
out.push(this.renderFolderNode(container, child, depth));
|
|
319
704
|
}
|
|
705
|
+
// An uploading file dropped onto this node.
|
|
706
|
+
if (node.id === this.uploadingNodeId)
|
|
707
|
+
out.push(this.renderUploadingRow(depth));
|
|
320
708
|
return out;
|
|
321
709
|
}
|
|
710
|
+
renderUploadingRow(depth) {
|
|
711
|
+
return (h("div", { class: "mrd-document-list__loading", key: "uploading" }, this.indent(depth), h("span", { class: "mrd-document-list__spinner" }), t('upload_file', this.locale)));
|
|
712
|
+
}
|
|
713
|
+
renderFolderEditRow(node, depth) {
|
|
714
|
+
return (h("div", { class: "mrd-document-list__node", key: node.id }, h("div", { class: "mrd-document-list__row mrd-document-list__row--edit" }, this.indent(depth), this.renderChevron(false, true), this.renderFolderIcon(), h("input", { class: "mrd-document-list__name-input", type: "text", placeholder: t('folder_name_placeholder', this.locale), ref: el => (this.pendingInputEl = el), onInput: this.onPendingInput, onKeyDown: this.onPendingKeyDown, onBlur: () => this.cancelFolder() })), node.error && (h("div", { class: "mrd-document-list__error", style: { paddingLeft: `${depth * 18 + 52}px` } }, node.error))));
|
|
715
|
+
}
|
|
322
716
|
renderFolderNode(container, node, depth) {
|
|
717
|
+
if (node.editing)
|
|
718
|
+
return this.renderFolderEditRow(node, depth);
|
|
323
719
|
const leaf = node.children.length === 0 && !node.hasDocs;
|
|
324
|
-
|
|
720
|
+
const selected = node.id === this.selectedId;
|
|
721
|
+
const cls = 'mrd-document-list__row'
|
|
722
|
+
+ (selected ? ' mrd-document-list__row--selected' : '')
|
|
723
|
+
+ (node.pending ? ' mrd-document-list__row--pending' : '')
|
|
724
|
+
+ (node.id === this.dropTargetId ? ' mrd-document-list__row--drop' : '');
|
|
725
|
+
return (h("div", { class: "mrd-document-list__node", key: node.id }, h("div", { class: cls, onClick: () => this.toggleFolder(container, node), onDragOver: (e) => this.onNodeDragOver(e, node), onDragLeave: () => this.onNodeDragLeave(node), onDrop: (e) => this.onNodeDrop(e, node, container) }, this.indent(depth), this.renderChevron(node.open, leaf), this.renderFolderIcon(), h("span", { class: "mrd-document-list__label" }, node.label), h("span", { class: "mrd-document-list__count" }, node.totalCount)), node.open && h("div", { class: "mrd-document-list__kids" }, this.renderChildren(container, node, depth + 1))));
|
|
325
726
|
}
|
|
326
727
|
renderContainerNode(node, depth) {
|
|
327
|
-
|
|
728
|
+
const selected = node.id === this.selectedId;
|
|
729
|
+
const cls = 'mrd-document-list__row mrd-document-list__row--dossier'
|
|
730
|
+
+ (selected ? ' mrd-document-list__row--selected' : '')
|
|
731
|
+
+ (node.id === this.dropTargetId ? ' mrd-document-list__row--drop' : '');
|
|
732
|
+
return (h("div", { class: "mrd-document-list__node", key: node.id }, h("div", { class: cls, onClick: () => this.toggleContainer(node), onDragOver: (e) => this.onNodeDragOver(e, node), onDragLeave: () => this.onNodeDragLeave(node), onDrop: (e) => this.onNodeDrop(e, node, node) }, this.indent(depth), this.renderChevron(node.open, false), this.renderDossierIcon(), h("span", { class: "mrd-document-list__label" }, node.label), h("span", { class: "mrd-document-list__count" }, node.totalCount)), node.open && (h("div", { class: "mrd-document-list__kids" }, node.loading && !node.foldersLoaded
|
|
328
733
|
? this.renderLoadingRow(depth + 1)
|
|
329
734
|
: this.renderChildren(node, node, depth + 1)))));
|
|
330
735
|
}
|
|
@@ -334,7 +739,7 @@ export class MrdDocumentList {
|
|
|
334
739
|
body = this.renderLoadingRow(0);
|
|
335
740
|
}
|
|
336
741
|
else if (this.containers.length === 0) {
|
|
337
|
-
body = h("div", { key: '
|
|
742
|
+
body = h("div", { key: 'b8c3297cd7cff5bf79ce694d2ee1a64c570df5ce', class: "mrd-document-list__empty" }, t('no_results', this.locale));
|
|
338
743
|
}
|
|
339
744
|
else if (this.singleContainer) {
|
|
340
745
|
// Dossier context: render the single container's folder tree at the root.
|
|
@@ -346,7 +751,7 @@ export class MrdDocumentList {
|
|
|
346
751
|
else {
|
|
347
752
|
body = this.containers.map(c => this.renderContainerNode(c, 0));
|
|
348
753
|
}
|
|
349
|
-
return (h(Host, { key: '
|
|
754
|
+
return (h(Host, { key: 'ff3b024a79ba2270aa28861923bc6406f98061aa' }, h("div", { key: '9db121e5336a4e3c82443257f6424db5ab4f0baf', class: "mrd-document-list", onDragOver: this.onRootDragOver, onDrop: this.onRootDrop }, body)));
|
|
350
755
|
}
|
|
351
756
|
static get is() { return "mrd-document-list"; }
|
|
352
757
|
static get encapsulation() { return "scoped"; }
|
|
@@ -457,7 +862,10 @@ export class MrdDocumentList {
|
|
|
457
862
|
return {
|
|
458
863
|
"containers": {},
|
|
459
864
|
"rootLoading": {},
|
|
460
|
-
"singleContainer": {}
|
|
865
|
+
"singleContainer": {},
|
|
866
|
+
"selectedId": {},
|
|
867
|
+
"dropTargetId": {},
|
|
868
|
+
"uploadingNodeId": {}
|
|
461
869
|
};
|
|
462
870
|
}
|
|
463
871
|
static get events() {
|
|
@@ -506,6 +914,81 @@ export class MrdDocumentList {
|
|
|
506
914
|
"resolved": "{ href?: string | undefined; label: string; }",
|
|
507
915
|
"references": {}
|
|
508
916
|
}
|
|
917
|
+
}, {
|
|
918
|
+
"method": "mrdCanCreate",
|
|
919
|
+
"name": "mrdCanCreate",
|
|
920
|
+
"bubbles": true,
|
|
921
|
+
"cancelable": true,
|
|
922
|
+
"composed": true,
|
|
923
|
+
"docs": {
|
|
924
|
+
"tags": [],
|
|
925
|
+
"text": "Emitted whenever the \"can create a folder here\" state changes, so a host\ntoolbar (mrd-document-container) can enable/disable its New folder button."
|
|
926
|
+
},
|
|
927
|
+
"complexType": {
|
|
928
|
+
"original": "boolean",
|
|
929
|
+
"resolved": "boolean",
|
|
930
|
+
"references": {}
|
|
931
|
+
}
|
|
932
|
+
}, {
|
|
933
|
+
"method": "mrdUpdateObject",
|
|
934
|
+
"name": "mrdUpdateObject",
|
|
935
|
+
"bubbles": true,
|
|
936
|
+
"cancelable": true,
|
|
937
|
+
"composed": true,
|
|
938
|
+
"docs": {
|
|
939
|
+
"tags": [],
|
|
940
|
+
"text": "Generic \"update an existing object\" request \u2014 fired when a document is\ndragged into another folder/container. Host PATCHes `href` with `values`\n(no move/folder knowledge needed). `values` carries the new folder string\n(or null for the container root) and, on a cross-container move, the new\ncontainer relation href."
|
|
941
|
+
},
|
|
942
|
+
"complexType": {
|
|
943
|
+
"original": "{ href: string; values: Record<string, unknown> }",
|
|
944
|
+
"resolved": "{ href: string; values: Record<string, unknown>; }",
|
|
945
|
+
"references": {
|
|
946
|
+
"Record": {
|
|
947
|
+
"location": "global",
|
|
948
|
+
"id": "global::Record"
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}, {
|
|
953
|
+
"method": "mrdUpload",
|
|
954
|
+
"name": "mrdUpload",
|
|
955
|
+
"bubbles": true,
|
|
956
|
+
"cancelable": true,
|
|
957
|
+
"composed": true,
|
|
958
|
+
"docs": {
|
|
959
|
+
"tags": [],
|
|
960
|
+
"text": "An external file was dropped onto a folder/container. Same flow as mrd-form:\nhost uploads the file and calls `setFileReference(uri)` with the result."
|
|
961
|
+
},
|
|
962
|
+
"complexType": {
|
|
963
|
+
"original": "{ file: File }",
|
|
964
|
+
"resolved": "{ file: File; }",
|
|
965
|
+
"references": {
|
|
966
|
+
"File": {
|
|
967
|
+
"location": "global",
|
|
968
|
+
"id": "global::File"
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}, {
|
|
973
|
+
"method": "mrdCreateObject",
|
|
974
|
+
"name": "mrdCreateObject",
|
|
975
|
+
"bubbles": true,
|
|
976
|
+
"cancelable": true,
|
|
977
|
+
"composed": true,
|
|
978
|
+
"docs": {
|
|
979
|
+
"tags": [],
|
|
980
|
+
"text": "Fired once the dropped file is uploaded \u2014 requests creation of a new document.\nHost POSTs `values` to `path` (on a 400 it can open the create form itself)."
|
|
981
|
+
},
|
|
982
|
+
"complexType": {
|
|
983
|
+
"original": "{ path: string; values: Record<string, unknown> }",
|
|
984
|
+
"resolved": "{ path: string; values: Record<string, unknown>; }",
|
|
985
|
+
"references": {
|
|
986
|
+
"Record": {
|
|
987
|
+
"location": "global",
|
|
988
|
+
"id": "global::Record"
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}
|
|
509
992
|
}];
|
|
510
993
|
}
|
|
511
994
|
static get methods() {
|
|
@@ -569,6 +1052,74 @@ export class MrdDocumentList {
|
|
|
569
1052
|
"text": "Inject the documents loaded for one folder node.",
|
|
570
1053
|
"tags": []
|
|
571
1054
|
}
|
|
1055
|
+
},
|
|
1056
|
+
"createFolder": {
|
|
1057
|
+
"complexType": {
|
|
1058
|
+
"signature": "() => Promise<void>",
|
|
1059
|
+
"parameters": [],
|
|
1060
|
+
"references": {
|
|
1061
|
+
"Promise": {
|
|
1062
|
+
"location": "global",
|
|
1063
|
+
"id": "global::Promise"
|
|
1064
|
+
}
|
|
1065
|
+
},
|
|
1066
|
+
"return": "Promise<void>"
|
|
1067
|
+
},
|
|
1068
|
+
"docs": {
|
|
1069
|
+
"text": "Start creating a folder under the current target. Called by the host toolbar\n(mrd-document-container). No-op when there is no valid target.",
|
|
1070
|
+
"tags": []
|
|
1071
|
+
}
|
|
1072
|
+
},
|
|
1073
|
+
"setFileReference": {
|
|
1074
|
+
"complexType": {
|
|
1075
|
+
"signature": "(uri: string) => Promise<void>",
|
|
1076
|
+
"parameters": [{
|
|
1077
|
+
"name": "uri",
|
|
1078
|
+
"type": "string",
|
|
1079
|
+
"docs": ""
|
|
1080
|
+
}],
|
|
1081
|
+
"references": {
|
|
1082
|
+
"Promise": {
|
|
1083
|
+
"location": "global",
|
|
1084
|
+
"id": "global::Promise"
|
|
1085
|
+
},
|
|
1086
|
+
"Record": {
|
|
1087
|
+
"location": "global",
|
|
1088
|
+
"id": "global::Record"
|
|
1089
|
+
},
|
|
1090
|
+
"DocRow": {
|
|
1091
|
+
"location": "local",
|
|
1092
|
+
"path": "/Users/michiel/Mosterd/mosterd-components/src/components/mrd-document-list/mrd-document-list.tsx",
|
|
1093
|
+
"id": "src/components/mrd-document-list/mrd-document-list.tsx::DocRow"
|
|
1094
|
+
}
|
|
1095
|
+
},
|
|
1096
|
+
"return": "Promise<void>"
|
|
1097
|
+
},
|
|
1098
|
+
"docs": {
|
|
1099
|
+
"text": "Host callback with the uploaded file reference. Builds the create payload\nfrom the archetype slots and requests the new document.",
|
|
1100
|
+
"tags": []
|
|
1101
|
+
}
|
|
1102
|
+
},
|
|
1103
|
+
"setCreatedHref": {
|
|
1104
|
+
"complexType": {
|
|
1105
|
+
"signature": "(href: string) => Promise<void>",
|
|
1106
|
+
"parameters": [{
|
|
1107
|
+
"name": "href",
|
|
1108
|
+
"type": "string",
|
|
1109
|
+
"docs": ""
|
|
1110
|
+
}],
|
|
1111
|
+
"references": {
|
|
1112
|
+
"Promise": {
|
|
1113
|
+
"location": "global",
|
|
1114
|
+
"id": "global::Promise"
|
|
1115
|
+
}
|
|
1116
|
+
},
|
|
1117
|
+
"return": "Promise<void>"
|
|
1118
|
+
},
|
|
1119
|
+
"docs": {
|
|
1120
|
+
"text": "Host callback with the created document's self href, so the optimistic row\nbecomes navigable without waiting for a reload.",
|
|
1121
|
+
"tags": []
|
|
1122
|
+
}
|
|
572
1123
|
}
|
|
573
1124
|
};
|
|
574
1125
|
}
|