@schukai/monster 4.67.0 → 4.69.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.
@@ -0,0 +1,2464 @@
1
+ /**
2
+ * Copyright © Volker Schukai and all contributing authors, {{copyRightYear}}. All rights reserved.
3
+ * Node module: @schukai/monster
4
+ *
5
+ * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
6
+ * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
7
+ *
8
+ * For those who do not wish to adhere to the AGPLv3, a commercial license is available.
9
+ * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
10
+ * For more information about purchasing a commercial license, please contact Volker Schukai.
11
+ *
12
+ * SPDX-License-Identifier: AGPL-3.0
13
+ */
14
+
15
+ import { instanceSymbol } from "../../constants.mjs";
16
+ import { internalSymbol } from "../../constants.mjs";
17
+ import {
18
+ assembleMethodSymbol,
19
+ CustomElement,
20
+ registerCustomElement,
21
+ } from "../../dom/customelement.mjs";
22
+ import { addAttributeToken } from "../../dom/attributes.mjs";
23
+ import { ATTRIBUTE_ERRORMESSAGE } from "../../dom/constants.mjs";
24
+ import {
25
+ findElementWithSelectorUpwards,
26
+ getDocument,
27
+ getWindow,
28
+ } from "../../dom/util.mjs";
29
+ import { Formatter } from "../../text/formatter.mjs";
30
+ import { diff } from "../../data/diff.mjs";
31
+ import { Pathfinder } from "../../data/pathfinder.mjs";
32
+ import { buildTree } from "../../data/buildtree.mjs";
33
+ import { ID } from "../../types/id.mjs";
34
+ import { NodeRecursiveIterator } from "../../types/noderecursiveiterator.mjs";
35
+ import { Observer } from "../../types/observer.mjs";
36
+ import { clone } from "../../util/clone.mjs";
37
+ import { isArray, isFunction, isObject, isString } from "../../types/is.mjs";
38
+ import { DeadMansSwitch } from "../../util/deadmansswitch.mjs";
39
+ import { CommonStyleSheet } from "../stylesheet/common.mjs";
40
+ import { ThemeStyleSheet } from "../stylesheet/theme.mjs";
41
+ import { FileManagerStyleSheet } from "./stylesheet/file-manager.mjs";
42
+ import "../content/image-editor.mjs";
43
+ import { ATTRIBUTE_BUTTON_LABEL } from "../form/constants.mjs";
44
+ import { getLocaleOfDocument } from "../../dom/locale.mjs";
45
+
46
+ import "../layout/split-panel.mjs";
47
+ import "../layout/panel.mjs";
48
+ import "../layout/tabs.mjs";
49
+ import { State } from "../state/state.mjs";
50
+ import "../tree-menu/tree-menu.mjs";
51
+ import "../form/message-state-button.mjs";
52
+ import "../datatable/datasource/rest.mjs";
53
+
54
+ export { FileManager };
55
+
56
+ void State;
57
+
58
+ /**
59
+ * @private
60
+ * @type {symbol}
61
+ */
62
+ const splitPanelSymbol = Symbol("splitPanel");
63
+
64
+ /**
65
+ * @private
66
+ * @type {symbol}
67
+ */
68
+ const treeMenuSymbol = Symbol("treeMenu");
69
+
70
+ /**
71
+ * @private
72
+ * @type {symbol}
73
+ */
74
+ const tabsSymbol = Symbol("tabs");
75
+ const tabsContainerSymbol = Symbol("tabsContainer");
76
+ const tabsResizeObserverSymbol = Symbol("tabsResizeObserver");
77
+ const hostResizeObserverSymbol = Symbol("hostResizeObserver");
78
+ const editorSizeRafSymbol = Symbol("editorSizeRaf");
79
+
80
+ /**
81
+ * @private
82
+ * @type {symbol}
83
+ */
84
+ const saveButtonSymbol = Symbol("saveButton");
85
+ const titleSymbol = Symbol("title");
86
+
87
+ /**
88
+ * @private
89
+ * @type {symbol}
90
+ */
91
+ const emptyStateSymbol = Symbol("emptyState");
92
+
93
+ /**
94
+ * @private
95
+ * @type {symbol}
96
+ */
97
+ const listDatasourceSymbol = Symbol("listDatasource");
98
+
99
+ /**
100
+ * @private
101
+ * @type {symbol}
102
+ */
103
+ const fileDatasourceSymbol = Symbol("fileDatasource");
104
+ const listDatasourceOwnedSymbol = Symbol("listDatasourceOwned");
105
+ const fileDatasourceOwnedSymbol = Symbol("fileDatasourceOwned");
106
+
107
+ /**
108
+ * @private
109
+ * @type {symbol}
110
+ */
111
+ const tabsByKeySymbol = Symbol("tabsByKey");
112
+
113
+ /**
114
+ * @private
115
+ * @type {symbol}
116
+ */
117
+ const tabsByReferenceSymbol = Symbol("tabsByReference");
118
+
119
+ /**
120
+ * @private
121
+ * @type {symbol}
122
+ */
123
+ const entryByValueSymbol = Symbol("entryByValue");
124
+ const treeEntriesSymbol = Symbol("treeEntries");
125
+ const loadedParentsSymbol = Symbol("loadedParents");
126
+ const listRequestParentSymbol = Symbol("listRequestParent");
127
+ const listRequestInFlightSymbol = Symbol("listRequestInFlight");
128
+ const queuedParentSymbol = Symbol("queuedParent");
129
+
130
+ /**
131
+ * @private
132
+ * @type {symbol}
133
+ */
134
+ const saveAllButtonSymbol = Symbol("saveAllButton");
135
+
136
+ /**
137
+ * A file manager for navigating and editing files.
138
+ *
139
+ * @fragments /fragments/components/files/file-manager/
140
+ *
141
+ * @since 4.44.0
142
+ * @summary A file manager control that builds a tree navigation from an API and opens files in tabs.
143
+ */
144
+ class FileManager extends CustomElement {
145
+ /**
146
+ * This method is called by the `instanceof` operator.
147
+ * @return {symbol}
148
+ */
149
+ static get [instanceSymbol]() {
150
+ return Symbol.for("@schukai/monster/components/files/file-manager");
151
+ }
152
+
153
+ /**
154
+ * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
155
+ * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
156
+ *
157
+ * @property {Object} templates Template definitions
158
+ * @property {string} templates.main Main template
159
+ * @property {Object} labels Label definitions
160
+ * @property {string} labels.title Title label
161
+ * @property {string} labels.save Save button label
162
+ * @property {string} labels.empty Empty state label
163
+ * @property {Object} layout Layout configuration
164
+ * @property {Object} layout.split Split panel options
165
+ * @property {Object} mapping Mapping for list and content
166
+ * @property {string} mapping.data Path to list data in the datasource response
167
+ * @property {string} mapping.id Template or key for entry id
168
+ * @property {string} mapping.parent Template or key for entry parent
169
+ * @property {string} mapping.label Template or key for entry label
170
+ * @property {string} mapping.value Template or key for entry value
171
+ * @property {string} mapping.path Template or key for entry path
172
+ * @property {string} mapping.type Template or key for entry type
173
+ * @property {string} mapping.icon Template or key for entry icon id
174
+ * @property {string} mapping.contentPath Path to content in file read response
175
+ * @property {Object} datasource Datasource definitions
176
+ * @property {Object} datasource.list List datasource options
177
+ * @property {string} datasource.list.selector Selector for existing datasource
178
+ * @property {Object} datasource.list.rest Options for monster-datasource-rest
179
+ * @property {Object} datasource.file File datasource options
180
+ * @property {string} datasource.file.selector Selector for existing datasource
181
+ * @property {Object} datasource.file.rest Options for monster-datasource-rest
182
+ * @property {Object} editor Editor configuration
183
+ * @property {string} editor.tag Tag name for editor element
184
+ * @property {Object} editor.adapter Adapter with getValue/setValue/bindChange
185
+ * @property {Object} tabs Tabs configuration
186
+ * @property {string} tabs.dirtyMarker Marker appended to tab labels
187
+ * @property {Object} icons Icon mapping configuration
188
+ */
189
+ get defaults() {
190
+ return Object.assign({}, super.defaults, {
191
+ templates: {
192
+ main: getTemplate(),
193
+ },
194
+ labels: getTranslations(),
195
+ layout: {
196
+ split: {
197
+ type: "vertical",
198
+ initial: "20%",
199
+ min: "15%",
200
+ max: "45%",
201
+ },
202
+ },
203
+ mapping: {
204
+ data: "dataset",
205
+ id: "id",
206
+ parent: "parent",
207
+ label: "${name}",
208
+ value: "${path}",
209
+ path: "${path}",
210
+ type: "${type}",
211
+ icon: null,
212
+ contentPath: "content",
213
+ },
214
+ datasource: {
215
+ list: {
216
+ selector: null,
217
+ rest: {
218
+ read: {
219
+ url: null,
220
+ },
221
+ },
222
+ },
223
+ file: {
224
+ selector: null,
225
+ rest: {
226
+ read: {
227
+ url: null,
228
+ },
229
+ write: {
230
+ url: null,
231
+ },
232
+ },
233
+ },
234
+ },
235
+ editor: {
236
+ readOnly: false,
237
+ defaultTag: "textarea",
238
+ byMimeType: {
239
+ "image/png": "monster-image-editor",
240
+ "image/jpeg": "monster-image-editor",
241
+ "image/gif": "monster-image-editor",
242
+ "image/svg+xml": "monster-image-editor",
243
+ "image/webp": "monster-image-editor",
244
+ "image/bmp": "monster-image-editor",
245
+ "image/x-icon": "monster-image-editor",
246
+ "text/plain": "textarea",
247
+ "text/markdown": "textarea",
248
+ "text/css": "textarea",
249
+ "text/html": "textarea",
250
+ "text/javascript": "textarea",
251
+ "application/json": "textarea",
252
+ },
253
+ },
254
+ tabs: {
255
+ dirtyMarker: "*",
256
+ },
257
+ persistence: {
258
+ enabled: true,
259
+ keyPrefix: "monster-file-manager-diff",
260
+ debounceMs: 300,
261
+ },
262
+ lazy: {
263
+ enabled: false,
264
+ parameter: "path",
265
+ root: null,
266
+ },
267
+ icons: {
268
+ default: "monster-file-icon-default",
269
+ code: "monster-file-icon-code",
270
+ image: "monster-file-icon-image",
271
+ sound: "monster-file-icon-sound",
272
+ pdf: "monster-file-icon-pdf",
273
+ css: "monster-file-icon-css",
274
+ csv: "monster-file-icon-csv",
275
+ doc: "monster-file-icon-doc",
276
+ jpg: "monster-file-icon-jpg",
277
+ html: "monster-file-icon-html",
278
+ gif: "monster-file-icon-gif",
279
+ mp4: "monster-file-icon-mp4",
280
+ mov: "monster-file-icon-mov",
281
+ mp3: "monster-file-icon-mp3",
282
+ txt: "monster-file-icon-txt",
283
+ png: "monster-file-icon-png",
284
+ byExtension: {
285
+ js: "code",
286
+ ts: "code",
287
+ json: "code",
288
+ css: "css",
289
+ scss: "css",
290
+ html: "html",
291
+ htm: "html",
292
+ txt: "txt",
293
+ md: "txt",
294
+ csv: "csv",
295
+ pdf: "pdf",
296
+ png: "png",
297
+ jpg: "jpg",
298
+ jpeg: "jpg",
299
+ gif: "gif",
300
+ svg: "image",
301
+ mp3: "mp3",
302
+ wav: "sound",
303
+ ogg: "sound",
304
+ mp4: "mp4",
305
+ m4p: "mp4",
306
+ mov: "mov",
307
+ },
308
+ },
309
+ debug: false,
310
+ });
311
+ }
312
+
313
+ /**
314
+ * @return {CSSStyleSheet[]}
315
+ */
316
+ static getCSSStyleSheet() {
317
+ return [CommonStyleSheet, ThemeStyleSheet, FileManagerStyleSheet];
318
+ }
319
+
320
+ /**
321
+ * @return {string}
322
+ */
323
+ static getTag() {
324
+ return "monster-file-manager";
325
+ }
326
+
327
+ /**
328
+ * @param {object} options
329
+ * @return {FileManager}
330
+ */
331
+ setOptions(options) {
332
+ super.setOptions(options);
333
+ queueMicrotask(() => {
334
+ syncDatasources.call(this);
335
+ refreshLabels.call(this);
336
+ });
337
+ return this;
338
+ }
339
+
340
+ /**
341
+ * @return {void}
342
+ */
343
+ [assembleMethodSymbol]() {
344
+ super[assembleMethodSymbol]();
345
+
346
+ this[tabsByKeySymbol] = new Map();
347
+ this[tabsByReferenceSymbol] = new Map();
348
+ this[entryByValueSymbol] = new Map();
349
+ this[treeEntriesSymbol] = [];
350
+ this[loadedParentsSymbol] = new Set();
351
+
352
+ queueMicrotask(() => {
353
+ initControlReferences.call(this);
354
+ initSplitPanel.call(this);
355
+ initTabs.call(this);
356
+ initSaveButton.call(this);
357
+ initTreeMenu.call(this);
358
+ initDatasources.call(this);
359
+ });
360
+
361
+ this[internalSymbol].attachObserver(
362
+ new Observer(() => {
363
+ syncDatasources.call(this);
364
+ refreshLabels.call(this);
365
+ }),
366
+ );
367
+ }
368
+ }
369
+
370
+ /**
371
+ * @private
372
+ * @return {FileManager}
373
+ */
374
+ function initControlReferences() {
375
+ if (!this.shadowRoot) {
376
+ throw new Error("no shadow-root is defined");
377
+ }
378
+
379
+ this[titleSymbol] = this.shadowRoot.querySelector(
380
+ "[data-monster-role=title]",
381
+ );
382
+ this[splitPanelSymbol] = this.shadowRoot.querySelector(
383
+ "[data-monster-role=split-panel]",
384
+ );
385
+ this[treeMenuSymbol] = this.shadowRoot.querySelector(
386
+ "[data-monster-role=tree]",
387
+ );
388
+ this[tabsSymbol] = this.shadowRoot.querySelector("[data-monster-role=tabs]");
389
+ this[tabsContainerSymbol] = this.shadowRoot.querySelector(
390
+ "[data-monster-role=tabs-container]",
391
+ );
392
+ this[saveButtonSymbol] = this.shadowRoot.querySelector(
393
+ "[data-monster-role=save-button]",
394
+ );
395
+ this[saveAllButtonSymbol] = this.shadowRoot.querySelector(
396
+ "[data-monster-role=save-all-button]",
397
+ );
398
+ this[emptyStateSymbol] = this.shadowRoot.querySelector(
399
+ "[data-monster-role=empty]",
400
+ );
401
+
402
+ return this;
403
+ }
404
+
405
+ /**
406
+ * @private
407
+ * @return {void}
408
+ */
409
+ function initSplitPanel() {
410
+ if (!this[splitPanelSymbol]) {
411
+ return;
412
+ }
413
+
414
+ const split = this.getOption("layout.split", {});
415
+ const initial = split.initial || "20%";
416
+ this[splitPanelSymbol].setOptions({
417
+ splitType: split.type || "vertical",
418
+ dimension: {
419
+ initial: initial,
420
+ min: split.min || "15%",
421
+ max: split.max || "45%",
422
+ },
423
+ });
424
+ this[splitPanelSymbol].setDimension(initial);
425
+ }
426
+
427
+ /**
428
+ * @private
429
+ * @return {void}
430
+ */
431
+ function initSaveButton() {
432
+ if (!this[saveButtonSymbol]) {
433
+ return;
434
+ }
435
+
436
+ this[saveButtonSymbol].setOption(
437
+ "labels.button",
438
+ this.getOption("labels.save"),
439
+ );
440
+ this[saveButtonSymbol].setOption("disabled", true);
441
+
442
+ this[saveButtonSymbol].setOption("actions.click", () => {
443
+ saveActiveTab.call(this);
444
+ });
445
+
446
+ if (this[saveAllButtonSymbol]) {
447
+ this[saveAllButtonSymbol].setOption(
448
+ "labels.button",
449
+ this.getOption("labels.saveAll"),
450
+ );
451
+ this[saveAllButtonSymbol].setOption("disabled", true);
452
+ this[saveAllButtonSymbol].setOption("actions.click", () => {
453
+ saveAllTabs.call(this);
454
+ });
455
+ }
456
+ }
457
+
458
+ /**
459
+ * @private
460
+ * @return {void}
461
+ */
462
+ function initTabs() {
463
+ if (!this[tabsSymbol]) {
464
+ return;
465
+ }
466
+
467
+ this[tabsSymbol].setOption("debug", this.getOption("debug") === true);
468
+ this[tabsSymbol].setOption("features.openFirst", false);
469
+
470
+ this[tabsSymbol].addEventListener("monster-tab-changed", (event) => {
471
+ const reference = event?.detail?.reference;
472
+ if (!reference) {
473
+ return;
474
+ }
475
+
476
+ updateEmptyState.call(this);
477
+ updateSaveButtonState.call(this);
478
+ scheduleUpdateEditorSizes.call(this);
479
+ });
480
+
481
+ this[tabsSymbol].addEventListener("monster-tab-remove", (event) => {
482
+ const reference = event?.detail?.reference;
483
+ if (!reference) {
484
+ return;
485
+ }
486
+
487
+ const tabData = this[tabsByReferenceSymbol].get(reference);
488
+ if (tabData?.unbindChange) {
489
+ tabData.unbindChange();
490
+ }
491
+
492
+ this[tabsByReferenceSymbol].delete(reference);
493
+ if (tabData?.key) {
494
+ this[tabsByKeySymbol].delete(tabData.key);
495
+ }
496
+
497
+ updateEmptyState.call(this);
498
+ updateSaveButtonState.call(this);
499
+ scheduleUpdateEditorSizes.call(this);
500
+ });
501
+
502
+ initHostResizeObserver.call(this);
503
+ scheduleUpdateEditorSizes.call(this);
504
+ }
505
+
506
+ /**
507
+ * @private
508
+ * @return {void}
509
+ */
510
+ function initTreeMenu() {
511
+ if (!this[treeMenuSymbol]) {
512
+ return;
513
+ }
514
+
515
+ this[treeMenuSymbol].setOptions({
516
+ mapping: getTreeMenuMapping.call(this),
517
+ actions: {
518
+ select: (entry) => {
519
+ const key = entry?.value;
520
+ if (!key) {
521
+ return;
522
+ }
523
+
524
+ const fileEntry = this[entryByValueSymbol].get(key);
525
+ if (!fileEntry) {
526
+ return;
527
+ }
528
+
529
+ openFile.call(this, fileEntry);
530
+ },
531
+ open: (entry) => {
532
+ handleTreeOpen.call(this, entry);
533
+ },
534
+ },
535
+ });
536
+ }
537
+
538
+ /**
539
+ * @private
540
+ * @return {void}
541
+ */
542
+ function initDatasources() {
543
+ initListDatasource.call(this);
544
+ initFileDatasource.call(this);
545
+ }
546
+
547
+ /**
548
+ * @private
549
+ * @return {void}
550
+ */
551
+ function initListDatasource() {
552
+ const selector = this.getOption("datasource.list.selector");
553
+ if (isString(selector) && selector.trim() !== "") {
554
+ const element = findElementWithSelectorUpwards(this, selector.trim());
555
+ if (element instanceof HTMLElement) {
556
+ customElements
557
+ .whenDefined(element.tagName.toLowerCase())
558
+ .then(() => {
559
+ logDebug.call(this, "list datasource found via selector", selector);
560
+ this[listDatasourceSymbol] = element;
561
+ attachListDatasourceListeners.call(this);
562
+ requestList.call(this, this.getOption("lazy.root", null));
563
+ })
564
+ .catch((err) => {
565
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, err.message);
566
+ });
567
+ return;
568
+ }
569
+
570
+ addAttributeToken(
571
+ this,
572
+ ATTRIBUTE_ERRORMESSAGE,
573
+ "list datasource selector not found",
574
+ );
575
+ }
576
+
577
+ const restConfig = this.getOption("datasource.list.rest", {});
578
+ if (!isObject(restConfig)) {
579
+ return;
580
+ }
581
+
582
+ const rest = getDocument().createElement("monster-datasource-rest");
583
+ rest.setOptions(restConfig);
584
+ rest.setOption("features.autoInit", true);
585
+ rest.setOption("autoInit.oneTime", true);
586
+ this.appendChild(rest);
587
+ this[listDatasourceSymbol] = rest;
588
+ this[listDatasourceOwnedSymbol] = true;
589
+ logDebug.call(this, "list datasource created", restConfig?.read?.url);
590
+
591
+ attachListDatasourceListeners.call(this);
592
+ requestList.call(this, this.getOption("lazy.root", null));
593
+ }
594
+
595
+ /**
596
+ * @private
597
+ * @return {void}
598
+ */
599
+ function initFileDatasource() {
600
+ const selector = this.getOption("datasource.file.selector");
601
+ if (isString(selector) && selector.trim() !== "") {
602
+ const element = findElementWithSelectorUpwards(this, selector.trim());
603
+ if (element instanceof HTMLElement) {
604
+ customElements
605
+ .whenDefined(element.tagName.toLowerCase())
606
+ .then(() => {
607
+ this[fileDatasourceSymbol] = element;
608
+ })
609
+ .catch((err) => {
610
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, err.message);
611
+ });
612
+ return;
613
+ }
614
+
615
+ addAttributeToken(
616
+ this,
617
+ ATTRIBUTE_ERRORMESSAGE,
618
+ "file datasource selector not found",
619
+ );
620
+ }
621
+
622
+ const restConfig = this.getOption("datasource.file.rest", {});
623
+ if (!isObject(restConfig)) {
624
+ return;
625
+ }
626
+
627
+ const rest = getDocument().createElement("monster-datasource-rest");
628
+ rest.setOptions(restConfig);
629
+ this.appendChild(rest);
630
+ this[fileDatasourceSymbol] = rest;
631
+ this[fileDatasourceOwnedSymbol] = true;
632
+ }
633
+
634
+ /**
635
+ * @private
636
+ * @return {void}
637
+ */
638
+ function attachListDatasourceListeners() {
639
+ const datasource = this[listDatasourceSymbol];
640
+ if (!(datasource instanceof HTMLElement)) {
641
+ return;
642
+ }
643
+
644
+ const hasObserver = !!datasource.datasource?.attachObserver;
645
+ if (!hasObserver) {
646
+ datasource.addEventListener("monster-datasource-fetched", () => {
647
+ logDebug.call(this, "list datasource fetched");
648
+ applyListData.call(this);
649
+ });
650
+ }
651
+ datasource.addEventListener("monster-datasource-error", (event) => {
652
+ logDebug.call(this, "list datasource error", event?.detail?.error?.message);
653
+ clearListRequest.call(this);
654
+ });
655
+
656
+ if (datasource.datasource && hasObserver) {
657
+ datasource.datasource.attachObserver(
658
+ new Observer(() => {
659
+ logDebug.call(this, "list datasource fetched");
660
+ applyListData.call(this);
661
+ }),
662
+ );
663
+ }
664
+ }
665
+
666
+ /**
667
+ * @private
668
+ * @return {void}
669
+ */
670
+ function applyListData() {
671
+ const datasource = this[listDatasourceSymbol];
672
+ if (!datasource || !datasource.data) {
673
+ logDebug.call(this, "list datasource has no data");
674
+ return;
675
+ }
676
+
677
+ let data = datasource.data;
678
+ const dataPath = this.getOption("mapping.data");
679
+
680
+ if (isString(dataPath) && dataPath.trim() !== "") {
681
+ const pathfinder = new Pathfinder(data);
682
+ if (pathfinder.exists(dataPath)) {
683
+ data = pathfinder.getVia(dataPath);
684
+ } else {
685
+ logDebug.call(
686
+ this,
687
+ "list datasource path not found, falling back to root data",
688
+ dataPath,
689
+ );
690
+ }
691
+ }
692
+
693
+ if (isObject(data)) {
694
+ data = Object.values(data);
695
+ }
696
+
697
+ if (!isArray(data)) {
698
+ data = [];
699
+ }
700
+
701
+ const lazyEnabled = this.getOption("lazy.enabled", false) === true;
702
+ const listParent = lazyEnabled
703
+ ? (this[listRequestParentSymbol] ?? this.getOption("lazy.root", null))
704
+ : null;
705
+
706
+ const entries = [];
707
+ const entryByValue = new Map();
708
+
709
+ for (const entry of data) {
710
+ const normalized = normalizeEntry.call(this, entry);
711
+ if (lazyEnabled) {
712
+ if (!matchesParent(normalized.parent, listParent)) {
713
+ continue;
714
+ }
715
+ }
716
+ entries.push(normalized);
717
+ entryByValue.set(normalized.value, normalized);
718
+ }
719
+
720
+ logDebug.call(
721
+ this,
722
+ "list entries normalized",
723
+ lazyEnabled
724
+ ? `${entries.length} (parent ${listParent ?? "root"})`
725
+ : entries.length,
726
+ );
727
+
728
+ if (lazyEnabled) {
729
+ mergeLazyEntries.call(this, entries, listParent);
730
+ } else {
731
+ this[entryByValueSymbol] = entryByValue;
732
+ const treeEntries = buildTreeEntries.call(this, entries);
733
+ this[treeEntriesSymbol] = treeEntries;
734
+ if (this[treeMenuSymbol]) {
735
+ this[treeMenuSymbol].setOption("entries", treeEntries);
736
+ }
737
+ }
738
+
739
+ clearListRequest.call(this);
740
+ updateEmptyState.call(this);
741
+ }
742
+
743
+ /**
744
+ * @private
745
+ * @param {object} entry
746
+ * @return {object}
747
+ */
748
+ function normalizeEntry(entry) {
749
+ const mapping = this.getOption("mapping", {});
750
+ const formatted = {
751
+ id: formatEntryValue(entry, mapping.id),
752
+ parent: formatEntryValue(entry, mapping.parent),
753
+ label: formatEntryValue(entry, mapping.label),
754
+ value: formatEntryValue(entry, mapping.value),
755
+ path: formatEntryValue(entry, mapping.path),
756
+ type: formatEntryValue(entry, mapping.type),
757
+ icon: formatEntryValue(entry, mapping.icon),
758
+ hasChildren:
759
+ entry?.hasChildren === true ||
760
+ entry?.["has-children"] === true ||
761
+ entry?.has_children === true,
762
+ raw: entry,
763
+ };
764
+
765
+ if (!formatted.path && isString(formatted.value)) {
766
+ formatted.path = formatted.value;
767
+ }
768
+
769
+ if (!formatted.value && isString(formatted.path)) {
770
+ formatted.value = formatted.path;
771
+ }
772
+
773
+ if (!formatted.id && isString(formatted.path)) {
774
+ formatted.id = formatted.path;
775
+ }
776
+
777
+ if (!formatted.parent && isString(formatted.path)) {
778
+ const parent = formatted.path.split("/").slice(0, -1).join("/");
779
+ formatted.parent = parent === "" ? null : parent;
780
+ }
781
+
782
+ if (!formatted.label) {
783
+ formatted.label = formatted.path || formatted.id || "file";
784
+ }
785
+
786
+ if (!formatted.type && isString(formatted.path)) {
787
+ formatted.type = getTypeFromPath(formatted.path);
788
+ }
789
+
790
+ if (formatted.hasChildren !== true) {
791
+ formatted.hasChildren = formatted.type === "folder";
792
+ }
793
+
794
+ if (!formatted.icon) {
795
+ formatted.icon = getIconIdForEntry.call(this, formatted);
796
+ }
797
+
798
+ return formatted;
799
+ }
800
+
801
+ /**
802
+ * @private
803
+ * @param {object} entry
804
+ * @return {string}
805
+ */
806
+ function getIconIdForEntry(entry) {
807
+ const icons = this.getOption("icons", {});
808
+ const byExtension = icons.byExtension || {};
809
+ const type = isString(entry.type) ? entry.type.toLowerCase() : "";
810
+ const ext = getExtension(entry.path || "");
811
+ let iconKey = "";
812
+
813
+ if (ext && byExtension[ext]) {
814
+ iconKey = byExtension[ext];
815
+ } else if (type && icons[type]) {
816
+ iconKey = type;
817
+ }
818
+
819
+ if (!iconKey || !icons[iconKey]) {
820
+ return icons.default;
821
+ }
822
+
823
+ return icons[iconKey];
824
+ }
825
+
826
+ /**
827
+ * @private
828
+ * @param {string} path
829
+ * @return {string}
830
+ */
831
+ function getTypeFromPath(path) {
832
+ const ext = getExtension(path);
833
+ if (!ext) {
834
+ return "file";
835
+ }
836
+ return ext.toLowerCase();
837
+ }
838
+
839
+ /**
840
+ * @private
841
+ * @param {string} path
842
+ * @return {string}
843
+ */
844
+ function getExtension(path) {
845
+ if (!isString(path)) {
846
+ return "";
847
+ }
848
+
849
+ const last = path.split("/").pop() || "";
850
+ const index = last.lastIndexOf(".");
851
+ if (index === -1) {
852
+ return "";
853
+ }
854
+
855
+ return last.substring(index + 1).toLowerCase();
856
+ }
857
+
858
+ /**
859
+ * @private
860
+ * @param {object} entry
861
+ * @param {string} template
862
+ * @return {string}
863
+ */
864
+ function formatEntryValue(entry, template) {
865
+ if (!isString(template) || template.trim() === "") {
866
+ return "";
867
+ }
868
+
869
+ if (template.includes("${")) {
870
+ try {
871
+ return new Formatter(entry).format(template);
872
+ } catch (e) {
873
+ return "";
874
+ }
875
+ }
876
+
877
+ if (entry && Object.prototype.hasOwnProperty.call(entry, template)) {
878
+ return entry[template];
879
+ }
880
+
881
+ return "";
882
+ }
883
+
884
+ /**
885
+ * @private
886
+ * @return {object}
887
+ */
888
+ function getTreeMenuMapping() {
889
+ return {
890
+ rootReferences: [null, undefined, 0, "0", ""],
891
+ idTemplate: "id",
892
+ parentKey: "parent",
893
+ selector: "*",
894
+ labelTemplate: "${label}",
895
+ valueTemplate: "${value}",
896
+ iconTemplate: "${icon}",
897
+ };
898
+ }
899
+
900
+ /**
901
+ * @private
902
+ * @param {object} entry
903
+ * @return {void}
904
+ */
905
+ function handleTreeOpen(entry) {
906
+ const lazyEnabled = this.getOption("lazy.enabled", false) === true;
907
+ if (!lazyEnabled) {
908
+ return;
909
+ }
910
+ const key = entry?.value;
911
+ if (!key) {
912
+ return;
913
+ }
914
+ const normalized = this[entryByValueSymbol].get(key);
915
+ if (!normalized || normalized.type !== "folder") {
916
+ return;
917
+ }
918
+ if (normalized.hasChildren !== true) {
919
+ return;
920
+ }
921
+
922
+ if (this[loadedParentsSymbol].has(normalized.path)) {
923
+ return;
924
+ }
925
+
926
+ requestList.call(this, normalized.path);
927
+ }
928
+
929
+ /**
930
+ * @private
931
+ * @param {Array} entries
932
+ * @return {Array}
933
+ */
934
+ function buildTreeEntries(entries) {
935
+ const nodes = buildTree(entries, "*", "id", "parent", {
936
+ rootReferences: [null, undefined, ""],
937
+ });
938
+
939
+ const options = [];
940
+ for (const node of nodes) {
941
+ const iterator = new NodeRecursiveIterator(node);
942
+ for (const n of iterator) {
943
+ const entry = n.value || {};
944
+ const intend = n.level;
945
+ const visibility = intend > 0 ? "hidden" : "visible";
946
+ options.push({
947
+ value: entry.value,
948
+ label: entry.label,
949
+ icon: buildIconMarkup(entry.icon),
950
+ intend,
951
+ state: "close",
952
+ visibility,
953
+ ["has-children"]: n.hasChildNodes(),
954
+ });
955
+ }
956
+ }
957
+
958
+ return options;
959
+ }
960
+
961
+ /**
962
+ * @private
963
+ * @param {string} iconId
964
+ * @return {string}
965
+ */
966
+ function buildIconMarkup(iconId) {
967
+ const id = isString(iconId) ? iconId : "";
968
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><use xlink:href="#${id}"></use></svg>`;
969
+ }
970
+
971
+ /**
972
+ * @private
973
+ * @param {Array} entries
974
+ * @param {string|null} parentValue
975
+ * @return {void}
976
+ */
977
+ function mergeLazyEntries(entries, parentValue) {
978
+ let treeEntries = Array.isArray(this[treeEntriesSymbol])
979
+ ? this[treeEntriesSymbol].slice()
980
+ : [];
981
+ const loadedParents = this[loadedParentsSymbol] || new Set();
982
+
983
+ if (parentValue === null || parentValue === undefined || parentValue === "") {
984
+ treeEntries = [];
985
+ this[entryByValueSymbol] = new Map();
986
+ loadedParents.clear();
987
+ }
988
+
989
+ const parentIndex = findTreeEntryIndex(treeEntries, parentValue);
990
+ const parentIntend = parentIndex >= 0 ? treeEntries[parentIndex].intend : -1;
991
+ const parentState =
992
+ parentIndex >= 0 ? treeEntries[parentIndex].state : "open";
993
+ const baseVisibility = parentState === "open" ? "visible" : "hidden";
994
+
995
+ const insertIndex =
996
+ parentIndex >= 0
997
+ ? getSubtreeEndIndex(treeEntries, parentIndex)
998
+ : treeEntries.length;
999
+
1000
+ const newEntries = [];
1001
+ let hasChildren = entries.length > 0;
1002
+
1003
+ for (const entry of entries) {
1004
+ if (!entry?.value) {
1005
+ continue;
1006
+ }
1007
+ if (this[entryByValueSymbol].has(entry.value)) {
1008
+ continue;
1009
+ }
1010
+
1011
+ this[entryByValueSymbol].set(entry.value, entry);
1012
+
1013
+ newEntries.push({
1014
+ value: entry.value,
1015
+ label: entry.label,
1016
+ icon: buildIconMarkup(entry.icon),
1017
+ intend: parentIntend + 1,
1018
+ state: "close",
1019
+ visibility: parentIndex >= 0 ? baseVisibility : "visible",
1020
+ ["has-children"]: entry.hasChildren === true,
1021
+ });
1022
+ }
1023
+
1024
+ const nextEntries = [
1025
+ ...treeEntries.slice(0, insertIndex),
1026
+ ...newEntries,
1027
+ ...treeEntries.slice(insertIndex),
1028
+ ];
1029
+
1030
+ if (parentIndex >= 0) {
1031
+ nextEntries[parentIndex] = Object.assign({}, nextEntries[parentIndex], {
1032
+ ["has-children"]: hasChildren,
1033
+ state: hasChildren ? nextEntries[parentIndex].state : "close",
1034
+ });
1035
+ }
1036
+
1037
+ this[treeEntriesSymbol] = nextEntries;
1038
+ loadedParents.add(parentValue ?? null);
1039
+ this[loadedParentsSymbol] = loadedParents;
1040
+
1041
+ if (this[treeMenuSymbol]) {
1042
+ this[treeMenuSymbol].setOption("entries", nextEntries);
1043
+ }
1044
+ }
1045
+
1046
+ /**
1047
+ * @private
1048
+ * @param {Array} entries
1049
+ * @param {string|null} value
1050
+ * @return {number}
1051
+ */
1052
+ function findTreeEntryIndex(entries, value) {
1053
+ if (!isString(value) || value.trim() === "") {
1054
+ return -1;
1055
+ }
1056
+ return entries.findIndex((entry) => entry.value === value);
1057
+ }
1058
+
1059
+ /**
1060
+ * @private
1061
+ * @param {Array} entries
1062
+ * @param {number} index
1063
+ * @return {number}
1064
+ */
1065
+ function getSubtreeEndIndex(entries, index) {
1066
+ if (index < 0 || index >= entries.length) {
1067
+ return entries.length;
1068
+ }
1069
+ const baseIntend = entries[index].intend;
1070
+ let i = index + 1;
1071
+ while (i < entries.length && entries[i].intend > baseIntend) {
1072
+ i += 1;
1073
+ }
1074
+ return i;
1075
+ }
1076
+
1077
+ /**
1078
+ * @private
1079
+ * @param {string|null|undefined} entryParent
1080
+ * @param {string|null|undefined} expectedParent
1081
+ * @return {boolean}
1082
+ */
1083
+ function matchesParent(entryParent, expectedParent) {
1084
+ const normalized = entryParent ?? null;
1085
+ const expected = expectedParent ?? null;
1086
+ if (expected === null) {
1087
+ return normalized === null || normalized === "" || normalized === undefined;
1088
+ }
1089
+ return normalized === expected;
1090
+ }
1091
+
1092
+ /**
1093
+ * @private
1094
+ * @param {string|null} parentValue
1095
+ * @return {void}
1096
+ */
1097
+ function requestList(parentValue) {
1098
+ const datasource = this[listDatasourceSymbol];
1099
+ if (!datasource?.read) {
1100
+ return;
1101
+ }
1102
+
1103
+ const isRestDatasource =
1104
+ datasource?.tagName?.toLowerCase?.() === "monster-datasource-rest";
1105
+ if (isRestDatasource) {
1106
+ const readUrl = getListReadUrl.call(this, parentValue);
1107
+ if (!isString(readUrl) || readUrl.trim() === "") {
1108
+ logDebug.call(this, "list datasource read url missing");
1109
+ return;
1110
+ }
1111
+ datasource.setOption("read.url", readUrl);
1112
+ }
1113
+
1114
+ if (this[listRequestInFlightSymbol] === true) {
1115
+ this[queuedParentSymbol] = parentValue;
1116
+ return;
1117
+ }
1118
+
1119
+ const paramName = this.getOption("lazy.parameter", "path");
1120
+ const params = {};
1121
+ if (isString(paramName) && paramName.trim() !== "") {
1122
+ params[paramName] = parentValue ?? "";
1123
+ }
1124
+
1125
+ this[listRequestInFlightSymbol] = true;
1126
+ this[listRequestParentSymbol] = parentValue ?? null;
1127
+ logDebug.call(this, "list request", parentValue ?? "root");
1128
+ datasource.setOption("read.parameters", params);
1129
+ datasource.read();
1130
+ }
1131
+
1132
+ /**
1133
+ * @private
1134
+ * @param {string|null} parentValue
1135
+ * @return {string|null}
1136
+ */
1137
+ function getListReadUrl(parentValue) {
1138
+ const baseUrl = this.getOption("datasource.list.rest.read.url");
1139
+ const fallbackUrl = this[listDatasourceSymbol]?.getOption?.("read.url");
1140
+ const url =
1141
+ isString(baseUrl) && baseUrl.trim() !== "" ? baseUrl : fallbackUrl;
1142
+
1143
+ if (!isString(url) || url.trim() === "") {
1144
+ return null;
1145
+ }
1146
+
1147
+ const trimmed = url.trim();
1148
+ if (trimmed.includes("${")) {
1149
+ return trimmed;
1150
+ }
1151
+
1152
+ const lazyEnabled = this.getOption("lazy.enabled", false) === true;
1153
+ if (!lazyEnabled) {
1154
+ return trimmed;
1155
+ }
1156
+
1157
+ const paramName = this.getOption("lazy.parameter", "path");
1158
+ if (!isString(paramName) || paramName.trim() === "") {
1159
+ return trimmed;
1160
+ }
1161
+
1162
+ const encoded = encodeURIComponent(parentValue ?? "");
1163
+ const escaped = escapeRegExp(paramName);
1164
+ const hasParam = new RegExp(`([?&])${escaped}=`).test(trimmed);
1165
+ const glue = trimmed.includes("?") ? "&" : "?";
1166
+
1167
+ if (hasParam) {
1168
+ return trimmed.replace(
1169
+ new RegExp(`([?&])${escaped}=[^&]*`),
1170
+ `$1${paramName}=${encoded}`,
1171
+ );
1172
+ }
1173
+
1174
+ return `${trimmed}${glue}${paramName}=${encoded}`;
1175
+ }
1176
+
1177
+ /**
1178
+ * @private
1179
+ * @param {string} value
1180
+ * @return {string}
1181
+ */
1182
+ function escapeRegExp(value) {
1183
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1184
+ }
1185
+
1186
+ /**
1187
+ * @private
1188
+ * @return {void}
1189
+ */
1190
+ function clearListRequest() {
1191
+ this[listRequestInFlightSymbol] = false;
1192
+ this[listRequestParentSymbol] = null;
1193
+ const queued = this[queuedParentSymbol];
1194
+ this[queuedParentSymbol] = null;
1195
+ if (queued !== null && queued !== undefined) {
1196
+ requestList.call(this, queued);
1197
+ }
1198
+ }
1199
+
1200
+ /**
1201
+ * @private
1202
+ * @param {object} entry
1203
+ * @return {void}
1204
+ */
1205
+ function openFile(entry) {
1206
+ if (entry?.type === "folder") {
1207
+ return;
1208
+ }
1209
+ const key = entry.value || entry.id;
1210
+ if (!key) {
1211
+ return;
1212
+ }
1213
+
1214
+ const existing = this[tabsByKeySymbol].get(key);
1215
+ if (existing) {
1216
+ this[tabsSymbol]?.activeTab(existing.reference);
1217
+ return;
1218
+ }
1219
+
1220
+ const editor = createEditorElement.call(this, entry);
1221
+ editor.setAttribute("data-monster-role", "editor");
1222
+
1223
+ const tabReference = new ID("file-tab").toString();
1224
+ this[tabsSymbol]?.addTab(editor, {
1225
+ tabId: tabReference,
1226
+ label: entry.label,
1227
+ active: true,
1228
+ removable: true,
1229
+ });
1230
+
1231
+ const tabElement = this[tabsSymbol]?.querySelector(
1232
+ `#${CSS.escape(tabReference)}`,
1233
+ );
1234
+ if (!(tabElement instanceof HTMLElement)) {
1235
+ throw new Error("tab element not found");
1236
+ }
1237
+ tabElement.setAttribute("data-monster-role", "tab-content");
1238
+
1239
+ const adapter = getEditorAdapter.call(this);
1240
+ adapter.setReadOnly(editor, this.getOption("editor.readOnly", false));
1241
+ const tabData = {
1242
+ key,
1243
+ reference: tabReference,
1244
+ entry: entry,
1245
+ editor: editor,
1246
+ adapter,
1247
+ original: "",
1248
+ dirty: false,
1249
+ tabElement,
1250
+ label: entry.label,
1251
+ unbindChange: null,
1252
+ };
1253
+
1254
+ tabData.unbindChange = adapter.bindChange(editor, () => {
1255
+ handleEditorChange.call(this, tabData);
1256
+ });
1257
+
1258
+ this[tabsByKeySymbol].set(key, tabData);
1259
+ this[tabsByReferenceSymbol].set(tabReference, tabData);
1260
+
1261
+ this[tabsSymbol]?.activeTab(tabReference);
1262
+ updateEmptyState.call(this);
1263
+ updateSaveButtonState.call(this);
1264
+ scheduleUpdateEditorSizes.call(this);
1265
+
1266
+ loadFileContent.call(this, tabData).catch((err) => {
1267
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, err.message);
1268
+ });
1269
+ }
1270
+
1271
+ /**
1272
+ * @private
1273
+ * @return {void}
1274
+ */
1275
+ function initTabsResizeObserver() {
1276
+ if (this[tabsResizeObserverSymbol]) {
1277
+ return;
1278
+ }
1279
+ if (!this[tabsSymbol]) {
1280
+ return;
1281
+ }
1282
+ if (typeof ResizeObserver === "undefined") {
1283
+ return;
1284
+ }
1285
+
1286
+ this[tabsResizeObserverSymbol] = new ResizeObserver(() => {
1287
+ scheduleUpdateEditorSizes.call(this);
1288
+ });
1289
+ this[tabsResizeObserverSymbol].observe(this[tabsSymbol]);
1290
+ }
1291
+
1292
+ /**
1293
+ * @private
1294
+ * @return {void}
1295
+ */
1296
+ function initHostResizeObserver() {
1297
+ if (this[hostResizeObserverSymbol]) {
1298
+ return;
1299
+ }
1300
+ if (typeof ResizeObserver === "undefined") {
1301
+ return;
1302
+ }
1303
+
1304
+ this[hostResizeObserverSymbol] = new ResizeObserver(() => {
1305
+ scheduleUpdateEditorSizes.call(this);
1306
+ });
1307
+ this[hostResizeObserverSymbol].observe(this);
1308
+ }
1309
+
1310
+ /**
1311
+ * @private
1312
+ * @return {void}
1313
+ */
1314
+ function scheduleUpdateEditorSizes() {
1315
+ const win = getWindow();
1316
+ if (!win) {
1317
+ return;
1318
+ }
1319
+ if (this[editorSizeRafSymbol]) {
1320
+ win.cancelAnimationFrame(this[editorSizeRafSymbol]);
1321
+ }
1322
+ this[editorSizeRafSymbol] = win.requestAnimationFrame(() => {
1323
+ this[editorSizeRafSymbol] = null;
1324
+ updateEditorSizes.call(this);
1325
+ });
1326
+ }
1327
+
1328
+ /**
1329
+ * @private
1330
+ * @return {void}
1331
+ */
1332
+ function updateEditorSizes() {
1333
+ if (!this[tabsSymbol]) {
1334
+ return;
1335
+ }
1336
+
1337
+ const tabsElement = this[tabsSymbol];
1338
+ const container = this[tabsContainerSymbol] || tabsElement;
1339
+ const containerRect = container.getBoundingClientRect();
1340
+ if (containerRect.height <= 0) {
1341
+ return;
1342
+ }
1343
+
1344
+ const nav = tabsElement.shadowRoot?.querySelector?.(
1345
+ "[data-monster-role=nav]",
1346
+ );
1347
+ const navHeight = nav?.getBoundingClientRect?.().height || 0;
1348
+ const available = Math.max(0, containerRect.height - navHeight);
1349
+
1350
+ for (const tabData of this[tabsByReferenceSymbol].values()) {
1351
+ const editor = tabData?.editor;
1352
+ const tabElement = tabData?.tabElement;
1353
+ if (tabElement instanceof HTMLElement) {
1354
+ tabElement.style.height = `${available}px`;
1355
+ tabElement.style.minHeight = `${available}px`;
1356
+ }
1357
+ if (!(editor instanceof HTMLElement)) {
1358
+ continue;
1359
+ }
1360
+ if (!isCanvasElement(editor)) {
1361
+ editor.style.height = "100%";
1362
+ editor.style.minHeight = "0";
1363
+ }
1364
+ if (isCanvasElement(editor)) {
1365
+ editor.width = Math.max(1, Math.floor(containerRect.width));
1366
+ editor.height = Math.max(1, Math.floor(available));
1367
+ renderCanvasFromValue(editor, editor.dataset.monsterValue || "");
1368
+ }
1369
+ }
1370
+ }
1371
+
1372
+ /**
1373
+ * @private
1374
+ * @param {object} tabData
1375
+ * @return {Promise<void>}
1376
+ */
1377
+ async function loadFileContent(tabData) {
1378
+ const datasource = this[fileDatasourceSymbol];
1379
+ if (!datasource?.read) {
1380
+ const content = tabData.entry?.raw?.content || "";
1381
+ const patched = applyPersistedDiff.call(this, content, tabData.entry);
1382
+ tabData.adapter.setValue(tabData.editor, patched.value);
1383
+ tabData.original = content;
1384
+ updateTabDirty.call(this, tabData, patched.dirty);
1385
+ return;
1386
+ }
1387
+
1388
+ const params = buildParameters(tabData.entry);
1389
+ const readUrl = datasource.getOption?.("read.url");
1390
+
1391
+ if (!isString(readUrl) || readUrl.trim() === "") {
1392
+ const content = tabData.entry?.raw?.content || "";
1393
+ const patched = applyPersistedDiff.call(this, content, tabData.entry);
1394
+ tabData.adapter.setValue(tabData.editor, patched.value);
1395
+ tabData.original = content;
1396
+ updateTabDirty.call(this, tabData, patched.dirty);
1397
+ return;
1398
+ }
1399
+
1400
+ datasource.setOption("read.parameters", params);
1401
+
1402
+ await datasource.read();
1403
+
1404
+ const data = datasource.data;
1405
+ const content = extractContent.call(this, data);
1406
+ const patched = applyPersistedDiff.call(this, content, tabData.entry);
1407
+ tabData.adapter.setValue(tabData.editor, patched.value);
1408
+ tabData.original = content;
1409
+ updateTabDirty.call(this, tabData, patched.dirty);
1410
+ }
1411
+
1412
+ /**
1413
+ * @private
1414
+ * @param {object} data
1415
+ * @return {string}
1416
+ */
1417
+ function extractContent(data) {
1418
+ if (isString(data)) {
1419
+ return data;
1420
+ }
1421
+
1422
+ const path = this.getOption("mapping.contentPath");
1423
+ if (isString(path) && path.trim() !== "") {
1424
+ try {
1425
+ const value = new Pathfinder(data).getVia(path);
1426
+ if (isString(value)) {
1427
+ return value;
1428
+ }
1429
+ } catch (e) {
1430
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
1431
+ }
1432
+ }
1433
+
1434
+ if (isString(data?.content)) {
1435
+ return data.content;
1436
+ }
1437
+
1438
+ if (isString(data?.file?.content)) {
1439
+ return data.file.content;
1440
+ }
1441
+
1442
+ return "";
1443
+ }
1444
+
1445
+ /**
1446
+ * @private
1447
+ * @param {object} tabData
1448
+ * @return {void}
1449
+ */
1450
+ function handleEditorChange(tabData) {
1451
+ const value = tabData.adapter.getValue(tabData.editor);
1452
+ const isDirty = value !== tabData.original;
1453
+ updateTabDirty.call(this, tabData, isDirty);
1454
+ schedulePersistDiff.call(this, tabData);
1455
+ updateSaveButtonState.call(this);
1456
+ }
1457
+
1458
+ /**
1459
+ * @private
1460
+ * @param {object} tabData
1461
+ * @param {boolean} dirty
1462
+ * @return {void}
1463
+ */
1464
+ function updateTabDirty(tabData, dirty) {
1465
+ const marker = this.getOption("tabs.dirtyMarker", "*");
1466
+ const label = dirty ? `${tabData.label}${marker}` : tabData.label;
1467
+ const current = tabData.tabElement?.getAttribute?.(ATTRIBUTE_BUTTON_LABEL);
1468
+ if (tabData.dirty === dirty && current === label) {
1469
+ return;
1470
+ }
1471
+ tabData.dirty = dirty;
1472
+ if (current !== label) {
1473
+ tabData.tabElement.setAttribute(ATTRIBUTE_BUTTON_LABEL, label);
1474
+ }
1475
+ }
1476
+
1477
+ /**
1478
+ * @private
1479
+ * @return {void}
1480
+ */
1481
+ function updateEmptyState() {
1482
+ if (!this[emptyStateSymbol]) {
1483
+ return;
1484
+ }
1485
+
1486
+ const hasTabs = this[tabsByReferenceSymbol].size > 0;
1487
+ if (hasTabs) {
1488
+ this[emptyStateSymbol].classList.add("hidden");
1489
+ } else {
1490
+ this[emptyStateSymbol].classList.remove("hidden");
1491
+ }
1492
+ }
1493
+
1494
+ /**
1495
+ * @private
1496
+ * @return {void}
1497
+ */
1498
+ function updateSaveButtonState() {
1499
+ if (!this[saveButtonSymbol]) {
1500
+ return;
1501
+ }
1502
+
1503
+ const active = getActiveTabData.call(this);
1504
+ const enabled = active?.dirty === true;
1505
+ this[saveButtonSymbol].setOption("disabled", !enabled);
1506
+
1507
+ if (this[saveAllButtonSymbol]) {
1508
+ const anyDirty = Array.from(this[tabsByReferenceSymbol].values()).some(
1509
+ (tab) => tab.dirty === true,
1510
+ );
1511
+ this[saveAllButtonSymbol].setOption("disabled", !anyDirty);
1512
+ }
1513
+ }
1514
+
1515
+ /**
1516
+ * @private
1517
+ * @return {object|null}
1518
+ */
1519
+ function getActiveTabData() {
1520
+ if (!this[tabsSymbol]) {
1521
+ return null;
1522
+ }
1523
+
1524
+ const reference = this[tabsSymbol].getActiveTab?.();
1525
+ if (!reference) {
1526
+ return null;
1527
+ }
1528
+
1529
+ return this[tabsByReferenceSymbol].get(reference) || null;
1530
+ }
1531
+
1532
+ /**
1533
+ * @private
1534
+ * @return {void}
1535
+ */
1536
+ function saveActiveTab() {
1537
+ const tabData = getActiveTabData.call(this);
1538
+ if (!tabData || tabData.dirty !== true) {
1539
+ return;
1540
+ }
1541
+
1542
+ if (this[saveButtonSymbol]) {
1543
+ this[saveButtonSymbol].setState("activity");
1544
+ }
1545
+
1546
+ saveTab
1547
+ .call(this, tabData)
1548
+ .then(() => {
1549
+ if (this[saveButtonSymbol]) {
1550
+ this[saveButtonSymbol].setState("successful", 1200);
1551
+ }
1552
+ })
1553
+ .catch((err) => {
1554
+ if (this[saveButtonSymbol]) {
1555
+ this[saveButtonSymbol].setState("failed", 1600);
1556
+ }
1557
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, err.message);
1558
+ })
1559
+ .finally(() => {
1560
+ updateSaveButtonState.call(this);
1561
+ });
1562
+ }
1563
+
1564
+ /**
1565
+ * @private
1566
+ * @param {object} tabData
1567
+ * @return {Promise<void>}
1568
+ */
1569
+ async function saveTab(tabData) {
1570
+ const datasource = this[fileDatasourceSymbol];
1571
+ if (!datasource?.write) {
1572
+ throw new Error("file datasource not available");
1573
+ }
1574
+
1575
+ const payload = buildWritePayload.call(this, tabData);
1576
+ const params = buildParameters(tabData.entry);
1577
+ datasource.data = payload;
1578
+ datasource.setOption("write.parameters", params);
1579
+
1580
+ await datasource.write();
1581
+
1582
+ tabData.original = tabData.adapter.getValue(tabData.editor);
1583
+ updateTabDirty.call(this, tabData, false);
1584
+ clearPersistedDiff.call(this, tabData.entry);
1585
+ }
1586
+
1587
+ /**
1588
+ * @private
1589
+ * @return {void}
1590
+ */
1591
+ function saveAllTabs() {
1592
+ if (!this[saveAllButtonSymbol]) {
1593
+ return;
1594
+ }
1595
+
1596
+ const dirtyTabs = Array.from(this[tabsByReferenceSymbol].values()).filter(
1597
+ (tab) => tab.dirty === true,
1598
+ );
1599
+
1600
+ if (dirtyTabs.length === 0) {
1601
+ return;
1602
+ }
1603
+
1604
+ this[saveAllButtonSymbol].setState("activity");
1605
+
1606
+ Promise.all(dirtyTabs.map((tab) => saveTab.call(this, tab)))
1607
+ .then(() => {
1608
+ this[saveAllButtonSymbol].setState("successful", 1200);
1609
+ })
1610
+ .catch((err) => {
1611
+ this[saveAllButtonSymbol].setState("failed", 1600);
1612
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, err.message);
1613
+ })
1614
+ .finally(() => {
1615
+ updateSaveButtonState.call(this);
1616
+ });
1617
+ }
1618
+
1619
+ /**
1620
+ * @private
1621
+ * @param {object} tabData
1622
+ * @return {object}
1623
+ */
1624
+ function buildWritePayload(tabData) {
1625
+ const custom = this.getOption("actions.buildPayload");
1626
+ if (isFunction(custom)) {
1627
+ return custom(tabData);
1628
+ }
1629
+
1630
+ const entry = tabData.entry || {};
1631
+ const content = tabData.adapter.getValue(tabData.editor);
1632
+ return {
1633
+ id: entry.id,
1634
+ path: entry.path,
1635
+ name: entry.label,
1636
+ type: entry.type,
1637
+ content: content,
1638
+ };
1639
+ }
1640
+
1641
+ /**
1642
+ * @private
1643
+ * @param {object} entry
1644
+ * @return {object}
1645
+ */
1646
+ function buildParameters(entry) {
1647
+ const payload = clone(entry);
1648
+ payload.id = entry.id;
1649
+ payload.path = entry.path;
1650
+ payload.name = entry.label;
1651
+ payload.type = entry.type;
1652
+ return payload;
1653
+ }
1654
+
1655
+ /**
1656
+ * @private
1657
+ * @return {object}
1658
+ */
1659
+ function getEditorAdapter() {
1660
+ const custom = this.getOption("editor.adapter");
1661
+ const base = {
1662
+ getValue: (element) => {
1663
+ if (isImageEditorElement(element)) {
1664
+ if (typeof element.getImageDataUrl === "function") {
1665
+ return element.getImageDataUrl(
1666
+ element.dataset.monsterOutputType || undefined,
1667
+ );
1668
+ }
1669
+ return element.dataset.monsterValue || "";
1670
+ }
1671
+ if (isCanvasElement(element)) {
1672
+ return element.dataset.monsterValue || "";
1673
+ }
1674
+ if (element && "value" in element) {
1675
+ return element.value ?? "";
1676
+ }
1677
+ return element?.textContent ?? "";
1678
+ },
1679
+ setValue: (element, value) => {
1680
+ const next = value ?? "";
1681
+ if (isImageEditorElement(element)) {
1682
+ element.dataset.monsterValue = next;
1683
+ if (typeof element.setImage === "function") {
1684
+ element.setImage(next, {
1685
+ contentType: element.dataset.monsterContentType,
1686
+ storeOriginal: true,
1687
+ });
1688
+ }
1689
+ return;
1690
+ }
1691
+ if (isCanvasElement(element)) {
1692
+ renderCanvasFromValue(element, next);
1693
+ return;
1694
+ }
1695
+ if (element && "value" in element) {
1696
+ element.value = next;
1697
+ return;
1698
+ }
1699
+ if (element) {
1700
+ element.textContent = next;
1701
+ }
1702
+ },
1703
+ bindChange: (element, callback) => {
1704
+ if (!element || typeof callback !== "function") {
1705
+ return () => {};
1706
+ }
1707
+ if (isCanvasElement(element)) {
1708
+ return () => {};
1709
+ }
1710
+ if (isImageEditorElement(element)) {
1711
+ const handler = () => callback();
1712
+ element.addEventListener("monster-image-editor-changed", handler);
1713
+ return () => {
1714
+ element.removeEventListener("monster-image-editor-changed", handler);
1715
+ };
1716
+ }
1717
+
1718
+ if (isCanvasElement(element)) {
1719
+ return () => {};
1720
+ }
1721
+
1722
+ const handler = () => callback();
1723
+ element.addEventListener("input", handler);
1724
+ element.addEventListener("change", handler);
1725
+ return () => {
1726
+ element.removeEventListener("input", handler);
1727
+ element.removeEventListener("change", handler);
1728
+ };
1729
+ },
1730
+ setReadOnly: (element, readOnly) => {
1731
+ if (!element) {
1732
+ return;
1733
+ }
1734
+ if (isImageEditorElement(element)) {
1735
+ if (readOnly === true) {
1736
+ element.setAttribute("data-monster-readonly", "");
1737
+ } else {
1738
+ element.removeAttribute("data-monster-readonly");
1739
+ }
1740
+ return;
1741
+ }
1742
+ if (isCanvasElement(element)) {
1743
+ return;
1744
+ }
1745
+ if ("readOnly" in element) {
1746
+ element.readOnly = readOnly === true;
1747
+ return;
1748
+ }
1749
+ if (readOnly === true) {
1750
+ element.setAttribute("data-monster-readonly", "");
1751
+ } else {
1752
+ element.removeAttribute("data-monster-readonly");
1753
+ }
1754
+ },
1755
+ };
1756
+
1757
+ if (!isObject(custom)) {
1758
+ return base;
1759
+ }
1760
+
1761
+ const adapter = Object.assign({}, base);
1762
+ if (isFunction(custom.getValue)) {
1763
+ adapter.getValue = custom.getValue;
1764
+ }
1765
+ if (isFunction(custom.setValue)) {
1766
+ adapter.setValue = custom.setValue;
1767
+ }
1768
+ if (isFunction(custom.bindChange)) {
1769
+ adapter.bindChange = custom.bindChange;
1770
+ }
1771
+ if (isFunction(custom.setReadOnly)) {
1772
+ adapter.setReadOnly = custom.setReadOnly;
1773
+ }
1774
+
1775
+ return adapter;
1776
+ }
1777
+
1778
+ /**
1779
+ * @private
1780
+ * @param {object} entry
1781
+ * @return {HTMLElement}
1782
+ */
1783
+ function createEditorElement(entry) {
1784
+ const tag = resolveEditorTag.call(this, entry);
1785
+ const editor = getDocument().createElement(tag);
1786
+ const mimeType = getMimeTypeForEntry(entry);
1787
+ if (isImageEditorElement(editor) && isString(mimeType)) {
1788
+ editor.dataset.monsterContentType = mimeType;
1789
+ editor.dataset.monsterOutputType = mimeType;
1790
+ if (typeof editor.setOption === "function") {
1791
+ editor.setOption("source.contentType", mimeType);
1792
+ editor.setOption("output.type", mimeType);
1793
+ }
1794
+ }
1795
+ return editor;
1796
+ }
1797
+
1798
+ /**
1799
+ * @private
1800
+ * @param {object} entry
1801
+ * @return {boolean}
1802
+ */
1803
+ function resolveEditorTag(entry) {
1804
+ const mimeType = getMimeTypeForEntry(entry);
1805
+ const map = this.getOption("editor.byMimeType", {});
1806
+ const tag = map?.[mimeType];
1807
+ if (isString(tag) && tag.trim() !== "") {
1808
+ return tag.trim();
1809
+ }
1810
+ const fallback = this.getOption("editor.defaultTag", "textarea");
1811
+ if (isString(fallback) && fallback.trim() !== "") {
1812
+ return fallback.trim();
1813
+ }
1814
+ return "textarea";
1815
+ }
1816
+
1817
+ /**
1818
+ * @private
1819
+ * @param {HTMLElement} element
1820
+ * @return {boolean}
1821
+ */
1822
+ function isCanvasElement(element) {
1823
+ return element instanceof HTMLCanvasElement;
1824
+ }
1825
+
1826
+ /**
1827
+ * @private
1828
+ * @param {HTMLElement} element
1829
+ * @return {boolean}
1830
+ */
1831
+ function isImageEditorElement(element) {
1832
+ return (
1833
+ element instanceof HTMLElement &&
1834
+ element.tagName.toLowerCase() === "monster-image-editor"
1835
+ );
1836
+ }
1837
+
1838
+ /**
1839
+ * @private
1840
+ * @param {HTMLCanvasElement} canvas
1841
+ * @param {string} value
1842
+ * @return {void}
1843
+ */
1844
+ function renderCanvasFromValue(canvas, value) {
1845
+ if (!isCanvasElement(canvas)) {
1846
+ return;
1847
+ }
1848
+
1849
+ const next = value ?? "";
1850
+ canvas.dataset.monsterValue = next;
1851
+
1852
+ const ctx = canvas.getContext("2d");
1853
+ if (!ctx) {
1854
+ return;
1855
+ }
1856
+
1857
+ const source = normalizeImageSource(next);
1858
+ if (!source) {
1859
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1860
+ return;
1861
+ }
1862
+
1863
+ const win = getWindow();
1864
+ if (!win) {
1865
+ return;
1866
+ }
1867
+
1868
+ const img = new win.Image();
1869
+ img.onload = () => {
1870
+ const targetWidth = canvas.width || img.naturalWidth;
1871
+ const targetHeight = canvas.height || img.naturalHeight;
1872
+
1873
+ if (canvas.width !== targetWidth) {
1874
+ canvas.width = targetWidth;
1875
+ }
1876
+ if (canvas.height !== targetHeight) {
1877
+ canvas.height = targetHeight;
1878
+ }
1879
+
1880
+ const scale = Math.min(
1881
+ canvas.width / img.naturalWidth,
1882
+ canvas.height / img.naturalHeight,
1883
+ );
1884
+ const drawWidth = img.naturalWidth * scale;
1885
+ const drawHeight = img.naturalHeight * scale;
1886
+ const offsetX = (canvas.width - drawWidth) / 2;
1887
+ const offsetY = (canvas.height - drawHeight) / 2;
1888
+
1889
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1890
+ ctx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight);
1891
+ };
1892
+ img.onerror = () => {
1893
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1894
+ };
1895
+ img.src = source;
1896
+ }
1897
+
1898
+ /**
1899
+ * @private
1900
+ * @param {string} value
1901
+ * @return {string}
1902
+ */
1903
+ function normalizeImageSource(value) {
1904
+ if (!isString(value)) {
1905
+ return "";
1906
+ }
1907
+ const trimmed = value.trim();
1908
+ if (trimmed === "") {
1909
+ return "";
1910
+ }
1911
+ if (trimmed.startsWith("data:image/")) {
1912
+ return trimmed;
1913
+ }
1914
+ if (trimmed.startsWith("<svg")) {
1915
+ return `data:image/svg+xml;utf8,${encodeURIComponent(trimmed)}`;
1916
+ }
1917
+ return trimmed;
1918
+ }
1919
+
1920
+ /**
1921
+ * @private
1922
+ * @param {object} entry
1923
+ * @return {string}
1924
+ */
1925
+ function getMimeTypeForEntry(entry) {
1926
+ if (isString(entry?.mimeType) && entry.mimeType.trim() !== "") {
1927
+ return entry.mimeType.trim().toLowerCase();
1928
+ }
1929
+ const ext = (entry?.type || "").toString().toLowerCase();
1930
+ switch (ext) {
1931
+ case "png":
1932
+ return "image/png";
1933
+ case "jpg":
1934
+ case "jpeg":
1935
+ return "image/jpeg";
1936
+ case "gif":
1937
+ return "image/gif";
1938
+ case "svg":
1939
+ return "image/svg+xml";
1940
+ case "webp":
1941
+ return "image/webp";
1942
+ case "bmp":
1943
+ return "image/bmp";
1944
+ case "ico":
1945
+ return "image/x-icon";
1946
+ case "md":
1947
+ return "text/markdown";
1948
+ case "css":
1949
+ return "text/css";
1950
+ case "html":
1951
+ case "htm":
1952
+ return "text/html";
1953
+ case "js":
1954
+ case "mjs":
1955
+ return "text/javascript";
1956
+ case "json":
1957
+ return "application/json";
1958
+ case "txt":
1959
+ return "text/plain";
1960
+ default:
1961
+ return "text/plain";
1962
+ }
1963
+ }
1964
+
1965
+ /**
1966
+ * @private
1967
+ * @param {object} tabData
1968
+ * @return {void}
1969
+ */
1970
+ function schedulePersistDiff(tabData) {
1971
+ const debounceMs = this.getOption("persistence.debounceMs", 300);
1972
+ if (!(tabData.persistDebounce instanceof DeadMansSwitch)) {
1973
+ tabData.persistDebounce = new DeadMansSwitch(debounceMs, () => {
1974
+ persistDiff.call(this, tabData);
1975
+ });
1976
+ return;
1977
+ }
1978
+
1979
+ try {
1980
+ tabData.persistDebounce.touch();
1981
+ } catch (_e) {
1982
+ tabData.persistDebounce = new DeadMansSwitch(debounceMs, () => {
1983
+ persistDiff.call(this, tabData);
1984
+ });
1985
+ }
1986
+ }
1987
+
1988
+ /**
1989
+ * @private
1990
+ * @return {void}
1991
+ */
1992
+ function syncDatasources() {
1993
+ if (this[listDatasourceOwnedSymbol] === true && this[listDatasourceSymbol]) {
1994
+ const restConfig = this.getOption("datasource.list.rest", {});
1995
+ if (isObject(restConfig)) {
1996
+ logDebug.call(this, "sync list datasource", restConfig?.read?.url);
1997
+ this[listDatasourceSymbol].setOptions(restConfig);
1998
+ this[listDatasourceSymbol].setOption("features.autoInit", true);
1999
+ this[listDatasourceSymbol].setOption("autoInit.oneTime", true);
2000
+ requestList.call(this, this.getOption("lazy.root", null));
2001
+ }
2002
+ } else if (!this[listDatasourceSymbol]) {
2003
+ initListDatasource.call(this);
2004
+ }
2005
+
2006
+ if (this[fileDatasourceOwnedSymbol] === true && this[fileDatasourceSymbol]) {
2007
+ const restConfig = this.getOption("datasource.file.rest", {});
2008
+ if (isObject(restConfig)) {
2009
+ this[fileDatasourceSymbol].setOptions(restConfig);
2010
+ }
2011
+ } else if (!this[fileDatasourceSymbol]) {
2012
+ initFileDatasource.call(this);
2013
+ }
2014
+ }
2015
+
2016
+ /**
2017
+ * @private
2018
+ * @param {string} message
2019
+ * @param {*=} data
2020
+ * @return {void}
2021
+ */
2022
+ function logDebug(message, data) {
2023
+ if (this.getOption("debug") !== true) {
2024
+ return;
2025
+ }
2026
+ if (data !== undefined) {
2027
+ console.log(`[monster-file-manager] ${message}`, data);
2028
+ } else {
2029
+ console.log(`[monster-file-manager] ${message}`);
2030
+ }
2031
+ }
2032
+
2033
+ /**
2034
+ * @private
2035
+ * @return {void}
2036
+ */
2037
+ function refreshLabels() {
2038
+ if (this[titleSymbol]) {
2039
+ this[titleSymbol].textContent = this.getOption("labels.title");
2040
+ }
2041
+ if (this[saveButtonSymbol]) {
2042
+ this[saveButtonSymbol].setOption(
2043
+ "labels.button",
2044
+ this.getOption("labels.save"),
2045
+ );
2046
+ }
2047
+ if (this[saveAllButtonSymbol]) {
2048
+ this[saveAllButtonSymbol].setOption(
2049
+ "labels.button",
2050
+ this.getOption("labels.saveAll"),
2051
+ );
2052
+ }
2053
+ if (this[emptyStateSymbol]) {
2054
+ const emptyLabel = this.getOption("labels.empty");
2055
+ if (isFunction(this[emptyStateSymbol].setOption)) {
2056
+ this[emptyStateSymbol].setOption("content.visual", getEmptyStateIcon());
2057
+ this[emptyStateSymbol].setOption("content.content", emptyLabel);
2058
+ } else {
2059
+ this[emptyStateSymbol].textContent = emptyLabel;
2060
+ }
2061
+ }
2062
+ }
2063
+
2064
+ /**
2065
+ * @private
2066
+ * @return {string}
2067
+ */
2068
+ function getEmptyStateIcon() {
2069
+ return `<svg width="4rem" height="4rem" viewBox="0 -12 512.00032 512" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path d="m455.074219 172.613281 53.996093-53.996093c2.226563-2.222657 3.273438-5.367188 2.828126-8.480469-.441407-3.113281-2.328126-5.839844-5.085938-7.355469l-64.914062-35.644531c-4.839844-2.65625-10.917969-.886719-13.578126 3.953125-2.65625 4.84375-.890624 10.921875 3.953126 13.578125l53.234374 29.230469-46.339843 46.335937-166.667969-91.519531 46.335938-46.335938 46.839843 25.722656c4.839844 2.65625 10.921875.890626 13.578125-3.953124 2.660156-4.839844.890625-10.921876-3.953125-13.578126l-53.417969-29.335937c-3.898437-2.140625-8.742187-1.449219-11.882812 1.695313l-54 54-54-54c-3.144531-3.144532-7.988281-3.832032-11.882812-1.695313l-184.929688 101.546875c-2.757812 1.515625-4.644531 4.238281-5.085938 7.355469-.445312 3.113281.601563 6.257812 2.828126 8.480469l53.996093 53.996093-53.996093 53.992188c-2.226563 2.226562-3.273438 5.367187-2.828126 8.484375.441407 3.113281 2.328126 5.839844 5.085938 7.351562l55.882812 30.6875v102.570313c0 3.652343 1.988282 7.011719 5.1875 8.769531l184.929688 101.542969c1.5.824219 3.15625 1.234375 4.8125 1.234375s3.3125-.410156 4.8125-1.234375l184.929688-101.542969c3.199218-1.757812 5.1875-5.117188 5.1875-8.769531v-102.570313l55.882812-30.683594c2.757812-1.515624 4.644531-4.242187 5.085938-7.355468.445312-3.113282-.601563-6.257813-2.828126-8.480469zm-199.074219 90.132813-164.152344-90.136719 164.152344-90.140625 164.152344 90.140625zm-62.832031-240.367188 46.332031 46.335938-166.667969 91.519531-46.335937-46.335937zm-120.328125 162.609375 166.667968 91.519531-46.339843 46.339844-166.671875-91.519531zm358.089844 184.796875-164.929688 90.5625v-102.222656c0-5.523438-4.476562-10-10-10s-10 4.476562-10 10v102.222656l-164.929688-90.5625v-85.671875l109.046876 59.878907c1.511718.828124 3.167968 1.234374 4.808593 1.234374 2.589844 0 5.152344-1.007812 7.074219-2.929687l54-54 54 54c1.921875 1.925781 4.484375 2.929687 7.074219 2.929687 1.640625 0 3.296875-.40625 4.808593-1.234374l109.046876-59.878907zm-112.09375-46.9375-46.339844-46.34375 166.667968-91.515625 46.34375 46.335938zm0 0"></path><path d="m404.800781 68.175781c2.628907 0 5.199219-1.070312 7.070313-2.933593 1.859375-1.859376 2.929687-4.4375 2.929687-7.066407 0-2.632812-1.070312-5.210937-2.929687-7.070312-1.859375-1.863281-4.441406-2.929688-7.070313-2.929688-2.640625 0-5.210937 1.066407-7.070312 2.929688-1.871094 1.859375-2.929688 4.4375-2.929688 7.070312 0 2.628907 1.058594 5.207031 2.929688 7.066407 1.859375 1.863281 4.441406 2.933593 7.070312 2.933593zm0 0"></path><path d="m256 314.925781c-2.628906 0-5.210938 1.066407-7.070312 2.929688-1.859376 1.867187-2.929688 4.4375-2.929688 7.070312 0 2.636719 1.070312 5.207031 2.929688 7.078125 1.859374 1.859375 4.441406 2.921875 7.070312 2.921875s5.210938-1.0625 7.070312-2.921875c1.859376-1.871094 2.929688-4.441406 2.929688-7.078125 0-2.632812-1.070312-5.203125-2.929688-7.070312-1.859374-1.863281-4.441406-2.929688-7.070312-2.929688zm0 0"></path></svg>`;
2070
+ }
2071
+
2072
+ /**
2073
+ * @private
2074
+ * @returns {object}
2075
+ */
2076
+ function getTranslations() {
2077
+ const locale = getLocaleOfDocument();
2078
+ switch (locale.language) {
2079
+ case "de":
2080
+ return {
2081
+ title: "Dateien",
2082
+ save: "Speichern",
2083
+ saveAll: "Alle speichern",
2084
+ empty: "Datei aus dem Baum auswaehlen.",
2085
+ };
2086
+ case "fr":
2087
+ return {
2088
+ title: "Fichiers",
2089
+ save: "Enregistrer",
2090
+ saveAll: "Tout enregistrer",
2091
+ empty: "Selectionnez un fichier dans l'arborescence.",
2092
+ };
2093
+ case "sp":
2094
+ return {
2095
+ title: "Archivos",
2096
+ save: "Guardar",
2097
+ saveAll: "Guardar todo",
2098
+ empty: "Seleccione un archivo del arbol.",
2099
+ };
2100
+ case "it":
2101
+ return {
2102
+ title: "File",
2103
+ save: "Salva",
2104
+ saveAll: "Salva tutto",
2105
+ empty: "Seleziona un file dall'albero.",
2106
+ };
2107
+ case "pl":
2108
+ return {
2109
+ title: "Pliki",
2110
+ save: "Zapisz",
2111
+ saveAll: "Zapisz wszystko",
2112
+ empty: "Wybierz plik z drzewa.",
2113
+ };
2114
+ case "no":
2115
+ case "dk":
2116
+ return {
2117
+ title: "Filer",
2118
+ save: "Lagre",
2119
+ saveAll: "Lagre alle",
2120
+ empty: "Velg en fil fra treet.",
2121
+ };
2122
+ case "sw":
2123
+ return {
2124
+ title: "Filer",
2125
+ save: "Spara",
2126
+ saveAll: "Spara alla",
2127
+ empty: "Valj en fil fran tradet.",
2128
+ };
2129
+ default:
2130
+ case "en":
2131
+ return {
2132
+ title: "Files",
2133
+ save: "Save",
2134
+ saveAll: "Save All",
2135
+ empty: "Select a file from the tree to open it.",
2136
+ };
2137
+ }
2138
+ }
2139
+
2140
+ /**
2141
+ * @private
2142
+ * @param {object} entry
2143
+ * @return {string|null}
2144
+ */
2145
+ function getStorageKey(entry) {
2146
+ const prefix = this.getOption("persistence.keyPrefix", "");
2147
+ const value = entry?.path || entry?.id;
2148
+ if (!isString(prefix) || prefix.trim() === "") {
2149
+ return null;
2150
+ }
2151
+ if (!isString(value) || value.trim() === "") {
2152
+ return null;
2153
+ }
2154
+ return `${prefix}:${value}`;
2155
+ }
2156
+
2157
+ /**
2158
+ * @private
2159
+ * @return {Storage|null}
2160
+ */
2161
+ function getStorage() {
2162
+ try {
2163
+ return getWindow()?.localStorage ?? null;
2164
+ } catch (_e) {
2165
+ return null;
2166
+ }
2167
+ }
2168
+
2169
+ /**
2170
+ * @private
2171
+ * @param {string} text
2172
+ * @return {number}
2173
+ */
2174
+ function hashText(text) {
2175
+ let hash = 0;
2176
+ for (let i = 0; i < text.length; i++) {
2177
+ hash = (hash + text.charCodeAt(i) * (i + 1)) % 2147483647;
2178
+ }
2179
+ return hash;
2180
+ }
2181
+
2182
+ /**
2183
+ * @private
2184
+ * @param {string} original
2185
+ * @param {object} entry
2186
+ * @return {{value: string, dirty: boolean}}
2187
+ */
2188
+ function applyPersistedDiff(original, entry) {
2189
+ const enabled = this.getOption("persistence.enabled", true);
2190
+ if (enabled !== true) {
2191
+ return { value: original, dirty: false };
2192
+ }
2193
+
2194
+ const storage = getStorage.call(this);
2195
+ if (!storage) {
2196
+ return { value: original, dirty: false };
2197
+ }
2198
+
2199
+ const key = getStorageKey.call(this, entry);
2200
+ if (!key) {
2201
+ return { value: original, dirty: false };
2202
+ }
2203
+
2204
+ const raw = storage.getItem(key);
2205
+ if (!raw) {
2206
+ return { value: original, dirty: false };
2207
+ }
2208
+
2209
+ let payload;
2210
+ try {
2211
+ payload = JSON.parse(raw);
2212
+ } catch (_e) {
2213
+ storage.removeItem(key);
2214
+ return { value: original, dirty: false };
2215
+ }
2216
+
2217
+ if (!payload || payload.baseHash !== hashText(original)) {
2218
+ storage.removeItem(key);
2219
+ return { value: original, dirty: false };
2220
+ }
2221
+
2222
+ const updated = applyDiffToLines(
2223
+ original.split("\n"),
2224
+ payload.diff || [],
2225
+ ).join("\n");
2226
+ return { value: updated, dirty: updated !== original };
2227
+ }
2228
+
2229
+ /**
2230
+ * @private
2231
+ * @param {string[]} lines
2232
+ * @param {Array} diffOps
2233
+ * @return {string[]}
2234
+ */
2235
+ function applyDiffToLines(lines, diffOps) {
2236
+ const out = lines.slice();
2237
+ const updates = [];
2238
+ const deletes = [];
2239
+ const adds = [];
2240
+
2241
+ for (const change of diffOps) {
2242
+ const index = change?.path?.[0];
2243
+ if (!Number.isInteger(index)) {
2244
+ continue;
2245
+ }
2246
+
2247
+ switch (change?.operator) {
2248
+ case "update":
2249
+ updates.push({
2250
+ index,
2251
+ value: change?.second?.value ?? "",
2252
+ });
2253
+ break;
2254
+ case "delete":
2255
+ deletes.push(index);
2256
+ break;
2257
+ case "add":
2258
+ adds.push({
2259
+ index,
2260
+ value: change?.second?.value ?? "",
2261
+ });
2262
+ break;
2263
+ default:
2264
+ break;
2265
+ }
2266
+ }
2267
+
2268
+ updates
2269
+ .sort((a, b) => a.index - b.index)
2270
+ .forEach(({ index, value }) => {
2271
+ out[index] = value;
2272
+ });
2273
+
2274
+ deletes
2275
+ .sort((a, b) => b - a)
2276
+ .forEach((index) => {
2277
+ if (index >= 0 && index < out.length) {
2278
+ out.splice(index, 1);
2279
+ }
2280
+ });
2281
+
2282
+ adds
2283
+ .sort((a, b) => a.index - b.index)
2284
+ .forEach(({ index, value }) => {
2285
+ const target = Math.max(0, Math.min(index, out.length));
2286
+ out.splice(target, 0, value);
2287
+ });
2288
+
2289
+ return out;
2290
+ }
2291
+
2292
+ /**
2293
+ * @private
2294
+ * @param {object} tabData
2295
+ * @return {void}
2296
+ */
2297
+ function persistDiff(tabData) {
2298
+ const enabled = this.getOption("persistence.enabled", true);
2299
+ if (enabled !== true) {
2300
+ return;
2301
+ }
2302
+
2303
+ const storage = getStorage.call(this);
2304
+ if (!storage) {
2305
+ return;
2306
+ }
2307
+
2308
+ const key = getStorageKey.call(this, tabData.entry);
2309
+ if (!key) {
2310
+ return;
2311
+ }
2312
+
2313
+ const current = tabData.adapter.getValue(tabData.editor);
2314
+ const original = tabData.original;
2315
+ if (current === original) {
2316
+ storage.removeItem(key);
2317
+ return;
2318
+ }
2319
+
2320
+ const diffResult = diff(original.split("\n"), current.split("\n"));
2321
+ const payload = {
2322
+ v: 1,
2323
+ baseHash: hashText(original),
2324
+ diff: diffResult,
2325
+ };
2326
+
2327
+ try {
2328
+ storage.setItem(key, JSON.stringify(payload));
2329
+ } catch (_e) {
2330
+ // ignore storage errors (quota, private mode)
2331
+ }
2332
+ }
2333
+
2334
+ /**
2335
+ * @private
2336
+ * @param {object} entry
2337
+ * @return {void}
2338
+ */
2339
+ function clearPersistedDiff(entry) {
2340
+ const storage = getStorage.call(this);
2341
+ if (!storage) {
2342
+ return;
2343
+ }
2344
+
2345
+ const key = getStorageKey.call(this, entry);
2346
+ if (key) {
2347
+ storage.removeItem(key);
2348
+ }
2349
+ }
2350
+
2351
+ /**
2352
+ * @private
2353
+ * @return {string}
2354
+ */
2355
+ function getTemplate() {
2356
+ // language=HTML
2357
+ return `
2358
+ <div data-monster-role="control" part="control">
2359
+ <div data-monster-role="toolbar" part="toolbar">
2360
+ <div data-monster-role="title" part="title" data-monster-replace="path:labels.title"></div>
2361
+ <div data-monster-role="actions" part="actions" class="monster-button-bar">
2362
+ <monster-message-state-button
2363
+ data-monster-role="save-button"
2364
+ data-monster-replace="path:labels.save"></monster-message-state-button>
2365
+ <monster-message-state-button
2366
+ data-monster-role="save-all-button"
2367
+ data-monster-replace="path:labels.saveAll"></monster-message-state-button>
2368
+ </div>
2369
+ </div>
2370
+ <monster-split-panel data-monster-role="split-panel">
2371
+ <div slot="start" data-monster-role="nav-panel" part="nav-panel">
2372
+ <monster-panel data-monster-role="tree-panel" part="tree-panel">
2373
+ <monster-tree-menu data-monster-role="tree">
2374
+ ${getIconSprite()}
2375
+ </monster-tree-menu>
2376
+ </monster-panel>
2377
+ </div>
2378
+ <div slot="end" data-monster-role="editor-panel" part="editor-panel">
2379
+ <div data-monster-role="tabs-container" part="tabs-container">
2380
+ <monster-tabs data-monster-role="tabs"></monster-tabs>
2381
+ </div>
2382
+ <monster-state data-monster-role="empty" part="empty"></monster-state>
2383
+ </div>
2384
+ </monster-split-panel>
2385
+ </div>
2386
+ `;
2387
+ }
2388
+
2389
+ /**
2390
+ * @private
2391
+ * @return {string}
2392
+ */
2393
+ function getIconSprite() {
2394
+ // language=HTML
2395
+ return `
2396
+ <svg xmlns="http://www.w3.org/2000/svg" style="display:none">
2397
+ <symbol id="monster-file-icon-default" viewBox="0 0 16 16">
2398
+ <path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5z"/>
2399
+ </symbol>
2400
+ <symbol id="monster-file-icon-code" viewBox="0 0 16 16">
2401
+ <path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5z"/>
2402
+ <path d="M8.646 6.646a.5.5 0 0 1 .708 0l2 2a.5.5 0 0 1 0 .708l-2 2a.5.5 0 0 1-.708-.708L10.293 9 8.646 7.354a.5.5 0 0 1 0-.708m-1.292 0a.5.5 0 0 0-.708 0l-2 2a.5.5 0 0 0 0 .708l2 2a.5.5 0 0 0 .708-.708L5.707 9l1.647-1.646a.5.5 0 0 0 0-.708"/>
2403
+ </symbol>
2404
+ <symbol id="monster-file-icon-image" viewBox="0 0 16 16">
2405
+ <path d="M6.502 7a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3"/>
2406
+ <path d="M14 14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zM4 1a1 1 0 0 0-1 1v10l2.224-2.224a.5.5 0 0 1 .61-.075L8 11l2.157-3.02a.5.5 0 0 1 .76-.063L13 10V4.5h-2A1.5 1.5 0 0 1 9.5 3V1z"/>
2407
+ </symbol>
2408
+ <symbol id="monster-file-icon-sound" viewBox="0 0 16 16">
2409
+ <path d="M11 6.64a1 1 0 0 0-1.243-.97l-1 .25A1 1 0 0 0 8 6.89v4.306A2.6 2.6 0 0 0 7 11c-.5 0-.974.134-1.338.377-.36.24-.662.628-.662 1.123s.301.883.662 1.123c.364.243.839.377 1.338.377s.974-.134 1.338-.377c.36-.24.662-.628.662-1.123V8.89l2-.5z"/>
2410
+ <path d="M14 14V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2M9.5 3A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z"/>
2411
+ </symbol>
2412
+ <symbol id="monster-file-icon-pdf" viewBox="0 0 16 16">
2413
+ <path d="M14 14V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2M9.5 3A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z"/>
2414
+ <path d="M4.603 14.087a.8.8 0 0 1-.438-.42c-.195-.388-.13-.776.08-1.102.198-.307.526-.568.897-.787a7.7 7.7 0 0 1 1.482-.645 20 20 0 0 0 1.062-2.227 7.3 7.3 0 0 1-.43-1.295c-.086-.4-.119-.796-.046-1.136.075-.354.274-.672.65-.823.192-.077.4-.12.602-.077a.7.7 0 0 1 .477.365c.088.164.12.356.127.538.007.188-.012.396-.047.614-.084.51-.27 1.134-.52 1.794a11 11 0 0 0 .98 1.686 5.8 5.8 0 0 1 1.334.05c.364.066.734.195.96.465.12.144.193.32.2.518.007.192-.047.382-.138.563a1.04 1.04 0 0 1-.354.416.86.86 0 0 1-.51.138c-.331-.014-.654-.196-.933-.417a5.7 5.7 0 0 1-.911-.95 11.7 11.7 0 0 0-1.997.406 11.3 11.3 0 0 1-1.02 1.51c-.292.35-.609.656-.927.787a.8.8 0 0 1-.58.029m1.379-1.901q-.25.115-.459.238c-.328.194-.541.383-.647.547-.094.145-.096.25-.04.361q.016.032.026.044l.035-.012c.137-.056.355-.235.635-.572a8 8 0 0 0 .45-.606m1.64-1.33a13 13 0 0 1 1.01-.193 12 12 0 0 1-.51-.858 21 21 0 0 1-.5 1.05zm2.446.45q.226.245.435.41c.24.19.407.253.498.256a.1.1 0 0 0 .07-.015.3.3 0 0 0 .094-.125.44.44 0 0 0 .059-.2.1.1 0 0 0-.026-.063c-.052-.062-.2-.152-.518-.209a4 4 0 0 0-.612-.053zM8.078 7.8a7 7 0 0 0 .2-.828q.046-.282.038-.465a.6.6 0 0 0-.032-.198.5.5 0 0 0-.145.04c-.087.035-.158.106-.196.283-.04.192-.03.469.046.822q.036.167.09.346z"/>
2415
+ </symbol>
2416
+ <symbol id="monster-file-icon-css" viewBox="0 0 16 16">
2417
+ <path d="M14 4.5V14a2 2 0 0 1-2 2h-1v-1h1a1 1 0 0 0 1-1V4.5h-2A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v9H2V2a2 2 0 0 1 2-2h5.5z"/>
2418
+ <path d="M3.397 14.841a1.13 1.13 0 0 0 .401.823q.195.162.478.252.284.091.665.091.507 0 .859-.158.354-.158.539-.44.187-.284.187-.656 0-.336-.134-.56a1 1 0 0 0-.375-.357 2 2 0 0 0-.566-.21l-.621-.144a1 1 0 0 1-.404-.176.37.37 0 0 1-.144-.299q0-.234.185-.384.188-.152.512-.152.214 0 .37.068a.6.6 0 0 1 .246.181.56.56 0 0 1 .12.258h.75a1.1 1.1 0 0 0-.2-.566 1.2 1.2 0 0 0-.5-.41 1.8 1.8 0 0 0-.78-.152q-.439 0-.776.15-.337.149-.527.421-.19.273-.19.639 0 .302.122.524.124.223.352.367.228.143.539.213l.618.144q.31.073.463.193a.39.39 0 0 1 .152.326.5.5 0 0 1-.085.29.56.56 0 0 1-.255.193q-.167.07-.413.07-.175 0-.32-.04a.8.8 0 0 1-.248-.115.58.58 0 0 1-.255-.384zM.806 13.693q0-.373.102-.633a.87.87 0 0 1 .302-.399.8.8 0 0 1 .475-.137q.225 0 .398.097a.7.7 0 0 1 .272.26.85.85 0 0 1 .12.381h.765v-.072a1.33 1.33 0 0 0-.466-.964 1.4 1.4 0 0 0-.489-.272 1.8 1.8 0 0 0-.606-.097q-.534 0-.911.223-.375.222-.572.632-.195.41-.196.979v.498q0 .568.193.976.197.407.572.626.375.217.914.217.439 0 .785-.164t.55-.454a1.27 1.27 0 0 0 .226-.674v-.076h-.764a.8.8 0 0 1-.118.363.7.7 0 0 1-.272.25.9.9 0 0 1-.401.087.85.85 0 0 1-.478-.132.83.83 0 0 1-.299-.392 1.7 1.7 0 0 1-.102-.627zM6.78 15.29a1.2 1.2 0 0 1-.111-.449h.764a.58.58 0 0 0 .255.384q.106.073.25.114.142.041.319.041.245 0 .413-.07a.56.56 0 0 0 .255-.193.5.5 0 0 0 .085-.29.39.39 0 0 0-.153-.326q-.152-.12-.463-.193l-.618-.143a1.7 1.7 0 0 1-.539-.214 1 1 0 0 1-.351-.367 1.1 1.1 0 0 1-.123-.524q0-.366.19-.639.19-.272.527-.422t.777-.149q.456 0 .779.152.326.153.5.41.18.255.2.566h-.75a.56.56 0 0 0-.12-.258.6.6 0 0 0-.246-.181.9.9 0 0 0-.37-.068q-.324 0-.512.152a.47.47 0 0 0-.184.384q0 .18.143.3a1 1 0 0 0 .404.175l.621.143q.326.075.566.211t.375.358.135.56q0 .37-.188.656a1.2 1.2 0 0 1-.539.439q-.351.158-.858.158-.381 0-.665-.09a1.4 1.4 0 0 1-.478-.252 1.1 1.1 0 0 1-.29-.375"/>
2419
+ </symbol>
2420
+ <symbol id="monster-file-icon-csv" viewBox="0 0 16 16">
2421
+ <path d="M14 4.5V14a2 2 0 0 1-2 2h-1v-1h1a1 1 0 0 0 1-1V4.5h-2A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v9H2V2a2 2 0 0 1 2-2h5.5z"/>
2422
+ <path d="M3.517 14.841a1.13 1.13 0 0 0 .401.823q.195.162.478.252.284.091.665.091.507 0 .859-.158.354-.158.539-.44.187-.284.187-.656 0-.336-.134-.56a1 1 0 0 0-.375-.357 2 2 0 0 0-.566-.21l-.621-.144a1 1 0 0 1-.404-.176.37.37 0 0 1-.144-.299q0-.234.185-.384.188-.152.512-.152.214 0 .37.068a.6.6 0 0 1 .246.181.56.56 0 0 1 .12.258h.75a1.1 1.1 0 0 0-.2-.566 1.2 1.2 0 0 0-.5-.41 1.8 1.8 0 0 0-.78-.152q-.439 0-.776.15-.337.149-.527.421-.19.273-.19.639 0 .302.122.524.124.223.352.367.228.143.539.213l.618.144q.31.073.463.193a.39.39 0 0 1 .152.326.5.5 0 0 1-.085.29.56.56 0 0 1-.255.193q-.167.07-.413.07-.175 0-.32-.04a.8.8 0 0 1-.248-.115.58.58 0 0 1-.255-.384zM.806 13.693q0-.373.102-.633a.87.87 0 0 1 .302-.399.8.8 0 0 1 .475-.137q.225 0 .398.097a.7.7 0 0 1 .272.26.85.85 0 0 1 .12.381h.765v-.072a1.33 1.33 0 0 0-.466-.964 1.4 1.4 0 0 0-.489-.272 1.8 1.8 0 0 0-.606-.097q-.534 0-.911.223-.375.222-.572.632-.195.41-.196.979v.498q0 .568.193.976.197.407.572.626.375.217.914.217.439 0 .785-.164t.55-.454a1.27 1.27 0 0 0 .226-.674v-.076h-.764a.8.8 0 0 1-.118.363.7.7 0 0 1-.272.25.9.9 0 0 1-.401.087.85.85 0 0 1-.478-.132.83.83 0 0 1-.299-.392 1.7 1.7 0 0 1-.102-.627zm8.239 2.238h-.953l-1.338-3.999h.917l.896 3.138h.038l.888-3.138h.879z"/>
2423
+ </symbol>
2424
+ <symbol id="monster-file-icon-doc" viewBox="0 0 16 16">
2425
+ <path d="M14 4.5V14a2 2 0 0 1-2 2v-1a1 1 0 0 0 1-1V4.5h-2A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v9H2V2a2 2 0 0 1 2-2h5.5z"/>
2426
+ <path d="M6.161 13.666v.522q0 .384-.117.641a.86.86 0 0 1-.322.387.9.9 0 0 1-.469.126.9.9 0 0 1-.471-.126.87.87 0 0 1-.32-.386 1.55 1.55 0 0 1-.117-.642v-.522q0-.386.117-.641a.87.87 0 0 1 .32-.387.87.87 0 0 1 .471-.129q.264 0 .469.13a.86.86 0 0 1 .322.386q.117.255.117.641m.803.519v-.513q0-.565-.205-.972a1.46 1.46 0 0 0-.589-.63q-.381-.22-.917-.22-.533 0-.92.22a1.44 1.44 0 0 0-.589.627q-.204.406-.205.975v.513q0 .563.205.973.205.406.59.627.386.216.92.216.535 0 .916-.216.383-.22.59-.627.204-.41.204-.973M0 11.926v4h1.459q.603 0 .999-.238a1.45 1.45 0 0 0 .595-.689q.196-.45.196-1.084 0-.63-.196-1.075a1.43 1.43 0 0 0-.59-.68q-.395-.234-1.004-.234zm.791.645h.563q.371 0 .609.152a.9.9 0 0 1 .354.454q.118.302.118.753a2.3 2.3 0 0 1-.068.592 1.1 1.1 0 0 1-.196.422.8.8 0 0 1-.334.252 1.3 1.3 0 0 1-.483.082H.79V12.57Zm7.422.483a1.7 1.7 0 0 0-.103.633v.495q0 .369.103.627a.83.83 0 0 0 .298.393.85.85 0 0 0 .478.131.9.9 0 0 0 .401-.088.7.7 0 0 0 .273-.248.8.8 0 0 0 .117-.364h.765v.076a1.27 1.27 0 0 1-.226.674q-.205.29-.55.454a1.8 1.8 0 0 1-.786.164q-.54 0-.914-.216a1.4 1.4 0 0 1-.571-.627q-.194-.408-.194-.976v-.498q0-.568.197-.978.195-.411.571-.633.378-.223.911-.223.328 0 .607.097.28.093.489.272a1.33 1.33 0 0 1 .466.964v.073H9.78a.85.85 0 0 0-.12-.38.7.7 0 0 0-.273-.261.8.8 0 0 0-.398-.097.8.8 0 0 0-.475.138.87.87 0 0 0-.301.398"/>
2427
+ </symbol>
2428
+ <symbol id="monster-file-icon-jpg" viewBox="0 0 16 16">
2429
+ <path d="M14 4.5V14a2 2 0 0 1-2 2h-1v-1h1a1 1 0 0 0 1-1V4.5h-2A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v9H2V2a2 2 0 0 1 2-2h5.5z"/>
2430
+ <path d="M9.66 12.632q.114.23.14.492h-.776a.8.8 0 0 0-.097-.249.7.7 0 0 0-.17-.19.7.7 0 0 0-.237-.126 1 1 0 0 0-.299-.044q-.428 0-.665.302-.234.301-.234.85v.498q0 .351.097.615a.9.9 0 0 0 .304.413.87.87 0 0 0 .519.146 1 1 0 0 0 .457-.096.67.67 0 0 0 .272-.264q.09-.164.091-.363v-.255H8.24v-.59h1.576v.798q0 .29-.097.55a1.3 1.3 0 0 1-.293.458 1.4 1.4 0 0 1-.495.313q-.296.111-.697.111a2 2 0 0 1-.753-.132 1.45 1.45 0 0 1-.533-.377 1.6 1.6 0 0 1-.32-.58 2.5 2.5 0 0 1-.105-.745v-.506q0-.543.2-.95.201-.406.582-.633.384-.228.926-.228.357 0 .636.1.28.1.48.275t.314.407ZM0 14.786q0 .246.082.465.083.22.243.39.165.17.407.267.246.093.569.093.63 0 .984-.345.357-.346.358-1.005v-2.725h-.791v2.745q0 .303-.138.466t-.422.164a.5.5 0 0 1-.454-.246.6.6 0 0 1-.073-.27H0Zm4.92-2.86H3.322v4h.791v-1.343h.803q.43 0 .732-.172.305-.177.463-.475.162-.302.161-.677 0-.374-.158-.677a1.2 1.2 0 0 0-.46-.477q-.3-.18-.732-.179Zm.546 1.333a.8.8 0 0 1-.085.381.57.57 0 0 1-.238.24.8.8 0 0 1-.375.082H4.11v-1.406h.66q.327 0 .512.182.185.181.185.521Z"/>
2431
+ </symbol>
2432
+ <symbol id="monster-file-icon-html" viewBox="0 0 16 16">
2433
+ <path d="M14 4.5V11h-1V4.5h-2A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v9H2V2a2 2 0 0 1 2-2h5.5z"/>
2434
+ <path d="M4.264 11.85v3.999h-.791v-1.714H1.79v1.714H1V11.85h.791v1.626h1.682V11.85h.79Zm2.251.662v3.337h-.794v-3.337H4.588v-.662h3.064v.662zm2.176 3.337v-2.66h.038l.952 2.159h.516l.946-2.16h.038v2.661h.715V11.85h-.8l-1.14 2.596H9.93L8.79 11.85h-.805v3.999zm4.71-.674h1.696v.674H12.61V11.85h.79v3.325Z"/>
2435
+ </symbol>
2436
+ <symbol id="monster-file-icon-gif" viewBox="0 0 16 16">
2437
+ <path d="M14 4.5V14a2 2 0 0 1-2 2H9v-1h3a1 1 0 0 0 1-1V4.5h-2A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v9H2V2a2 2 0 0 1 2-2h5.5z"/>
2438
+ <path d="M3.278 13.124a1.4 1.4 0 0 0-.14-.492 1.3 1.3 0 0 0-.314-.407 1.5 1.5 0 0 0-.48-.275 1.9 1.9 0 0 0-.636-.1q-.542 0-.926.229a1.5 1.5 0 0 0-.583.632 2.1 2.1 0 0 0-.199.95v.506q0 .408.105.745.105.336.32.58.213.243.533.377.323.132.753.132.402 0 .697-.111a1.29 1.29 0 0 0 .788-.77q.097-.261.097-.551v-.797H1.717v.589h.823v.255q0 .199-.09.363a.67.67 0 0 1-.273.264 1 1 0 0 1-.457.096.87.87 0 0 1-.519-.146.9.9 0 0 1-.305-.413 1.8 1.8 0 0 1-.096-.615v-.499q0-.547.234-.85.237-.3.665-.301a1 1 0 0 1 .3.044q.136.044.236.126a.7.7 0 0 1 .17.19.8.8 0 0 1 .097.25zm1.353 2.801v-3.999H3.84v4h.79Zm1.493-1.59v1.59h-.791v-3.999H7.88v.653H6.124v1.117h1.605v.638z"/>
2439
+ </symbol>
2440
+ <symbol id="monster-file-icon-mp4" viewBox="0 0 16 16">
2441
+ <path d="M14 4.5V14a2 2 0 0 1-2 2v-1a1 1 0 0 0 1-1V4.5h-2A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v9H2V2a2 2 0 0 1 2-2h5.5z"/>
2442
+ <path d="M.706 15.849v-2.66h.038l.952 2.16h.516l.946-2.16h.038v2.66h.715V11.85h-.8l-1.14 2.596h-.026L.805 11.85H0v3.999zm5.237-3.999q-.393.65-.79 1.3t-.748 1.31v.648h1.937v.741h.74v-.741h.49v-.639h-.49V11.85H5.944Zm-.82 2.62v-.021q.27-.51.571-1.017.304-.507.607-.984h.04v2.021H5.124Zm2.893-2.62h1.6q.434 0 .732.179.302.175.46.477t.158.677-.16.677q-.159.298-.464.474a1.45 1.45 0 0 1-.732.173h-.803v1.342h-.79zm2.06 1.714a.8.8 0 0 0 .085-.381q0-.34-.185-.521-.184-.183-.513-.182h-.659v1.406h.66a.8.8 0 0 0 .374-.082.57.57 0 0 0 .238-.24"/>
2443
+ </symbol>
2444
+ <symbol id="monster-file-icon-mov" viewBox="0 0 16 16">
2445
+ <path d="M14 4.5V14a2 2 0 0 1-2 2v-1a1 1 0 0 0 1-1V4.5h-2A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v9H2V2a2 2 0 0 1 2-2h5.5z"/>
2446
+ <path d="M7.086 13.666v.522q0 .384-.117.641a.86.86 0 0 1-.323.387.9.9 0 0 1-.468.126.9.9 0 0 1-.472-.126.87.87 0 0 1-.32-.386 1.55 1.55 0 0 1-.117-.642v-.522q0-.386.118-.641a.87.87 0 0 1 .319-.387.87.87 0 0 1 .472-.129q.263 0 .468.13a.86.86 0 0 1 .323.386q.117.255.117.641m.802.519v-.513q0-.565-.205-.972a1.46 1.46 0 0 0-.588-.63q-.381-.22-.917-.22-.534 0-.92.22a1.44 1.44 0 0 0-.59.627q-.204.406-.204.975v.513q0 .563.205.973.205.406.589.627.386.216.92.216.536 0 .917-.216.383-.22.588-.627.205-.41.205-.973m-7.182 1.74v-2.66h.038l.952 2.16h.516l.946-2.16h.038v2.66h.715v-3.999h-.8l-1.14 2.596h-.026l-1.14-2.596H0v4zm9.54 0h-.952l-1.34-3.999h.918l.896 3.138h.038l.888-3.138h.879z"/>
2447
+ </symbol>
2448
+ <symbol id="monster-file-icon-mp3" viewBox="0 0 16 16">
2449
+ <path d="M14 4.5V14a2 2 0 0 1-2 2v-1a1 1 0 0 0 1-1V4.5h-2A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v9H2V2a2 2 0 0 1 2-2h5.5z"/>
2450
+ <path d="M9.089 14.17h-.443v-.609h.422a.7.7 0 0 0 .322-.073.56.56 0 0 0 .22-.2.5.5 0 0 0 .076-.284.49.49 0 0 0-.176-.392.65.65 0 0 0-.442-.15.7.7 0 0 0-.252.041.6.6 0 0 0-.193.112.5.5 0 0 0-.179.349H7.71q.009-.235.102-.437.094-.202.27-.352.176-.152.428-.237.255-.085.583-.088.418-.003.723.132.304.135.472.372a.9.9 0 0 1 .173.539.83.83 0 0 1-.12.478.96.96 0 0 1-.619.439v.041a1 1 0 0 1 .718.434.9.9 0 0 1 .144.521q.003.285-.117.507a1.1 1.1 0 0 1-.329.378q-.21.152-.486.234-.273.08-.583.08-.451 0-.77-.153a1.2 1.2 0 0 1-.487-.41 1.1 1.1 0 0 1-.178-.563h.726a.46.46 0 0 0 .106.258.7.7 0 0 0 .249.179 1 1 0 0 0 .357.067.9.9 0 0 0 .384-.076.6.6 0 0 0 .252-.217.56.56 0 0 0 .088-.319.56.56 0 0 0-.334-.522.8.8 0 0 0-.372-.079ZM.706 15.925v-2.66h.038l.952 2.16h.516l.946-2.16h.038v2.66h.715v-3.999h-.8l-1.14 2.596h-.026l-1.14-2.596H0v4zm5.458-3.999h-1.6v4h.792v-1.342h.803q.43 0 .732-.173.304-.177.463-.475a1.4 1.4 0 0 0 .161-.677q0-.374-.158-.677a1.2 1.2 0 0 0-.46-.477 1.4 1.4 0 0 0-.733-.179m.545 1.333a.8.8 0 0 1-.085.381.57.57 0 0 1-.237.24.8.8 0 0 1-.375.082h-.66v-1.406h.66q.328 0 .513.182.184.181.184.521"/>
2451
+ </symbol>
2452
+ <symbol id="monster-file-icon-txt" viewBox="0 0 16 16">
2453
+ <path d="M14 4.5V14a2 2 0 0 1-2 2h-2v-1h2a1 1 0 0 0 1-1V4.5h-2A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v9H2V2a2 2 0 0 1 2-2h5.5z"/>
2454
+ <path d="M1.928 15.849v-3.337h1.136v-.662H0v.662h1.134v3.337zm4.689-3.999h-.894L4.9 13.289h-.035l-.832-1.439h-.932l1.228 1.983-1.24 2.016h.862l.853-1.415h.035l.85 1.415h.907l-1.253-1.992zm1.93.662v3.337h-.794v-3.337H6.619v-.662h3.064v.662H8.546Z"/>
2455
+ </symbol>
2456
+ <symbol id="monster-file-icon-png" viewBox="0 0 16 16">
2457
+ <path d="M14 4.5V14a2 2 0 0 1-2 2v-1a1 1 0 0 0 1-1V4.5h-2A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v9H2V2a2 2 0 0 1 2-2h5.5z"/>
2458
+ <path d="M10.24 12.632q.114.23.14.492h-.776a.8.8 0 0 0-.097-.249.7.7 0 0 0-.17-.19.7.7 0 0 0-.237-.126 1 1 0 0 0-.299-.044q-.427 0-.665.302-.234.301-.234.85v.498q0 .351.097.615a.9.9 0 0 0 .304.413.87.87 0 0 0 .519.146 1 1 0 0 0 .457-.096.67.67 0 0 0 .272-.264q.09-.164.091-.363v-.255H8.82v-.59h1.576v.798q0 .29-.097.55a1.3 1.3 0 0 1-.293.458 1.4 1.4 0 0 1-.495.313q-.296.111-.697.111a2 2 0 0 1-.753-.132 1.45 1.45 0 0 1-.533-.377 1.6 1.6 0 0 1-.32-.58 2.5 2.5 0 0 1-.105-.745v-.506q0-.543.2-.95.201-.406.582-.633.384-.228.926-.228.357 0 .636.1.281.1.48.275.2.176.314.407Zm-8.64-.706H0v4h.791v-1.343h.803q.43 0 .732-.172.305-.177.463-.475a1.4 1.4 0 0 0 .161-.677q0-.374-.158-.677a1.2 1.2 0 0 0-.46-.477q-.3-.18-.732-.179m.545 1.333a.8.8 0 0 1-.085.381.57.57 0 0 1-.238.24.8.8 0 0 1-.375.082H.788v-1.406h.66q.327 0 .512.182.185.181.185.521m1.964 2.666V13.25h.032l1.761 2.675h.656v-3.999h-.75v2.66h-.032l-1.752-2.66h-.662v4z"/>
2459
+ </symbol>
2460
+ </svg>
2461
+ `;
2462
+ }
2463
+
2464
+ registerCustomElement(FileManager);