@schukai/monster 4.66.2 → 4.68.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,2397 @@
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 { ATTRIBUTE_BUTTON_LABEL } from "../form/constants.mjs";
43
+ import { getLocaleOfDocument } from "../../dom/locale.mjs";
44
+
45
+ import "../layout/split-panel.mjs";
46
+ import "../layout/panel.mjs";
47
+ import "../layout/tabs.mjs";
48
+ import { State } from "../state/state.mjs";
49
+ import "../tree-menu/tree-menu.mjs";
50
+ import "../form/message-state-button.mjs";
51
+ import "../datatable/datasource/rest.mjs";
52
+
53
+ export { FileManager };
54
+
55
+ void State;
56
+
57
+ /**
58
+ * @private
59
+ * @type {symbol}
60
+ */
61
+ const splitPanelSymbol = Symbol("splitPanel");
62
+
63
+ /**
64
+ * @private
65
+ * @type {symbol}
66
+ */
67
+ const treeMenuSymbol = Symbol("treeMenu");
68
+
69
+ /**
70
+ * @private
71
+ * @type {symbol}
72
+ */
73
+ const tabsSymbol = Symbol("tabs");
74
+ const tabsContainerSymbol = Symbol("tabsContainer");
75
+ const tabsResizeObserverSymbol = Symbol("tabsResizeObserver");
76
+ const hostResizeObserverSymbol = Symbol("hostResizeObserver");
77
+ const editorSizeRafSymbol = Symbol("editorSizeRaf");
78
+
79
+ /**
80
+ * @private
81
+ * @type {symbol}
82
+ */
83
+ const saveButtonSymbol = Symbol("saveButton");
84
+ const titleSymbol = Symbol("title");
85
+
86
+ /**
87
+ * @private
88
+ * @type {symbol}
89
+ */
90
+ const emptyStateSymbol = Symbol("emptyState");
91
+
92
+ /**
93
+ * @private
94
+ * @type {symbol}
95
+ */
96
+ const listDatasourceSymbol = Symbol("listDatasource");
97
+
98
+ /**
99
+ * @private
100
+ * @type {symbol}
101
+ */
102
+ const fileDatasourceSymbol = Symbol("fileDatasource");
103
+ const listDatasourceOwnedSymbol = Symbol("listDatasourceOwned");
104
+ const fileDatasourceOwnedSymbol = Symbol("fileDatasourceOwned");
105
+
106
+ /**
107
+ * @private
108
+ * @type {symbol}
109
+ */
110
+ const tabsByKeySymbol = Symbol("tabsByKey");
111
+
112
+ /**
113
+ * @private
114
+ * @type {symbol}
115
+ */
116
+ const tabsByReferenceSymbol = Symbol("tabsByReference");
117
+
118
+ /**
119
+ * @private
120
+ * @type {symbol}
121
+ */
122
+ const entryByValueSymbol = Symbol("entryByValue");
123
+ const treeEntriesSymbol = Symbol("treeEntries");
124
+ const loadedParentsSymbol = Symbol("loadedParents");
125
+ const listRequestParentSymbol = Symbol("listRequestParent");
126
+ const listRequestInFlightSymbol = Symbol("listRequestInFlight");
127
+ const queuedParentSymbol = Symbol("queuedParent");
128
+
129
+ /**
130
+ * @private
131
+ * @type {symbol}
132
+ */
133
+ const saveAllButtonSymbol = Symbol("saveAllButton");
134
+
135
+ /**
136
+ * A file manager for navigating and editing files.
137
+ *
138
+ * @fragments /fragments/components/files/file-manager/
139
+ *
140
+ * @since 4.44.0
141
+ * @summary A file manager control that builds a tree navigation from an API and opens files in tabs.
142
+ */
143
+ class FileManager extends CustomElement {
144
+ /**
145
+ * This method is called by the `instanceof` operator.
146
+ * @return {symbol}
147
+ */
148
+ static get [instanceSymbol]() {
149
+ return Symbol.for("@schukai/monster/components/files/file-manager");
150
+ }
151
+
152
+ /**
153
+ * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
154
+ * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
155
+ *
156
+ * @property {Object} templates Template definitions
157
+ * @property {string} templates.main Main template
158
+ * @property {Object} labels Label definitions
159
+ * @property {string} labels.title Title label
160
+ * @property {string} labels.save Save button label
161
+ * @property {string} labels.empty Empty state label
162
+ * @property {Object} layout Layout configuration
163
+ * @property {Object} layout.split Split panel options
164
+ * @property {Object} mapping Mapping for list and content
165
+ * @property {string} mapping.data Path to list data in the datasource response
166
+ * @property {string} mapping.id Template or key for entry id
167
+ * @property {string} mapping.parent Template or key for entry parent
168
+ * @property {string} mapping.label Template or key for entry label
169
+ * @property {string} mapping.value Template or key for entry value
170
+ * @property {string} mapping.path Template or key for entry path
171
+ * @property {string} mapping.type Template or key for entry type
172
+ * @property {string} mapping.icon Template or key for entry icon id
173
+ * @property {string} mapping.contentPath Path to content in file read response
174
+ * @property {Object} datasource Datasource definitions
175
+ * @property {Object} datasource.list List datasource options
176
+ * @property {string} datasource.list.selector Selector for existing datasource
177
+ * @property {Object} datasource.list.rest Options for monster-datasource-rest
178
+ * @property {Object} datasource.file File datasource options
179
+ * @property {string} datasource.file.selector Selector for existing datasource
180
+ * @property {Object} datasource.file.rest Options for monster-datasource-rest
181
+ * @property {Object} editor Editor configuration
182
+ * @property {string} editor.tag Tag name for editor element
183
+ * @property {Object} editor.adapter Adapter with getValue/setValue/bindChange
184
+ * @property {Object} tabs Tabs configuration
185
+ * @property {string} tabs.dirtyMarker Marker appended to tab labels
186
+ * @property {Object} icons Icon mapping configuration
187
+ */
188
+ get defaults() {
189
+ return Object.assign({}, super.defaults, {
190
+ templates: {
191
+ main: getTemplate(),
192
+ },
193
+ labels: getTranslations(),
194
+ layout: {
195
+ split: {
196
+ type: "vertical",
197
+ initial: "20%",
198
+ min: "15%",
199
+ max: "45%",
200
+ },
201
+ },
202
+ mapping: {
203
+ data: "dataset",
204
+ id: "id",
205
+ parent: "parent",
206
+ label: "${name}",
207
+ value: "${path}",
208
+ path: "${path}",
209
+ type: "${type}",
210
+ icon: null,
211
+ contentPath: "content",
212
+ },
213
+ datasource: {
214
+ list: {
215
+ selector: null,
216
+ rest: {
217
+ read: {
218
+ url: null,
219
+ },
220
+ },
221
+ },
222
+ file: {
223
+ selector: null,
224
+ rest: {
225
+ read: {
226
+ url: null,
227
+ },
228
+ write: {
229
+ url: null,
230
+ },
231
+ },
232
+ },
233
+ },
234
+ editor: {
235
+ readOnly: false,
236
+ defaultTag: "textarea",
237
+ byMimeType: {
238
+ "image/png": "canvas",
239
+ "image/jpeg": "canvas",
240
+ "image/gif": "canvas",
241
+ "image/svg+xml": "canvas",
242
+ "image/webp": "canvas",
243
+ "image/bmp": "canvas",
244
+ "image/x-icon": "canvas",
245
+ "text/plain": "textarea",
246
+ "text/markdown": "textarea",
247
+ "text/css": "textarea",
248
+ "text/html": "textarea",
249
+ "text/javascript": "textarea",
250
+ "application/json": "textarea",
251
+ },
252
+ },
253
+ tabs: {
254
+ dirtyMarker: "*",
255
+ },
256
+ persistence: {
257
+ enabled: true,
258
+ keyPrefix: "monster-file-manager-diff",
259
+ debounceMs: 300,
260
+ },
261
+ lazy: {
262
+ enabled: false,
263
+ parameter: "path",
264
+ root: null,
265
+ },
266
+ icons: {
267
+ default: "monster-file-icon-default",
268
+ code: "monster-file-icon-code",
269
+ image: "monster-file-icon-image",
270
+ sound: "monster-file-icon-sound",
271
+ pdf: "monster-file-icon-pdf",
272
+ css: "monster-file-icon-css",
273
+ csv: "monster-file-icon-csv",
274
+ doc: "monster-file-icon-doc",
275
+ jpg: "monster-file-icon-jpg",
276
+ html: "monster-file-icon-html",
277
+ gif: "monster-file-icon-gif",
278
+ mp4: "monster-file-icon-mp4",
279
+ mov: "monster-file-icon-mov",
280
+ mp3: "monster-file-icon-mp3",
281
+ txt: "monster-file-icon-txt",
282
+ png: "monster-file-icon-png",
283
+ byExtension: {
284
+ js: "code",
285
+ ts: "code",
286
+ json: "code",
287
+ css: "css",
288
+ scss: "css",
289
+ html: "html",
290
+ htm: "html",
291
+ txt: "txt",
292
+ md: "txt",
293
+ csv: "csv",
294
+ pdf: "pdf",
295
+ png: "png",
296
+ jpg: "jpg",
297
+ jpeg: "jpg",
298
+ gif: "gif",
299
+ svg: "image",
300
+ mp3: "mp3",
301
+ wav: "sound",
302
+ ogg: "sound",
303
+ mp4: "mp4",
304
+ m4p: "mp4",
305
+ mov: "mov",
306
+ },
307
+ },
308
+ debug: false,
309
+ });
310
+ }
311
+
312
+ /**
313
+ * @return {CSSStyleSheet[]}
314
+ */
315
+ static getCSSStyleSheet() {
316
+ return [CommonStyleSheet, ThemeStyleSheet, FileManagerStyleSheet];
317
+ }
318
+
319
+ /**
320
+ * @return {string}
321
+ */
322
+ static getTag() {
323
+ return "monster-file-manager";
324
+ }
325
+
326
+ /**
327
+ * @param {object} options
328
+ * @return {FileManager}
329
+ */
330
+ setOptions(options) {
331
+ super.setOptions(options);
332
+ queueMicrotask(() => {
333
+ syncDatasources.call(this);
334
+ refreshLabels.call(this);
335
+ });
336
+ return this;
337
+ }
338
+
339
+ /**
340
+ * @return {void}
341
+ */
342
+ [assembleMethodSymbol]() {
343
+ super[assembleMethodSymbol]();
344
+
345
+ this[tabsByKeySymbol] = new Map();
346
+ this[tabsByReferenceSymbol] = new Map();
347
+ this[entryByValueSymbol] = new Map();
348
+ this[treeEntriesSymbol] = [];
349
+ this[loadedParentsSymbol] = new Set();
350
+
351
+ queueMicrotask(() => {
352
+ initControlReferences.call(this);
353
+ initSplitPanel.call(this);
354
+ initTabs.call(this);
355
+ initSaveButton.call(this);
356
+ initTreeMenu.call(this);
357
+ initDatasources.call(this);
358
+ });
359
+
360
+ this[internalSymbol].attachObserver(
361
+ new Observer(() => {
362
+ syncDatasources.call(this);
363
+ refreshLabels.call(this);
364
+ }),
365
+ );
366
+ }
367
+ }
368
+
369
+ /**
370
+ * @private
371
+ * @return {FileManager}
372
+ */
373
+ function initControlReferences() {
374
+ if (!this.shadowRoot) {
375
+ throw new Error("no shadow-root is defined");
376
+ }
377
+
378
+ this[titleSymbol] = this.shadowRoot.querySelector(
379
+ "[data-monster-role=title]",
380
+ );
381
+ this[splitPanelSymbol] = this.shadowRoot.querySelector(
382
+ "[data-monster-role=split-panel]",
383
+ );
384
+ this[treeMenuSymbol] = this.shadowRoot.querySelector(
385
+ "[data-monster-role=tree]",
386
+ );
387
+ this[tabsSymbol] = this.shadowRoot.querySelector("[data-monster-role=tabs]");
388
+ this[tabsContainerSymbol] = this.shadowRoot.querySelector(
389
+ "[data-monster-role=tabs-container]",
390
+ );
391
+ this[saveButtonSymbol] = this.shadowRoot.querySelector(
392
+ "[data-monster-role=save-button]",
393
+ );
394
+ this[saveAllButtonSymbol] = this.shadowRoot.querySelector(
395
+ "[data-monster-role=save-all-button]",
396
+ );
397
+ this[emptyStateSymbol] = this.shadowRoot.querySelector(
398
+ "[data-monster-role=empty]",
399
+ );
400
+
401
+ return this;
402
+ }
403
+
404
+ /**
405
+ * @private
406
+ * @return {void}
407
+ */
408
+ function initSplitPanel() {
409
+ if (!this[splitPanelSymbol]) {
410
+ return;
411
+ }
412
+
413
+ const split = this.getOption("layout.split", {});
414
+ const initial = split.initial || "20%";
415
+ this[splitPanelSymbol].setOptions({
416
+ splitType: split.type || "vertical",
417
+ dimension: {
418
+ initial: initial,
419
+ min: split.min || "15%",
420
+ max: split.max || "45%",
421
+ },
422
+ });
423
+ this[splitPanelSymbol].setDimension(initial);
424
+ }
425
+
426
+ /**
427
+ * @private
428
+ * @return {void}
429
+ */
430
+ function initSaveButton() {
431
+ if (!this[saveButtonSymbol]) {
432
+ return;
433
+ }
434
+
435
+ this[saveButtonSymbol].setOption(
436
+ "labels.button",
437
+ this.getOption("labels.save"),
438
+ );
439
+ this[saveButtonSymbol].setOption("disabled", true);
440
+
441
+ this[saveButtonSymbol].setOption("actions.click", () => {
442
+ saveActiveTab.call(this);
443
+ });
444
+
445
+ if (this[saveAllButtonSymbol]) {
446
+ this[saveAllButtonSymbol].setOption(
447
+ "labels.button",
448
+ this.getOption("labels.saveAll"),
449
+ );
450
+ this[saveAllButtonSymbol].setOption("disabled", true);
451
+ this[saveAllButtonSymbol].setOption("actions.click", () => {
452
+ saveAllTabs.call(this);
453
+ });
454
+ }
455
+ }
456
+
457
+ /**
458
+ * @private
459
+ * @return {void}
460
+ */
461
+ function initTabs() {
462
+ if (!this[tabsSymbol]) {
463
+ return;
464
+ }
465
+
466
+ this[tabsSymbol].setOption("debug", this.getOption("debug") === true);
467
+ this[tabsSymbol].setOption("features.openFirst", false);
468
+
469
+ this[tabsSymbol].addEventListener("monster-tab-changed", (event) => {
470
+ const reference = event?.detail?.reference;
471
+ if (!reference) {
472
+ return;
473
+ }
474
+
475
+ updateEmptyState.call(this);
476
+ updateSaveButtonState.call(this);
477
+ scheduleUpdateEditorSizes.call(this);
478
+ });
479
+
480
+ this[tabsSymbol].addEventListener("monster-tab-remove", (event) => {
481
+ const reference = event?.detail?.reference;
482
+ if (!reference) {
483
+ return;
484
+ }
485
+
486
+ const tabData = this[tabsByReferenceSymbol].get(reference);
487
+ if (tabData?.unbindChange) {
488
+ tabData.unbindChange();
489
+ }
490
+
491
+ this[tabsByReferenceSymbol].delete(reference);
492
+ if (tabData?.key) {
493
+ this[tabsByKeySymbol].delete(tabData.key);
494
+ }
495
+
496
+ updateEmptyState.call(this);
497
+ updateSaveButtonState.call(this);
498
+ scheduleUpdateEditorSizes.call(this);
499
+ });
500
+
501
+ initHostResizeObserver.call(this);
502
+ scheduleUpdateEditorSizes.call(this);
503
+ }
504
+
505
+ /**
506
+ * @private
507
+ * @return {void}
508
+ */
509
+ function initTreeMenu() {
510
+ if (!this[treeMenuSymbol]) {
511
+ return;
512
+ }
513
+
514
+ this[treeMenuSymbol].setOptions({
515
+ mapping: getTreeMenuMapping.call(this),
516
+ actions: {
517
+ select: (entry) => {
518
+ const key = entry?.value;
519
+ if (!key) {
520
+ return;
521
+ }
522
+
523
+ const fileEntry = this[entryByValueSymbol].get(key);
524
+ if (!fileEntry) {
525
+ return;
526
+ }
527
+
528
+ openFile.call(this, fileEntry);
529
+ },
530
+ open: (entry) => {
531
+ handleTreeOpen.call(this, entry);
532
+ },
533
+ },
534
+ });
535
+ }
536
+
537
+ /**
538
+ * @private
539
+ * @return {void}
540
+ */
541
+ function initDatasources() {
542
+ initListDatasource.call(this);
543
+ initFileDatasource.call(this);
544
+ }
545
+
546
+ /**
547
+ * @private
548
+ * @return {void}
549
+ */
550
+ function initListDatasource() {
551
+ const selector = this.getOption("datasource.list.selector");
552
+ if (isString(selector) && selector.trim() !== "") {
553
+ const element = findElementWithSelectorUpwards(this, selector.trim());
554
+ if (element instanceof HTMLElement) {
555
+ customElements
556
+ .whenDefined(element.tagName.toLowerCase())
557
+ .then(() => {
558
+ logDebug.call(this, "list datasource found via selector", selector);
559
+ this[listDatasourceSymbol] = element;
560
+ attachListDatasourceListeners.call(this);
561
+ requestList.call(this, this.getOption("lazy.root", null));
562
+ })
563
+ .catch((err) => {
564
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, err.message);
565
+ });
566
+ return;
567
+ }
568
+
569
+ addAttributeToken(
570
+ this,
571
+ ATTRIBUTE_ERRORMESSAGE,
572
+ "list datasource selector not found",
573
+ );
574
+ }
575
+
576
+ const restConfig = this.getOption("datasource.list.rest", {});
577
+ if (!isObject(restConfig)) {
578
+ return;
579
+ }
580
+
581
+ const rest = getDocument().createElement("monster-datasource-rest");
582
+ rest.setOptions(restConfig);
583
+ rest.setOption("features.autoInit", true);
584
+ rest.setOption("autoInit.oneTime", true);
585
+ this.appendChild(rest);
586
+ this[listDatasourceSymbol] = rest;
587
+ this[listDatasourceOwnedSymbol] = true;
588
+ logDebug.call(this, "list datasource created", restConfig?.read?.url);
589
+
590
+ attachListDatasourceListeners.call(this);
591
+ requestList.call(this, this.getOption("lazy.root", null));
592
+ }
593
+
594
+ /**
595
+ * @private
596
+ * @return {void}
597
+ */
598
+ function initFileDatasource() {
599
+ const selector = this.getOption("datasource.file.selector");
600
+ if (isString(selector) && selector.trim() !== "") {
601
+ const element = findElementWithSelectorUpwards(this, selector.trim());
602
+ if (element instanceof HTMLElement) {
603
+ customElements
604
+ .whenDefined(element.tagName.toLowerCase())
605
+ .then(() => {
606
+ this[fileDatasourceSymbol] = element;
607
+ })
608
+ .catch((err) => {
609
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, err.message);
610
+ });
611
+ return;
612
+ }
613
+
614
+ addAttributeToken(
615
+ this,
616
+ ATTRIBUTE_ERRORMESSAGE,
617
+ "file datasource selector not found",
618
+ );
619
+ }
620
+
621
+ const restConfig = this.getOption("datasource.file.rest", {});
622
+ if (!isObject(restConfig)) {
623
+ return;
624
+ }
625
+
626
+ const rest = getDocument().createElement("monster-datasource-rest");
627
+ rest.setOptions(restConfig);
628
+ this.appendChild(rest);
629
+ this[fileDatasourceSymbol] = rest;
630
+ this[fileDatasourceOwnedSymbol] = true;
631
+ }
632
+
633
+ /**
634
+ * @private
635
+ * @return {void}
636
+ */
637
+ function attachListDatasourceListeners() {
638
+ const datasource = this[listDatasourceSymbol];
639
+ if (!(datasource instanceof HTMLElement)) {
640
+ return;
641
+ }
642
+
643
+ const hasObserver = !!datasource.datasource?.attachObserver;
644
+ if (!hasObserver) {
645
+ datasource.addEventListener("monster-datasource-fetched", () => {
646
+ logDebug.call(this, "list datasource fetched");
647
+ applyListData.call(this);
648
+ });
649
+ }
650
+ datasource.addEventListener("monster-datasource-error", (event) => {
651
+ logDebug.call(this, "list datasource error", event?.detail?.error?.message);
652
+ clearListRequest.call(this);
653
+ });
654
+
655
+ if (datasource.datasource && hasObserver) {
656
+ datasource.datasource.attachObserver(
657
+ new Observer(() => {
658
+ logDebug.call(this, "list datasource fetched");
659
+ applyListData.call(this);
660
+ }),
661
+ );
662
+ }
663
+ }
664
+
665
+ /**
666
+ * @private
667
+ * @return {void}
668
+ */
669
+ function applyListData() {
670
+ const datasource = this[listDatasourceSymbol];
671
+ if (!datasource || !datasource.data) {
672
+ logDebug.call(this, "list datasource has no data");
673
+ return;
674
+ }
675
+
676
+ let data = datasource.data;
677
+ const dataPath = this.getOption("mapping.data");
678
+
679
+ if (isString(dataPath) && dataPath.trim() !== "") {
680
+ const pathfinder = new Pathfinder(data);
681
+ if (pathfinder.exists(dataPath)) {
682
+ data = pathfinder.getVia(dataPath);
683
+ } else {
684
+ logDebug.call(
685
+ this,
686
+ "list datasource path not found, falling back to root data",
687
+ dataPath,
688
+ );
689
+ }
690
+ }
691
+
692
+ if (isObject(data)) {
693
+ data = Object.values(data);
694
+ }
695
+
696
+ if (!isArray(data)) {
697
+ data = [];
698
+ }
699
+
700
+ const lazyEnabled = this.getOption("lazy.enabled", false) === true;
701
+ const listParent = lazyEnabled
702
+ ? (this[listRequestParentSymbol] ?? this.getOption("lazy.root", null))
703
+ : null;
704
+
705
+ const entries = [];
706
+ const entryByValue = new Map();
707
+
708
+ for (const entry of data) {
709
+ const normalized = normalizeEntry.call(this, entry);
710
+ if (lazyEnabled) {
711
+ if (!matchesParent(normalized.parent, listParent)) {
712
+ continue;
713
+ }
714
+ }
715
+ entries.push(normalized);
716
+ entryByValue.set(normalized.value, normalized);
717
+ }
718
+
719
+ logDebug.call(
720
+ this,
721
+ "list entries normalized",
722
+ lazyEnabled
723
+ ? `${entries.length} (parent ${listParent ?? "root"})`
724
+ : entries.length,
725
+ );
726
+
727
+ if (lazyEnabled) {
728
+ mergeLazyEntries.call(this, entries, listParent);
729
+ } else {
730
+ this[entryByValueSymbol] = entryByValue;
731
+ const treeEntries = buildTreeEntries.call(this, entries);
732
+ this[treeEntriesSymbol] = treeEntries;
733
+ if (this[treeMenuSymbol]) {
734
+ this[treeMenuSymbol].setOption("entries", treeEntries);
735
+ }
736
+ }
737
+
738
+ clearListRequest.call(this);
739
+ updateEmptyState.call(this);
740
+ }
741
+
742
+ /**
743
+ * @private
744
+ * @param {object} entry
745
+ * @return {object}
746
+ */
747
+ function normalizeEntry(entry) {
748
+ const mapping = this.getOption("mapping", {});
749
+ const formatted = {
750
+ id: formatEntryValue(entry, mapping.id),
751
+ parent: formatEntryValue(entry, mapping.parent),
752
+ label: formatEntryValue(entry, mapping.label),
753
+ value: formatEntryValue(entry, mapping.value),
754
+ path: formatEntryValue(entry, mapping.path),
755
+ type: formatEntryValue(entry, mapping.type),
756
+ icon: formatEntryValue(entry, mapping.icon),
757
+ hasChildren:
758
+ entry?.hasChildren === true ||
759
+ entry?.["has-children"] === true ||
760
+ entry?.has_children === true,
761
+ raw: entry,
762
+ };
763
+
764
+ if (!formatted.path && isString(formatted.value)) {
765
+ formatted.path = formatted.value;
766
+ }
767
+
768
+ if (!formatted.value && isString(formatted.path)) {
769
+ formatted.value = formatted.path;
770
+ }
771
+
772
+ if (!formatted.id && isString(formatted.path)) {
773
+ formatted.id = formatted.path;
774
+ }
775
+
776
+ if (!formatted.parent && isString(formatted.path)) {
777
+ const parent = formatted.path.split("/").slice(0, -1).join("/");
778
+ formatted.parent = parent === "" ? null : parent;
779
+ }
780
+
781
+ if (!formatted.label) {
782
+ formatted.label = formatted.path || formatted.id || "file";
783
+ }
784
+
785
+ if (!formatted.type && isString(formatted.path)) {
786
+ formatted.type = getTypeFromPath(formatted.path);
787
+ }
788
+
789
+ if (formatted.hasChildren !== true) {
790
+ formatted.hasChildren = formatted.type === "folder";
791
+ }
792
+
793
+ if (!formatted.icon) {
794
+ formatted.icon = getIconIdForEntry.call(this, formatted);
795
+ }
796
+
797
+ return formatted;
798
+ }
799
+
800
+ /**
801
+ * @private
802
+ * @param {object} entry
803
+ * @return {string}
804
+ */
805
+ function getIconIdForEntry(entry) {
806
+ const icons = this.getOption("icons", {});
807
+ const byExtension = icons.byExtension || {};
808
+ const type = isString(entry.type) ? entry.type.toLowerCase() : "";
809
+ const ext = getExtension(entry.path || "");
810
+ let iconKey = "";
811
+
812
+ if (ext && byExtension[ext]) {
813
+ iconKey = byExtension[ext];
814
+ } else if (type && icons[type]) {
815
+ iconKey = type;
816
+ }
817
+
818
+ if (!iconKey || !icons[iconKey]) {
819
+ return icons.default;
820
+ }
821
+
822
+ return icons[iconKey];
823
+ }
824
+
825
+ /**
826
+ * @private
827
+ * @param {string} path
828
+ * @return {string}
829
+ */
830
+ function getTypeFromPath(path) {
831
+ const ext = getExtension(path);
832
+ if (!ext) {
833
+ return "file";
834
+ }
835
+ return ext.toLowerCase();
836
+ }
837
+
838
+ /**
839
+ * @private
840
+ * @param {string} path
841
+ * @return {string}
842
+ */
843
+ function getExtension(path) {
844
+ if (!isString(path)) {
845
+ return "";
846
+ }
847
+
848
+ const last = path.split("/").pop() || "";
849
+ const index = last.lastIndexOf(".");
850
+ if (index === -1) {
851
+ return "";
852
+ }
853
+
854
+ return last.substring(index + 1).toLowerCase();
855
+ }
856
+
857
+ /**
858
+ * @private
859
+ * @param {object} entry
860
+ * @param {string} template
861
+ * @return {string}
862
+ */
863
+ function formatEntryValue(entry, template) {
864
+ if (!isString(template) || template.trim() === "") {
865
+ return "";
866
+ }
867
+
868
+ if (template.includes("${")) {
869
+ try {
870
+ return new Formatter(entry).format(template);
871
+ } catch (e) {
872
+ return "";
873
+ }
874
+ }
875
+
876
+ if (entry && Object.prototype.hasOwnProperty.call(entry, template)) {
877
+ return entry[template];
878
+ }
879
+
880
+ return "";
881
+ }
882
+
883
+ /**
884
+ * @private
885
+ * @return {object}
886
+ */
887
+ function getTreeMenuMapping() {
888
+ return {
889
+ rootReferences: [null, undefined, 0, "0", ""],
890
+ idTemplate: "id",
891
+ parentKey: "parent",
892
+ selector: "*",
893
+ labelTemplate: "${label}",
894
+ valueTemplate: "${value}",
895
+ iconTemplate: "${icon}",
896
+ };
897
+ }
898
+
899
+ /**
900
+ * @private
901
+ * @param {object} entry
902
+ * @return {void}
903
+ */
904
+ function handleTreeOpen(entry) {
905
+ const lazyEnabled = this.getOption("lazy.enabled", false) === true;
906
+ if (!lazyEnabled) {
907
+ return;
908
+ }
909
+ const key = entry?.value;
910
+ if (!key) {
911
+ return;
912
+ }
913
+ const normalized = this[entryByValueSymbol].get(key);
914
+ if (!normalized || normalized.type !== "folder") {
915
+ return;
916
+ }
917
+ if (normalized.hasChildren !== true) {
918
+ return;
919
+ }
920
+
921
+ if (this[loadedParentsSymbol].has(normalized.path)) {
922
+ return;
923
+ }
924
+
925
+ requestList.call(this, normalized.path);
926
+ }
927
+
928
+ /**
929
+ * @private
930
+ * @param {Array} entries
931
+ * @return {Array}
932
+ */
933
+ function buildTreeEntries(entries) {
934
+ const nodes = buildTree(entries, "*", "id", "parent", {
935
+ rootReferences: [null, undefined, ""],
936
+ });
937
+
938
+ const options = [];
939
+ for (const node of nodes) {
940
+ const iterator = new NodeRecursiveIterator(node);
941
+ for (const n of iterator) {
942
+ const entry = n.value || {};
943
+ const intend = n.level;
944
+ const visibility = intend > 0 ? "hidden" : "visible";
945
+ options.push({
946
+ value: entry.value,
947
+ label: entry.label,
948
+ icon: buildIconMarkup(entry.icon),
949
+ intend,
950
+ state: "close",
951
+ visibility,
952
+ ["has-children"]: n.hasChildNodes(),
953
+ });
954
+ }
955
+ }
956
+
957
+ return options;
958
+ }
959
+
960
+ /**
961
+ * @private
962
+ * @param {string} iconId
963
+ * @return {string}
964
+ */
965
+ function buildIconMarkup(iconId) {
966
+ const id = isString(iconId) ? iconId : "";
967
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><use xlink:href="#${id}"></use></svg>`;
968
+ }
969
+
970
+ /**
971
+ * @private
972
+ * @param {Array} entries
973
+ * @param {string|null} parentValue
974
+ * @return {void}
975
+ */
976
+ function mergeLazyEntries(entries, parentValue) {
977
+ let treeEntries = Array.isArray(this[treeEntriesSymbol])
978
+ ? this[treeEntriesSymbol].slice()
979
+ : [];
980
+ const loadedParents = this[loadedParentsSymbol] || new Set();
981
+
982
+ if (parentValue === null || parentValue === undefined || parentValue === "") {
983
+ treeEntries = [];
984
+ this[entryByValueSymbol] = new Map();
985
+ loadedParents.clear();
986
+ }
987
+
988
+ const parentIndex = findTreeEntryIndex(treeEntries, parentValue);
989
+ const parentIntend = parentIndex >= 0 ? treeEntries[parentIndex].intend : -1;
990
+ const parentState =
991
+ parentIndex >= 0 ? treeEntries[parentIndex].state : "open";
992
+ const baseVisibility = parentState === "open" ? "visible" : "hidden";
993
+
994
+ const insertIndex =
995
+ parentIndex >= 0
996
+ ? getSubtreeEndIndex(treeEntries, parentIndex)
997
+ : treeEntries.length;
998
+
999
+ const newEntries = [];
1000
+ let hasChildren = entries.length > 0;
1001
+
1002
+ for (const entry of entries) {
1003
+ if (!entry?.value) {
1004
+ continue;
1005
+ }
1006
+ if (this[entryByValueSymbol].has(entry.value)) {
1007
+ continue;
1008
+ }
1009
+
1010
+ this[entryByValueSymbol].set(entry.value, entry);
1011
+
1012
+ newEntries.push({
1013
+ value: entry.value,
1014
+ label: entry.label,
1015
+ icon: buildIconMarkup(entry.icon),
1016
+ intend: parentIntend + 1,
1017
+ state: "close",
1018
+ visibility: parentIndex >= 0 ? baseVisibility : "visible",
1019
+ ["has-children"]: entry.hasChildren === true,
1020
+ });
1021
+ }
1022
+
1023
+ const nextEntries = [
1024
+ ...treeEntries.slice(0, insertIndex),
1025
+ ...newEntries,
1026
+ ...treeEntries.slice(insertIndex),
1027
+ ];
1028
+
1029
+ if (parentIndex >= 0) {
1030
+ nextEntries[parentIndex] = Object.assign({}, nextEntries[parentIndex], {
1031
+ ["has-children"]: hasChildren,
1032
+ state: hasChildren ? nextEntries[parentIndex].state : "close",
1033
+ });
1034
+ }
1035
+
1036
+ this[treeEntriesSymbol] = nextEntries;
1037
+ loadedParents.add(parentValue ?? null);
1038
+ this[loadedParentsSymbol] = loadedParents;
1039
+
1040
+ if (this[treeMenuSymbol]) {
1041
+ this[treeMenuSymbol].setOption("entries", nextEntries);
1042
+ }
1043
+ }
1044
+
1045
+ /**
1046
+ * @private
1047
+ * @param {Array} entries
1048
+ * @param {string|null} value
1049
+ * @return {number}
1050
+ */
1051
+ function findTreeEntryIndex(entries, value) {
1052
+ if (!isString(value) || value.trim() === "") {
1053
+ return -1;
1054
+ }
1055
+ return entries.findIndex((entry) => entry.value === value);
1056
+ }
1057
+
1058
+ /**
1059
+ * @private
1060
+ * @param {Array} entries
1061
+ * @param {number} index
1062
+ * @return {number}
1063
+ */
1064
+ function getSubtreeEndIndex(entries, index) {
1065
+ if (index < 0 || index >= entries.length) {
1066
+ return entries.length;
1067
+ }
1068
+ const baseIntend = entries[index].intend;
1069
+ let i = index + 1;
1070
+ while (i < entries.length && entries[i].intend > baseIntend) {
1071
+ i += 1;
1072
+ }
1073
+ return i;
1074
+ }
1075
+
1076
+ /**
1077
+ * @private
1078
+ * @param {string|null|undefined} entryParent
1079
+ * @param {string|null|undefined} expectedParent
1080
+ * @return {boolean}
1081
+ */
1082
+ function matchesParent(entryParent, expectedParent) {
1083
+ const normalized = entryParent ?? null;
1084
+ const expected = expectedParent ?? null;
1085
+ if (expected === null) {
1086
+ return normalized === null || normalized === "" || normalized === undefined;
1087
+ }
1088
+ return normalized === expected;
1089
+ }
1090
+
1091
+ /**
1092
+ * @private
1093
+ * @param {string|null} parentValue
1094
+ * @return {void}
1095
+ */
1096
+ function requestList(parentValue) {
1097
+ const datasource = this[listDatasourceSymbol];
1098
+ if (!datasource?.read) {
1099
+ return;
1100
+ }
1101
+
1102
+ const isRestDatasource =
1103
+ datasource?.tagName?.toLowerCase?.() === "monster-datasource-rest";
1104
+ if (isRestDatasource) {
1105
+ const readUrl = getListReadUrl.call(this, parentValue);
1106
+ if (!isString(readUrl) || readUrl.trim() === "") {
1107
+ logDebug.call(this, "list datasource read url missing");
1108
+ return;
1109
+ }
1110
+ datasource.setOption("read.url", readUrl);
1111
+ }
1112
+
1113
+ if (this[listRequestInFlightSymbol] === true) {
1114
+ this[queuedParentSymbol] = parentValue;
1115
+ return;
1116
+ }
1117
+
1118
+ const paramName = this.getOption("lazy.parameter", "path");
1119
+ const params = {};
1120
+ if (isString(paramName) && paramName.trim() !== "") {
1121
+ params[paramName] = parentValue ?? "";
1122
+ }
1123
+
1124
+ this[listRequestInFlightSymbol] = true;
1125
+ this[listRequestParentSymbol] = parentValue ?? null;
1126
+ logDebug.call(this, "list request", parentValue ?? "root");
1127
+ datasource.setOption("read.parameters", params);
1128
+ datasource.read();
1129
+ }
1130
+
1131
+ /**
1132
+ * @private
1133
+ * @param {string|null} parentValue
1134
+ * @return {string|null}
1135
+ */
1136
+ function getListReadUrl(parentValue) {
1137
+ const baseUrl = this.getOption("datasource.list.rest.read.url");
1138
+ const fallbackUrl = this[listDatasourceSymbol]?.getOption?.("read.url");
1139
+ const url =
1140
+ isString(baseUrl) && baseUrl.trim() !== "" ? baseUrl : fallbackUrl;
1141
+
1142
+ if (!isString(url) || url.trim() === "") {
1143
+ return null;
1144
+ }
1145
+
1146
+ const trimmed = url.trim();
1147
+ if (trimmed.includes("${")) {
1148
+ return trimmed;
1149
+ }
1150
+
1151
+ const lazyEnabled = this.getOption("lazy.enabled", false) === true;
1152
+ if (!lazyEnabled) {
1153
+ return trimmed;
1154
+ }
1155
+
1156
+ const paramName = this.getOption("lazy.parameter", "path");
1157
+ if (!isString(paramName) || paramName.trim() === "") {
1158
+ return trimmed;
1159
+ }
1160
+
1161
+ const encoded = encodeURIComponent(parentValue ?? "");
1162
+ const escaped = escapeRegExp(paramName);
1163
+ const hasParam = new RegExp(`([?&])${escaped}=`).test(trimmed);
1164
+ const glue = trimmed.includes("?") ? "&" : "?";
1165
+
1166
+ if (hasParam) {
1167
+ return trimmed.replace(
1168
+ new RegExp(`([?&])${escaped}=[^&]*`),
1169
+ `$1${paramName}=${encoded}`,
1170
+ );
1171
+ }
1172
+
1173
+ return `${trimmed}${glue}${paramName}=${encoded}`;
1174
+ }
1175
+
1176
+ /**
1177
+ * @private
1178
+ * @param {string} value
1179
+ * @return {string}
1180
+ */
1181
+ function escapeRegExp(value) {
1182
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1183
+ }
1184
+
1185
+ /**
1186
+ * @private
1187
+ * @return {void}
1188
+ */
1189
+ function clearListRequest() {
1190
+ this[listRequestInFlightSymbol] = false;
1191
+ this[listRequestParentSymbol] = null;
1192
+ const queued = this[queuedParentSymbol];
1193
+ this[queuedParentSymbol] = null;
1194
+ if (queued !== null && queued !== undefined) {
1195
+ requestList.call(this, queued);
1196
+ }
1197
+ }
1198
+
1199
+ /**
1200
+ * @private
1201
+ * @param {object} entry
1202
+ * @return {void}
1203
+ */
1204
+ function openFile(entry) {
1205
+ if (entry?.type === "folder") {
1206
+ return;
1207
+ }
1208
+ const key = entry.value || entry.id;
1209
+ if (!key) {
1210
+ return;
1211
+ }
1212
+
1213
+ const existing = this[tabsByKeySymbol].get(key);
1214
+ if (existing) {
1215
+ this[tabsSymbol]?.activeTab(existing.reference);
1216
+ return;
1217
+ }
1218
+
1219
+ const editor = createEditorElement.call(this, entry);
1220
+ editor.setAttribute("data-monster-role", "editor");
1221
+
1222
+ const tabReference = new ID("file-tab").toString();
1223
+ this[tabsSymbol]?.addTab(editor, {
1224
+ tabId: tabReference,
1225
+ label: entry.label,
1226
+ active: true,
1227
+ removable: true,
1228
+ });
1229
+
1230
+ const tabElement = this[tabsSymbol]?.querySelector(
1231
+ `#${CSS.escape(tabReference)}`,
1232
+ );
1233
+ if (!(tabElement instanceof HTMLElement)) {
1234
+ throw new Error("tab element not found");
1235
+ }
1236
+ tabElement.setAttribute("data-monster-role", "tab-content");
1237
+
1238
+ const adapter = getEditorAdapter.call(this);
1239
+ adapter.setReadOnly(editor, this.getOption("editor.readOnly", false));
1240
+ const tabData = {
1241
+ key,
1242
+ reference: tabReference,
1243
+ entry: entry,
1244
+ editor: editor,
1245
+ adapter,
1246
+ original: "",
1247
+ dirty: false,
1248
+ tabElement,
1249
+ label: entry.label,
1250
+ unbindChange: null,
1251
+ };
1252
+
1253
+ tabData.unbindChange = adapter.bindChange(editor, () => {
1254
+ handleEditorChange.call(this, tabData);
1255
+ });
1256
+
1257
+ this[tabsByKeySymbol].set(key, tabData);
1258
+ this[tabsByReferenceSymbol].set(tabReference, tabData);
1259
+
1260
+ this[tabsSymbol]?.activeTab(tabReference);
1261
+ updateEmptyState.call(this);
1262
+ updateSaveButtonState.call(this);
1263
+ scheduleUpdateEditorSizes.call(this);
1264
+
1265
+ loadFileContent.call(this, tabData).catch((err) => {
1266
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, err.message);
1267
+ });
1268
+ }
1269
+
1270
+ /**
1271
+ * @private
1272
+ * @return {void}
1273
+ */
1274
+ function initTabsResizeObserver() {
1275
+ if (this[tabsResizeObserverSymbol]) {
1276
+ return;
1277
+ }
1278
+ if (!this[tabsSymbol]) {
1279
+ return;
1280
+ }
1281
+ if (typeof ResizeObserver === "undefined") {
1282
+ return;
1283
+ }
1284
+
1285
+ this[tabsResizeObserverSymbol] = new ResizeObserver(() => {
1286
+ scheduleUpdateEditorSizes.call(this);
1287
+ });
1288
+ this[tabsResizeObserverSymbol].observe(this[tabsSymbol]);
1289
+ }
1290
+
1291
+ /**
1292
+ * @private
1293
+ * @return {void}
1294
+ */
1295
+ function initHostResizeObserver() {
1296
+ if (this[hostResizeObserverSymbol]) {
1297
+ return;
1298
+ }
1299
+ if (typeof ResizeObserver === "undefined") {
1300
+ return;
1301
+ }
1302
+
1303
+ this[hostResizeObserverSymbol] = new ResizeObserver(() => {
1304
+ scheduleUpdateEditorSizes.call(this);
1305
+ });
1306
+ this[hostResizeObserverSymbol].observe(this);
1307
+ }
1308
+
1309
+ /**
1310
+ * @private
1311
+ * @return {void}
1312
+ */
1313
+ function scheduleUpdateEditorSizes() {
1314
+ const win = getWindow();
1315
+ if (!win) {
1316
+ return;
1317
+ }
1318
+ if (this[editorSizeRafSymbol]) {
1319
+ win.cancelAnimationFrame(this[editorSizeRafSymbol]);
1320
+ }
1321
+ this[editorSizeRafSymbol] = win.requestAnimationFrame(() => {
1322
+ this[editorSizeRafSymbol] = null;
1323
+ updateEditorSizes.call(this);
1324
+ });
1325
+ }
1326
+
1327
+ /**
1328
+ * @private
1329
+ * @return {void}
1330
+ */
1331
+ function updateEditorSizes() {
1332
+ if (!this[tabsSymbol]) {
1333
+ return;
1334
+ }
1335
+
1336
+ const tabsElement = this[tabsSymbol];
1337
+ const container = this[tabsContainerSymbol] || tabsElement;
1338
+ const containerRect = container.getBoundingClientRect();
1339
+ if (containerRect.height <= 0) {
1340
+ return;
1341
+ }
1342
+
1343
+ const nav = tabsElement.shadowRoot?.querySelector?.(
1344
+ "[data-monster-role=nav]",
1345
+ );
1346
+ const navHeight = nav?.getBoundingClientRect?.().height || 0;
1347
+ const available = Math.max(0, containerRect.height - navHeight);
1348
+
1349
+ for (const tabData of this[tabsByReferenceSymbol].values()) {
1350
+ const editor = tabData?.editor;
1351
+ if (!(editor instanceof HTMLElement)) {
1352
+ continue;
1353
+ }
1354
+ editor.style.height = `${available}px`;
1355
+ editor.style.minHeight = `${available}px`;
1356
+ if (isCanvasElement(editor)) {
1357
+ editor.width = Math.max(1, Math.floor(containerRect.width));
1358
+ editor.height = Math.max(1, Math.floor(available));
1359
+ renderCanvasFromValue(editor, editor.dataset.monsterValue || "");
1360
+ }
1361
+ }
1362
+ }
1363
+
1364
+ /**
1365
+ * @private
1366
+ * @param {object} tabData
1367
+ * @return {Promise<void>}
1368
+ */
1369
+ async function loadFileContent(tabData) {
1370
+ const datasource = this[fileDatasourceSymbol];
1371
+ if (!datasource?.read) {
1372
+ const content = tabData.entry?.raw?.content || "";
1373
+ const patched = applyPersistedDiff.call(this, content, tabData.entry);
1374
+ tabData.adapter.setValue(tabData.editor, patched.value);
1375
+ tabData.original = content;
1376
+ updateTabDirty.call(this, tabData, patched.dirty);
1377
+ return;
1378
+ }
1379
+
1380
+ const params = buildParameters(tabData.entry);
1381
+ const readUrl = datasource.getOption?.("read.url");
1382
+
1383
+ if (!isString(readUrl) || readUrl.trim() === "") {
1384
+ const content = tabData.entry?.raw?.content || "";
1385
+ const patched = applyPersistedDiff.call(this, content, tabData.entry);
1386
+ tabData.adapter.setValue(tabData.editor, patched.value);
1387
+ tabData.original = content;
1388
+ updateTabDirty.call(this, tabData, patched.dirty);
1389
+ return;
1390
+ }
1391
+
1392
+ datasource.setOption("read.parameters", params);
1393
+
1394
+ await datasource.read();
1395
+
1396
+ const data = datasource.data;
1397
+ const content = extractContent.call(this, data);
1398
+ const patched = applyPersistedDiff.call(this, content, tabData.entry);
1399
+ tabData.adapter.setValue(tabData.editor, patched.value);
1400
+ tabData.original = content;
1401
+ updateTabDirty.call(this, tabData, patched.dirty);
1402
+ }
1403
+
1404
+ /**
1405
+ * @private
1406
+ * @param {object} data
1407
+ * @return {string}
1408
+ */
1409
+ function extractContent(data) {
1410
+ if (isString(data)) {
1411
+ return data;
1412
+ }
1413
+
1414
+ const path = this.getOption("mapping.contentPath");
1415
+ if (isString(path) && path.trim() !== "") {
1416
+ try {
1417
+ const value = new Pathfinder(data).getVia(path);
1418
+ if (isString(value)) {
1419
+ return value;
1420
+ }
1421
+ } catch (e) {
1422
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
1423
+ }
1424
+ }
1425
+
1426
+ if (isString(data?.content)) {
1427
+ return data.content;
1428
+ }
1429
+
1430
+ if (isString(data?.file?.content)) {
1431
+ return data.file.content;
1432
+ }
1433
+
1434
+ return "";
1435
+ }
1436
+
1437
+ /**
1438
+ * @private
1439
+ * @param {object} tabData
1440
+ * @return {void}
1441
+ */
1442
+ function handleEditorChange(tabData) {
1443
+ const value = tabData.adapter.getValue(tabData.editor);
1444
+ const isDirty = value !== tabData.original;
1445
+ updateTabDirty.call(this, tabData, isDirty);
1446
+ schedulePersistDiff.call(this, tabData);
1447
+ updateSaveButtonState.call(this);
1448
+ }
1449
+
1450
+ /**
1451
+ * @private
1452
+ * @param {object} tabData
1453
+ * @param {boolean} dirty
1454
+ * @return {void}
1455
+ */
1456
+ function updateTabDirty(tabData, dirty) {
1457
+ const marker = this.getOption("tabs.dirtyMarker", "*");
1458
+ const label = dirty ? `${tabData.label}${marker}` : tabData.label;
1459
+ const current = tabData.tabElement?.getAttribute?.(ATTRIBUTE_BUTTON_LABEL);
1460
+ if (tabData.dirty === dirty && current === label) {
1461
+ return;
1462
+ }
1463
+ tabData.dirty = dirty;
1464
+ if (current !== label) {
1465
+ tabData.tabElement.setAttribute(ATTRIBUTE_BUTTON_LABEL, label);
1466
+ }
1467
+ }
1468
+
1469
+ /**
1470
+ * @private
1471
+ * @return {void}
1472
+ */
1473
+ function updateEmptyState() {
1474
+ if (!this[emptyStateSymbol]) {
1475
+ return;
1476
+ }
1477
+
1478
+ const hasTabs = this[tabsByReferenceSymbol].size > 0;
1479
+ if (hasTabs) {
1480
+ this[emptyStateSymbol].classList.add("hidden");
1481
+ } else {
1482
+ this[emptyStateSymbol].classList.remove("hidden");
1483
+ }
1484
+ }
1485
+
1486
+ /**
1487
+ * @private
1488
+ * @return {void}
1489
+ */
1490
+ function updateSaveButtonState() {
1491
+ if (!this[saveButtonSymbol]) {
1492
+ return;
1493
+ }
1494
+
1495
+ const active = getActiveTabData.call(this);
1496
+ const enabled = active?.dirty === true;
1497
+ this[saveButtonSymbol].setOption("disabled", !enabled);
1498
+
1499
+ if (this[saveAllButtonSymbol]) {
1500
+ const anyDirty = Array.from(this[tabsByReferenceSymbol].values()).some(
1501
+ (tab) => tab.dirty === true,
1502
+ );
1503
+ this[saveAllButtonSymbol].setOption("disabled", !anyDirty);
1504
+ }
1505
+ }
1506
+
1507
+ /**
1508
+ * @private
1509
+ * @return {object|null}
1510
+ */
1511
+ function getActiveTabData() {
1512
+ if (!this[tabsSymbol]) {
1513
+ return null;
1514
+ }
1515
+
1516
+ const reference = this[tabsSymbol].getActiveTab?.();
1517
+ if (!reference) {
1518
+ return null;
1519
+ }
1520
+
1521
+ return this[tabsByReferenceSymbol].get(reference) || null;
1522
+ }
1523
+
1524
+ /**
1525
+ * @private
1526
+ * @return {void}
1527
+ */
1528
+ function saveActiveTab() {
1529
+ const tabData = getActiveTabData.call(this);
1530
+ if (!tabData || tabData.dirty !== true) {
1531
+ return;
1532
+ }
1533
+
1534
+ if (this[saveButtonSymbol]) {
1535
+ this[saveButtonSymbol].setState("activity");
1536
+ }
1537
+
1538
+ saveTab
1539
+ .call(this, tabData)
1540
+ .then(() => {
1541
+ if (this[saveButtonSymbol]) {
1542
+ this[saveButtonSymbol].setState("successful", 1200);
1543
+ }
1544
+ })
1545
+ .catch((err) => {
1546
+ if (this[saveButtonSymbol]) {
1547
+ this[saveButtonSymbol].setState("failed", 1600);
1548
+ }
1549
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, err.message);
1550
+ })
1551
+ .finally(() => {
1552
+ updateSaveButtonState.call(this);
1553
+ });
1554
+ }
1555
+
1556
+ /**
1557
+ * @private
1558
+ * @param {object} tabData
1559
+ * @return {Promise<void>}
1560
+ */
1561
+ async function saveTab(tabData) {
1562
+ const datasource = this[fileDatasourceSymbol];
1563
+ if (!datasource?.write) {
1564
+ throw new Error("file datasource not available");
1565
+ }
1566
+
1567
+ const payload = buildWritePayload.call(this, tabData);
1568
+ const params = buildParameters(tabData.entry);
1569
+ datasource.data = payload;
1570
+ datasource.setOption("write.parameters", params);
1571
+
1572
+ await datasource.write();
1573
+
1574
+ tabData.original = tabData.adapter.getValue(tabData.editor);
1575
+ updateTabDirty.call(this, tabData, false);
1576
+ clearPersistedDiff.call(this, tabData.entry);
1577
+ }
1578
+
1579
+ /**
1580
+ * @private
1581
+ * @return {void}
1582
+ */
1583
+ function saveAllTabs() {
1584
+ if (!this[saveAllButtonSymbol]) {
1585
+ return;
1586
+ }
1587
+
1588
+ const dirtyTabs = Array.from(this[tabsByReferenceSymbol].values()).filter(
1589
+ (tab) => tab.dirty === true,
1590
+ );
1591
+
1592
+ if (dirtyTabs.length === 0) {
1593
+ return;
1594
+ }
1595
+
1596
+ this[saveAllButtonSymbol].setState("activity");
1597
+
1598
+ Promise.all(dirtyTabs.map((tab) => saveTab.call(this, tab)))
1599
+ .then(() => {
1600
+ this[saveAllButtonSymbol].setState("successful", 1200);
1601
+ })
1602
+ .catch((err) => {
1603
+ this[saveAllButtonSymbol].setState("failed", 1600);
1604
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, err.message);
1605
+ })
1606
+ .finally(() => {
1607
+ updateSaveButtonState.call(this);
1608
+ });
1609
+ }
1610
+
1611
+ /**
1612
+ * @private
1613
+ * @param {object} tabData
1614
+ * @return {object}
1615
+ */
1616
+ function buildWritePayload(tabData) {
1617
+ const custom = this.getOption("actions.buildPayload");
1618
+ if (isFunction(custom)) {
1619
+ return custom(tabData);
1620
+ }
1621
+
1622
+ const entry = tabData.entry || {};
1623
+ const content = tabData.adapter.getValue(tabData.editor);
1624
+ return {
1625
+ id: entry.id,
1626
+ path: entry.path,
1627
+ name: entry.label,
1628
+ type: entry.type,
1629
+ content: content,
1630
+ };
1631
+ }
1632
+
1633
+ /**
1634
+ * @private
1635
+ * @param {object} entry
1636
+ * @return {object}
1637
+ */
1638
+ function buildParameters(entry) {
1639
+ const payload = clone(entry);
1640
+ payload.id = entry.id;
1641
+ payload.path = entry.path;
1642
+ payload.name = entry.label;
1643
+ payload.type = entry.type;
1644
+ return payload;
1645
+ }
1646
+
1647
+ /**
1648
+ * @private
1649
+ * @return {object}
1650
+ */
1651
+ function getEditorAdapter() {
1652
+ const custom = this.getOption("editor.adapter");
1653
+ const base = {
1654
+ getValue: (element) => {
1655
+ if (isCanvasElement(element)) {
1656
+ return element.dataset.monsterValue || "";
1657
+ }
1658
+ if (element && "value" in element) {
1659
+ return element.value ?? "";
1660
+ }
1661
+ return element?.textContent ?? "";
1662
+ },
1663
+ setValue: (element, value) => {
1664
+ const next = value ?? "";
1665
+ if (isCanvasElement(element)) {
1666
+ renderCanvasFromValue(element, next);
1667
+ return;
1668
+ }
1669
+ if (element && "value" in element) {
1670
+ element.value = next;
1671
+ return;
1672
+ }
1673
+ if (element) {
1674
+ element.textContent = next;
1675
+ }
1676
+ },
1677
+ bindChange: (element, callback) => {
1678
+ if (!element || typeof callback !== "function") {
1679
+ return () => {};
1680
+ }
1681
+ if (isCanvasElement(element)) {
1682
+ return () => {};
1683
+ }
1684
+
1685
+ const handler = () => callback();
1686
+ element.addEventListener("input", handler);
1687
+ element.addEventListener("change", handler);
1688
+ return () => {
1689
+ element.removeEventListener("input", handler);
1690
+ element.removeEventListener("change", handler);
1691
+ };
1692
+ },
1693
+ setReadOnly: (element, readOnly) => {
1694
+ if (!element) {
1695
+ return;
1696
+ }
1697
+ if (isCanvasElement(element)) {
1698
+ return;
1699
+ }
1700
+ if ("readOnly" in element) {
1701
+ element.readOnly = readOnly === true;
1702
+ return;
1703
+ }
1704
+ if (readOnly === true) {
1705
+ element.setAttribute("data-monster-readonly", "");
1706
+ } else {
1707
+ element.removeAttribute("data-monster-readonly");
1708
+ }
1709
+ },
1710
+ };
1711
+
1712
+ if (!isObject(custom)) {
1713
+ return base;
1714
+ }
1715
+
1716
+ const adapter = Object.assign({}, base);
1717
+ if (isFunction(custom.getValue)) {
1718
+ adapter.getValue = custom.getValue;
1719
+ }
1720
+ if (isFunction(custom.setValue)) {
1721
+ adapter.setValue = custom.setValue;
1722
+ }
1723
+ if (isFunction(custom.bindChange)) {
1724
+ adapter.bindChange = custom.bindChange;
1725
+ }
1726
+ if (isFunction(custom.setReadOnly)) {
1727
+ adapter.setReadOnly = custom.setReadOnly;
1728
+ }
1729
+
1730
+ return adapter;
1731
+ }
1732
+
1733
+ /**
1734
+ * @private
1735
+ * @param {object} entry
1736
+ * @return {HTMLElement}
1737
+ */
1738
+ function createEditorElement(entry) {
1739
+ const tag = resolveEditorTag.call(this, entry);
1740
+ return getDocument().createElement(tag);
1741
+ }
1742
+
1743
+ /**
1744
+ * @private
1745
+ * @param {object} entry
1746
+ * @return {boolean}
1747
+ */
1748
+ function resolveEditorTag(entry) {
1749
+ const mimeType = getMimeTypeForEntry(entry);
1750
+ const map = this.getOption("editor.byMimeType", {});
1751
+ const tag = map?.[mimeType];
1752
+ if (isString(tag) && tag.trim() !== "") {
1753
+ return tag.trim();
1754
+ }
1755
+ const fallback = this.getOption("editor.defaultTag", "textarea");
1756
+ if (isString(fallback) && fallback.trim() !== "") {
1757
+ return fallback.trim();
1758
+ }
1759
+ return "textarea";
1760
+ }
1761
+
1762
+ /**
1763
+ * @private
1764
+ * @param {HTMLElement} element
1765
+ * @return {boolean}
1766
+ */
1767
+ function isCanvasElement(element) {
1768
+ return element instanceof HTMLCanvasElement;
1769
+ }
1770
+
1771
+ /**
1772
+ * @private
1773
+ * @param {HTMLCanvasElement} canvas
1774
+ * @param {string} value
1775
+ * @return {void}
1776
+ */
1777
+ function renderCanvasFromValue(canvas, value) {
1778
+ if (!isCanvasElement(canvas)) {
1779
+ return;
1780
+ }
1781
+
1782
+ const next = value ?? "";
1783
+ canvas.dataset.monsterValue = next;
1784
+
1785
+ const ctx = canvas.getContext("2d");
1786
+ if (!ctx) {
1787
+ return;
1788
+ }
1789
+
1790
+ const source = normalizeImageSource(next);
1791
+ if (!source) {
1792
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1793
+ return;
1794
+ }
1795
+
1796
+ const win = getWindow();
1797
+ if (!win) {
1798
+ return;
1799
+ }
1800
+
1801
+ const img = new win.Image();
1802
+ img.onload = () => {
1803
+ const targetWidth = canvas.width || img.naturalWidth;
1804
+ const targetHeight = canvas.height || img.naturalHeight;
1805
+
1806
+ if (canvas.width !== targetWidth) {
1807
+ canvas.width = targetWidth;
1808
+ }
1809
+ if (canvas.height !== targetHeight) {
1810
+ canvas.height = targetHeight;
1811
+ }
1812
+
1813
+ const scale = Math.min(
1814
+ canvas.width / img.naturalWidth,
1815
+ canvas.height / img.naturalHeight,
1816
+ );
1817
+ const drawWidth = img.naturalWidth * scale;
1818
+ const drawHeight = img.naturalHeight * scale;
1819
+ const offsetX = (canvas.width - drawWidth) / 2;
1820
+ const offsetY = (canvas.height - drawHeight) / 2;
1821
+
1822
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1823
+ ctx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight);
1824
+ };
1825
+ img.onerror = () => {
1826
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1827
+ };
1828
+ img.src = source;
1829
+ }
1830
+
1831
+ /**
1832
+ * @private
1833
+ * @param {string} value
1834
+ * @return {string}
1835
+ */
1836
+ function normalizeImageSource(value) {
1837
+ if (!isString(value)) {
1838
+ return "";
1839
+ }
1840
+ const trimmed = value.trim();
1841
+ if (trimmed === "") {
1842
+ return "";
1843
+ }
1844
+ if (trimmed.startsWith("data:image/")) {
1845
+ return trimmed;
1846
+ }
1847
+ if (trimmed.startsWith("<svg")) {
1848
+ return `data:image/svg+xml;utf8,${encodeURIComponent(trimmed)}`;
1849
+ }
1850
+ return trimmed;
1851
+ }
1852
+
1853
+ /**
1854
+ * @private
1855
+ * @param {object} entry
1856
+ * @return {string}
1857
+ */
1858
+ function getMimeTypeForEntry(entry) {
1859
+ if (isString(entry?.mimeType) && entry.mimeType.trim() !== "") {
1860
+ return entry.mimeType.trim().toLowerCase();
1861
+ }
1862
+ const ext = (entry?.type || "").toString().toLowerCase();
1863
+ switch (ext) {
1864
+ case "png":
1865
+ return "image/png";
1866
+ case "jpg":
1867
+ case "jpeg":
1868
+ return "image/jpeg";
1869
+ case "gif":
1870
+ return "image/gif";
1871
+ case "svg":
1872
+ return "image/svg+xml";
1873
+ case "webp":
1874
+ return "image/webp";
1875
+ case "bmp":
1876
+ return "image/bmp";
1877
+ case "ico":
1878
+ return "image/x-icon";
1879
+ case "md":
1880
+ return "text/markdown";
1881
+ case "css":
1882
+ return "text/css";
1883
+ case "html":
1884
+ case "htm":
1885
+ return "text/html";
1886
+ case "js":
1887
+ case "mjs":
1888
+ return "text/javascript";
1889
+ case "json":
1890
+ return "application/json";
1891
+ case "txt":
1892
+ return "text/plain";
1893
+ default:
1894
+ return "text/plain";
1895
+ }
1896
+ }
1897
+
1898
+ /**
1899
+ * @private
1900
+ * @param {object} tabData
1901
+ * @return {void}
1902
+ */
1903
+ function schedulePersistDiff(tabData) {
1904
+ const debounceMs = this.getOption("persistence.debounceMs", 300);
1905
+ if (!(tabData.persistDebounce instanceof DeadMansSwitch)) {
1906
+ tabData.persistDebounce = new DeadMansSwitch(debounceMs, () => {
1907
+ persistDiff.call(this, tabData);
1908
+ });
1909
+ return;
1910
+ }
1911
+
1912
+ try {
1913
+ tabData.persistDebounce.touch();
1914
+ } catch (_e) {
1915
+ tabData.persistDebounce = new DeadMansSwitch(debounceMs, () => {
1916
+ persistDiff.call(this, tabData);
1917
+ });
1918
+ }
1919
+ }
1920
+
1921
+ /**
1922
+ * @private
1923
+ * @return {void}
1924
+ */
1925
+ function syncDatasources() {
1926
+ if (this[listDatasourceOwnedSymbol] === true && this[listDatasourceSymbol]) {
1927
+ const restConfig = this.getOption("datasource.list.rest", {});
1928
+ if (isObject(restConfig)) {
1929
+ logDebug.call(this, "sync list datasource", restConfig?.read?.url);
1930
+ this[listDatasourceSymbol].setOptions(restConfig);
1931
+ this[listDatasourceSymbol].setOption("features.autoInit", true);
1932
+ this[listDatasourceSymbol].setOption("autoInit.oneTime", true);
1933
+ requestList.call(this, this.getOption("lazy.root", null));
1934
+ }
1935
+ } else if (!this[listDatasourceSymbol]) {
1936
+ initListDatasource.call(this);
1937
+ }
1938
+
1939
+ if (this[fileDatasourceOwnedSymbol] === true && this[fileDatasourceSymbol]) {
1940
+ const restConfig = this.getOption("datasource.file.rest", {});
1941
+ if (isObject(restConfig)) {
1942
+ this[fileDatasourceSymbol].setOptions(restConfig);
1943
+ }
1944
+ } else if (!this[fileDatasourceSymbol]) {
1945
+ initFileDatasource.call(this);
1946
+ }
1947
+ }
1948
+
1949
+ /**
1950
+ * @private
1951
+ * @param {string} message
1952
+ * @param {*=} data
1953
+ * @return {void}
1954
+ */
1955
+ function logDebug(message, data) {
1956
+ if (this.getOption("debug") !== true) {
1957
+ return;
1958
+ }
1959
+ if (data !== undefined) {
1960
+ console.log(`[monster-file-manager] ${message}`, data);
1961
+ } else {
1962
+ console.log(`[monster-file-manager] ${message}`);
1963
+ }
1964
+ }
1965
+
1966
+ /**
1967
+ * @private
1968
+ * @return {void}
1969
+ */
1970
+ function refreshLabels() {
1971
+ if (this[titleSymbol]) {
1972
+ this[titleSymbol].textContent = this.getOption("labels.title");
1973
+ }
1974
+ if (this[saveButtonSymbol]) {
1975
+ this[saveButtonSymbol].setOption(
1976
+ "labels.button",
1977
+ this.getOption("labels.save"),
1978
+ );
1979
+ }
1980
+ if (this[saveAllButtonSymbol]) {
1981
+ this[saveAllButtonSymbol].setOption(
1982
+ "labels.button",
1983
+ this.getOption("labels.saveAll"),
1984
+ );
1985
+ }
1986
+ if (this[emptyStateSymbol]) {
1987
+ const emptyLabel = this.getOption("labels.empty");
1988
+ if (isFunction(this[emptyStateSymbol].setOption)) {
1989
+ this[emptyStateSymbol].setOption("content.visual", getEmptyStateIcon());
1990
+ this[emptyStateSymbol].setOption("content.content", emptyLabel);
1991
+ } else {
1992
+ this[emptyStateSymbol].textContent = emptyLabel;
1993
+ }
1994
+ }
1995
+ }
1996
+
1997
+ /**
1998
+ * @private
1999
+ * @return {string}
2000
+ */
2001
+ function getEmptyStateIcon() {
2002
+ 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>`;
2003
+ }
2004
+
2005
+ /**
2006
+ * @private
2007
+ * @returns {object}
2008
+ */
2009
+ function getTranslations() {
2010
+ const locale = getLocaleOfDocument();
2011
+ switch (locale.language) {
2012
+ case "de":
2013
+ return {
2014
+ title: "Dateien",
2015
+ save: "Speichern",
2016
+ saveAll: "Alle speichern",
2017
+ empty: "Datei aus dem Baum auswaehlen.",
2018
+ };
2019
+ case "fr":
2020
+ return {
2021
+ title: "Fichiers",
2022
+ save: "Enregistrer",
2023
+ saveAll: "Tout enregistrer",
2024
+ empty: "Selectionnez un fichier dans l'arborescence.",
2025
+ };
2026
+ case "sp":
2027
+ return {
2028
+ title: "Archivos",
2029
+ save: "Guardar",
2030
+ saveAll: "Guardar todo",
2031
+ empty: "Seleccione un archivo del arbol.",
2032
+ };
2033
+ case "it":
2034
+ return {
2035
+ title: "File",
2036
+ save: "Salva",
2037
+ saveAll: "Salva tutto",
2038
+ empty: "Seleziona un file dall'albero.",
2039
+ };
2040
+ case "pl":
2041
+ return {
2042
+ title: "Pliki",
2043
+ save: "Zapisz",
2044
+ saveAll: "Zapisz wszystko",
2045
+ empty: "Wybierz plik z drzewa.",
2046
+ };
2047
+ case "no":
2048
+ case "dk":
2049
+ return {
2050
+ title: "Filer",
2051
+ save: "Lagre",
2052
+ saveAll: "Lagre alle",
2053
+ empty: "Velg en fil fra treet.",
2054
+ };
2055
+ case "sw":
2056
+ return {
2057
+ title: "Filer",
2058
+ save: "Spara",
2059
+ saveAll: "Spara alla",
2060
+ empty: "Valj en fil fran tradet.",
2061
+ };
2062
+ default:
2063
+ case "en":
2064
+ return {
2065
+ title: "Files",
2066
+ save: "Save",
2067
+ saveAll: "Save All",
2068
+ empty: "Select a file from the tree to open it.",
2069
+ };
2070
+ }
2071
+ }
2072
+
2073
+ /**
2074
+ * @private
2075
+ * @param {object} entry
2076
+ * @return {string|null}
2077
+ */
2078
+ function getStorageKey(entry) {
2079
+ const prefix = this.getOption("persistence.keyPrefix", "");
2080
+ const value = entry?.path || entry?.id;
2081
+ if (!isString(prefix) || prefix.trim() === "") {
2082
+ return null;
2083
+ }
2084
+ if (!isString(value) || value.trim() === "") {
2085
+ return null;
2086
+ }
2087
+ return `${prefix}:${value}`;
2088
+ }
2089
+
2090
+ /**
2091
+ * @private
2092
+ * @return {Storage|null}
2093
+ */
2094
+ function getStorage() {
2095
+ try {
2096
+ return getWindow()?.localStorage ?? null;
2097
+ } catch (_e) {
2098
+ return null;
2099
+ }
2100
+ }
2101
+
2102
+ /**
2103
+ * @private
2104
+ * @param {string} text
2105
+ * @return {number}
2106
+ */
2107
+ function hashText(text) {
2108
+ let hash = 0;
2109
+ for (let i = 0; i < text.length; i++) {
2110
+ hash = (hash + text.charCodeAt(i) * (i + 1)) % 2147483647;
2111
+ }
2112
+ return hash;
2113
+ }
2114
+
2115
+ /**
2116
+ * @private
2117
+ * @param {string} original
2118
+ * @param {object} entry
2119
+ * @return {{value: string, dirty: boolean}}
2120
+ */
2121
+ function applyPersistedDiff(original, entry) {
2122
+ const enabled = this.getOption("persistence.enabled", true);
2123
+ if (enabled !== true) {
2124
+ return { value: original, dirty: false };
2125
+ }
2126
+
2127
+ const storage = getStorage.call(this);
2128
+ if (!storage) {
2129
+ return { value: original, dirty: false };
2130
+ }
2131
+
2132
+ const key = getStorageKey.call(this, entry);
2133
+ if (!key) {
2134
+ return { value: original, dirty: false };
2135
+ }
2136
+
2137
+ const raw = storage.getItem(key);
2138
+ if (!raw) {
2139
+ return { value: original, dirty: false };
2140
+ }
2141
+
2142
+ let payload;
2143
+ try {
2144
+ payload = JSON.parse(raw);
2145
+ } catch (_e) {
2146
+ storage.removeItem(key);
2147
+ return { value: original, dirty: false };
2148
+ }
2149
+
2150
+ if (!payload || payload.baseHash !== hashText(original)) {
2151
+ storage.removeItem(key);
2152
+ return { value: original, dirty: false };
2153
+ }
2154
+
2155
+ const updated = applyDiffToLines(
2156
+ original.split("\n"),
2157
+ payload.diff || [],
2158
+ ).join("\n");
2159
+ return { value: updated, dirty: updated !== original };
2160
+ }
2161
+
2162
+ /**
2163
+ * @private
2164
+ * @param {string[]} lines
2165
+ * @param {Array} diffOps
2166
+ * @return {string[]}
2167
+ */
2168
+ function applyDiffToLines(lines, diffOps) {
2169
+ const out = lines.slice();
2170
+ const updates = [];
2171
+ const deletes = [];
2172
+ const adds = [];
2173
+
2174
+ for (const change of diffOps) {
2175
+ const index = change?.path?.[0];
2176
+ if (!Number.isInteger(index)) {
2177
+ continue;
2178
+ }
2179
+
2180
+ switch (change?.operator) {
2181
+ case "update":
2182
+ updates.push({
2183
+ index,
2184
+ value: change?.second?.value ?? "",
2185
+ });
2186
+ break;
2187
+ case "delete":
2188
+ deletes.push(index);
2189
+ break;
2190
+ case "add":
2191
+ adds.push({
2192
+ index,
2193
+ value: change?.second?.value ?? "",
2194
+ });
2195
+ break;
2196
+ default:
2197
+ break;
2198
+ }
2199
+ }
2200
+
2201
+ updates
2202
+ .sort((a, b) => a.index - b.index)
2203
+ .forEach(({ index, value }) => {
2204
+ out[index] = value;
2205
+ });
2206
+
2207
+ deletes
2208
+ .sort((a, b) => b - a)
2209
+ .forEach((index) => {
2210
+ if (index >= 0 && index < out.length) {
2211
+ out.splice(index, 1);
2212
+ }
2213
+ });
2214
+
2215
+ adds
2216
+ .sort((a, b) => a.index - b.index)
2217
+ .forEach(({ index, value }) => {
2218
+ const target = Math.max(0, Math.min(index, out.length));
2219
+ out.splice(target, 0, value);
2220
+ });
2221
+
2222
+ return out;
2223
+ }
2224
+
2225
+ /**
2226
+ * @private
2227
+ * @param {object} tabData
2228
+ * @return {void}
2229
+ */
2230
+ function persistDiff(tabData) {
2231
+ const enabled = this.getOption("persistence.enabled", true);
2232
+ if (enabled !== true) {
2233
+ return;
2234
+ }
2235
+
2236
+ const storage = getStorage.call(this);
2237
+ if (!storage) {
2238
+ return;
2239
+ }
2240
+
2241
+ const key = getStorageKey.call(this, tabData.entry);
2242
+ if (!key) {
2243
+ return;
2244
+ }
2245
+
2246
+ const current = tabData.adapter.getValue(tabData.editor);
2247
+ const original = tabData.original;
2248
+ if (current === original) {
2249
+ storage.removeItem(key);
2250
+ return;
2251
+ }
2252
+
2253
+ const diffResult = diff(original.split("\n"), current.split("\n"));
2254
+ const payload = {
2255
+ v: 1,
2256
+ baseHash: hashText(original),
2257
+ diff: diffResult,
2258
+ };
2259
+
2260
+ try {
2261
+ storage.setItem(key, JSON.stringify(payload));
2262
+ } catch (_e) {
2263
+ // ignore storage errors (quota, private mode)
2264
+ }
2265
+ }
2266
+
2267
+ /**
2268
+ * @private
2269
+ * @param {object} entry
2270
+ * @return {void}
2271
+ */
2272
+ function clearPersistedDiff(entry) {
2273
+ const storage = getStorage.call(this);
2274
+ if (!storage) {
2275
+ return;
2276
+ }
2277
+
2278
+ const key = getStorageKey.call(this, entry);
2279
+ if (key) {
2280
+ storage.removeItem(key);
2281
+ }
2282
+ }
2283
+
2284
+ /**
2285
+ * @private
2286
+ * @return {string}
2287
+ */
2288
+ function getTemplate() {
2289
+ // language=HTML
2290
+ return `
2291
+ <div data-monster-role="control" part="control">
2292
+ <div data-monster-role="toolbar" part="toolbar">
2293
+ <div data-monster-role="title" part="title" data-monster-replace="path:labels.title"></div>
2294
+ <div data-monster-role="actions" part="actions" class="monster-button-bar">
2295
+ <monster-message-state-button
2296
+ data-monster-role="save-button"
2297
+ data-monster-replace="path:labels.save"></monster-message-state-button>
2298
+ <monster-message-state-button
2299
+ data-monster-role="save-all-button"
2300
+ data-monster-replace="path:labels.saveAll"></monster-message-state-button>
2301
+ </div>
2302
+ </div>
2303
+ <monster-split-panel data-monster-role="split-panel">
2304
+ <div slot="start" data-monster-role="nav-panel" part="nav-panel">
2305
+ <monster-panel data-monster-role="tree-panel" part="tree-panel">
2306
+ <monster-tree-menu data-monster-role="tree">
2307
+ ${getIconSprite()}
2308
+ </monster-tree-menu>
2309
+ </monster-panel>
2310
+ </div>
2311
+ <div slot="end" data-monster-role="editor-panel" part="editor-panel">
2312
+ <div data-monster-role="tabs-container" part="tabs-container">
2313
+ <monster-tabs data-monster-role="tabs"></monster-tabs>
2314
+ </div>
2315
+ <monster-state data-monster-role="empty" part="empty"></monster-state>
2316
+ </div>
2317
+ </monster-split-panel>
2318
+ </div>
2319
+ `;
2320
+ }
2321
+
2322
+ /**
2323
+ * @private
2324
+ * @return {string}
2325
+ */
2326
+ function getIconSprite() {
2327
+ // language=HTML
2328
+ return `
2329
+ <svg xmlns="http://www.w3.org/2000/svg" style="display:none">
2330
+ <symbol id="monster-file-icon-default" viewBox="0 0 16 16">
2331
+ <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"/>
2332
+ </symbol>
2333
+ <symbol id="monster-file-icon-code" viewBox="0 0 16 16">
2334
+ <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"/>
2335
+ <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"/>
2336
+ </symbol>
2337
+ <symbol id="monster-file-icon-image" viewBox="0 0 16 16">
2338
+ <path d="M6.502 7a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3"/>
2339
+ <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"/>
2340
+ </symbol>
2341
+ <symbol id="monster-file-icon-sound" viewBox="0 0 16 16">
2342
+ <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"/>
2343
+ <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"/>
2344
+ </symbol>
2345
+ <symbol id="monster-file-icon-pdf" viewBox="0 0 16 16">
2346
+ <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"/>
2347
+ <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"/>
2348
+ </symbol>
2349
+ <symbol id="monster-file-icon-css" viewBox="0 0 16 16">
2350
+ <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"/>
2351
+ <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"/>
2352
+ </symbol>
2353
+ <symbol id="monster-file-icon-csv" viewBox="0 0 16 16">
2354
+ <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"/>
2355
+ <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"/>
2356
+ </symbol>
2357
+ <symbol id="monster-file-icon-doc" viewBox="0 0 16 16">
2358
+ <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"/>
2359
+ <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"/>
2360
+ </symbol>
2361
+ <symbol id="monster-file-icon-jpg" viewBox="0 0 16 16">
2362
+ <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"/>
2363
+ <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"/>
2364
+ </symbol>
2365
+ <symbol id="monster-file-icon-html" viewBox="0 0 16 16">
2366
+ <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"/>
2367
+ <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"/>
2368
+ </symbol>
2369
+ <symbol id="monster-file-icon-gif" viewBox="0 0 16 16">
2370
+ <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"/>
2371
+ <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"/>
2372
+ </symbol>
2373
+ <symbol id="monster-file-icon-mp4" viewBox="0 0 16 16">
2374
+ <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"/>
2375
+ <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"/>
2376
+ </symbol>
2377
+ <symbol id="monster-file-icon-mov" viewBox="0 0 16 16">
2378
+ <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"/>
2379
+ <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"/>
2380
+ </symbol>
2381
+ <symbol id="monster-file-icon-mp3" viewBox="0 0 16 16">
2382
+ <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"/>
2383
+ <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"/>
2384
+ </symbol>
2385
+ <symbol id="monster-file-icon-txt" viewBox="0 0 16 16">
2386
+ <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"/>
2387
+ <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"/>
2388
+ </symbol>
2389
+ <symbol id="monster-file-icon-png" viewBox="0 0 16 16">
2390
+ <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"/>
2391
+ <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"/>
2392
+ </symbol>
2393
+ </svg>
2394
+ `;
2395
+ }
2396
+
2397
+ registerCustomElement(FileManager);