@mat3ra/esse 2026.5.18-0 → 2026.5.18-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/html/app.js +148 -107
- package/src/html/index.html +1 -0
package/package.json
CHANGED
package/src/html/app.js
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
|
|
1
|
+
/* global monaco */
|
|
2
|
+
/* eslint-disable import/no-amd, import/no-dynamic-require, no-use-before-define */
|
|
2
3
|
|
|
3
4
|
// ── Monaco bootstrap ──────────────────────────────────────────────────────────
|
|
4
5
|
let monacoEditor = null;
|
|
5
6
|
let pendingContent = null;
|
|
6
7
|
|
|
7
|
-
require.config({ paths: { vs:
|
|
8
|
-
require([
|
|
9
|
-
monacoEditor = monaco.editor.create(document.getElementById(
|
|
10
|
-
value:
|
|
11
|
-
language:
|
|
12
|
-
theme:
|
|
8
|
+
require.config({ paths: { vs: "https://cdn.jsdelivr.net/npm/monaco-editor@0.44.0/min/vs" } });
|
|
9
|
+
require(["vs/editor/editor.main"], () => {
|
|
10
|
+
monacoEditor = monaco.editor.create(document.getElementById("monaco-editor"), {
|
|
11
|
+
value: "",
|
|
12
|
+
language: "json",
|
|
13
|
+
theme: "vs-dark",
|
|
13
14
|
readOnly: true,
|
|
14
15
|
minimap: { enabled: true },
|
|
15
16
|
scrollBeyondLastLine: false,
|
|
16
17
|
fontSize: 13,
|
|
17
|
-
lineNumbers:
|
|
18
|
-
wordWrap:
|
|
18
|
+
lineNumbers: "on",
|
|
19
|
+
wordWrap: "off",
|
|
19
20
|
automaticLayout: true,
|
|
20
|
-
renderLineHighlight:
|
|
21
|
+
renderLineHighlight: "all",
|
|
21
22
|
});
|
|
22
23
|
if (pendingContent !== null) {
|
|
23
24
|
applyContent(pendingContent);
|
|
@@ -26,23 +27,27 @@ require(['vs/editor/editor.main'], function () {
|
|
|
26
27
|
});
|
|
27
28
|
|
|
28
29
|
function applyContent(text) {
|
|
29
|
-
if (!monacoEditor) {
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
if (!monacoEditor) {
|
|
31
|
+
pendingContent = text;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
document.getElementById("welcome").style.display = "none";
|
|
35
|
+
document.getElementById("monaco-editor").style.display = "block";
|
|
32
36
|
monacoEditor.setValue(text);
|
|
33
37
|
monacoEditor.revealLine(1);
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
// ── State ─────────────────────────────────────────────────────────────────────
|
|
37
|
-
let allFiles
|
|
38
|
-
|
|
39
|
-
let selectedFile
|
|
41
|
+
let allFiles = [];
|
|
42
|
+
const expandedSet = new Set(); // set of folder paths that are expanded
|
|
43
|
+
let selectedFile = null; // currently selected file path
|
|
44
|
+
let currentQuery = "";
|
|
40
45
|
|
|
41
46
|
// ── Build nested tree object from flat path list ──────────────────────────────
|
|
42
47
|
function pathsToTree(paths) {
|
|
43
48
|
const root = {};
|
|
44
|
-
paths.forEach(p => {
|
|
45
|
-
const parts = p.split(
|
|
49
|
+
paths.forEach((p) => {
|
|
50
|
+
const parts = p.split("/");
|
|
46
51
|
let node = root;
|
|
47
52
|
for (let i = 0; i < parts.length; i++) {
|
|
48
53
|
const part = parts[i];
|
|
@@ -61,44 +66,49 @@ function pathsToTree(paths) {
|
|
|
61
66
|
// ── Render tree (lazy — children only rendered when folder is opened) ─────────
|
|
62
67
|
function renderTree(container, node, depth, folderPath) {
|
|
63
68
|
// Folders first (alphabetical)
|
|
64
|
-
const folderKeys = Object.keys(node)
|
|
65
|
-
|
|
69
|
+
const folderKeys = Object.keys(node)
|
|
70
|
+
.filter((k) => k !== "__files")
|
|
71
|
+
.sort();
|
|
72
|
+
folderKeys.forEach((key) => {
|
|
66
73
|
const fp = folderPath ? `${folderPath}/${key}` : key;
|
|
67
74
|
const isOpen = expandedSet.has(fp);
|
|
68
75
|
|
|
69
|
-
const row = document.createElement(
|
|
70
|
-
row.className =
|
|
76
|
+
const row = document.createElement("div");
|
|
77
|
+
row.className = "t-item folder";
|
|
71
78
|
row.style.paddingLeft = `${depth * 12 + 4}px`;
|
|
72
79
|
row.dataset.fp = fp;
|
|
80
|
+
row.setAttribute("aria-expanded", String(isOpen));
|
|
73
81
|
|
|
74
82
|
row.innerHTML =
|
|
75
|
-
`<span class="t-chevron">${isOpen ?
|
|
76
|
-
`<span class="t-icon" style="color:var(--icon-folder)"
|
|
83
|
+
`<span class="t-chevron">${isOpen ? "▾" : "▸"}</span>` +
|
|
84
|
+
`<span class="t-icon" style="color:var(--icon-folder)">${isOpen ? "📂" : "📁"}</span>` +
|
|
77
85
|
`<span class="t-label">${escHtml(key)}</span>`;
|
|
78
86
|
|
|
79
|
-
const children = document.createElement(
|
|
80
|
-
children.className =
|
|
81
|
-
children.dataset.rendered =
|
|
87
|
+
const children = document.createElement("div");
|
|
88
|
+
children.className = "t-children" + (isOpen ? " open" : "");
|
|
89
|
+
children.dataset.rendered = "false";
|
|
82
90
|
|
|
83
|
-
row.addEventListener(
|
|
91
|
+
row.addEventListener("click", (e) => {
|
|
84
92
|
e.stopPropagation();
|
|
85
|
-
const opening = !children.classList.contains(
|
|
86
|
-
children.classList.toggle(
|
|
87
|
-
row.querySelector(
|
|
93
|
+
const opening = !children.classList.contains("open");
|
|
94
|
+
children.classList.toggle("open", opening);
|
|
95
|
+
row.querySelector(".t-chevron").textContent = opening ? "▾" : "▸";
|
|
96
|
+
row.querySelector(".t-icon").textContent = opening ? "📂" : "📁";
|
|
97
|
+
row.setAttribute("aria-expanded", String(opening));
|
|
88
98
|
if (opening) {
|
|
89
99
|
expandedSet.add(fp);
|
|
90
|
-
if (children.dataset.rendered ===
|
|
100
|
+
if (children.dataset.rendered === "false") {
|
|
91
101
|
renderTree(children, node[key], depth + 1, fp);
|
|
92
|
-
children.dataset.rendered =
|
|
102
|
+
children.dataset.rendered = "true";
|
|
93
103
|
}
|
|
94
104
|
} else {
|
|
95
105
|
expandedSet.delete(fp);
|
|
96
106
|
}
|
|
97
107
|
});
|
|
98
108
|
|
|
99
|
-
if (isOpen && children.dataset.rendered ===
|
|
109
|
+
if (isOpen && children.dataset.rendered === "false") {
|
|
100
110
|
renderTree(children, node[key], depth + 1, fp);
|
|
101
|
-
children.dataset.rendered =
|
|
111
|
+
children.dataset.rendered = "true";
|
|
102
112
|
}
|
|
103
113
|
|
|
104
114
|
container.appendChild(row);
|
|
@@ -111,8 +121,8 @@ function renderTree(container, node, depth, folderPath) {
|
|
|
111
121
|
.slice()
|
|
112
122
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
113
123
|
.forEach(({ name, path }) => {
|
|
114
|
-
const row = document.createElement(
|
|
115
|
-
row.className =
|
|
124
|
+
const row = document.createElement("div");
|
|
125
|
+
row.className = "t-item file" + (path === selectedFile ? " selected" : "");
|
|
116
126
|
row.style.paddingLeft = `${depth * 12 + 22}px`;
|
|
117
127
|
row.dataset.path = path;
|
|
118
128
|
|
|
@@ -120,7 +130,7 @@ function renderTree(container, node, depth, folderPath) {
|
|
|
120
130
|
`<span class="t-icon">📄</span>` +
|
|
121
131
|
`<span class="t-label">${escHtml(name)}</span>`;
|
|
122
132
|
|
|
123
|
-
row.addEventListener(
|
|
133
|
+
row.addEventListener("click", () => openFile(path));
|
|
124
134
|
container.appendChild(row);
|
|
125
135
|
});
|
|
126
136
|
}
|
|
@@ -132,26 +142,28 @@ function renderSearch(container, matches, query) {
|
|
|
132
142
|
container.innerHTML = '<div class="tree-msg">No results found.</div>';
|
|
133
143
|
return;
|
|
134
144
|
}
|
|
135
|
-
document.getElementById(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
row
|
|
145
|
+
document.getElementById("status-count").textContent = `${matches.length} result${
|
|
146
|
+
matches.length !== 1 ? "s" : ""
|
|
147
|
+
}`;
|
|
148
|
+
|
|
149
|
+
matches.forEach((path) => {
|
|
150
|
+
const fileName = path.split("/").pop();
|
|
151
|
+
const dirPart = path.slice(0, path.length - fileName.length - 1);
|
|
152
|
+
const row = document.createElement("div");
|
|
153
|
+
row.className = "t-item file flat-result" + (path === selectedFile ? " selected" : "");
|
|
154
|
+
row.style.paddingLeft = "8px";
|
|
143
155
|
row.dataset.path = path;
|
|
144
156
|
|
|
145
|
-
const hl = s => hlMatch(s, query);
|
|
157
|
+
const hl = (s) => hlMatch(s, query);
|
|
146
158
|
|
|
147
159
|
row.innerHTML =
|
|
148
160
|
`<span class="t-icon" style="flex-shrink:0">📄</span>` +
|
|
149
161
|
`<span class="t-label">` +
|
|
150
|
-
|
|
151
|
-
|
|
162
|
+
`<span class="t-filename">${hl(fileName)}</span>` +
|
|
163
|
+
`<span class="t-filepath">${hl(dirPart)}</span>` +
|
|
152
164
|
`</span>`;
|
|
153
165
|
|
|
154
|
-
row.addEventListener(
|
|
166
|
+
row.addEventListener("click", () => openFile(path));
|
|
155
167
|
container.appendChild(row);
|
|
156
168
|
});
|
|
157
169
|
}
|
|
@@ -161,21 +173,23 @@ function hlMatch(text, query) {
|
|
|
161
173
|
if (!query) return escHtml(text);
|
|
162
174
|
const idx = text.toLowerCase().indexOf(query.toLowerCase());
|
|
163
175
|
if (idx === -1) return escHtml(text);
|
|
164
|
-
return
|
|
165
|
-
|
|
166
|
-
|
|
176
|
+
return (
|
|
177
|
+
escHtml(text.slice(0, idx)) +
|
|
178
|
+
`<mark>${escHtml(text.slice(idx, idx + query.length))}</mark>` +
|
|
179
|
+
escHtml(text.slice(idx + query.length))
|
|
180
|
+
);
|
|
167
181
|
}
|
|
168
182
|
|
|
169
183
|
function escHtml(s) {
|
|
170
|
-
return s.replace(/&/g,
|
|
184
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
171
185
|
}
|
|
172
186
|
|
|
173
187
|
// ── Pre-expand folders up to a given depth ────────────────────────────────────
|
|
174
188
|
function preExpand(node, depth, folderPath, maxDepth) {
|
|
175
189
|
if (depth >= maxDepth) return;
|
|
176
190
|
Object.keys(node)
|
|
177
|
-
.filter(k => k !==
|
|
178
|
-
.forEach(key => {
|
|
191
|
+
.filter((k) => k !== "__files")
|
|
192
|
+
.forEach((key) => {
|
|
179
193
|
const fp = folderPath ? `${folderPath}/${key}` : key;
|
|
180
194
|
expandedSet.add(fp);
|
|
181
195
|
preExpand(node[key], depth + 1, fp, maxDepth);
|
|
@@ -184,61 +198,83 @@ function preExpand(node, depth, folderPath, maxDepth) {
|
|
|
184
198
|
|
|
185
199
|
// ── Rebuild the whole tree pane ───────────────────────────────────────────────
|
|
186
200
|
function rebuildTree(query) {
|
|
187
|
-
|
|
188
|
-
container
|
|
201
|
+
currentQuery = query;
|
|
202
|
+
const container = document.getElementById("file-tree");
|
|
203
|
+
container.innerHTML = "";
|
|
189
204
|
|
|
190
205
|
if (!query) {
|
|
191
|
-
document.getElementById(
|
|
192
|
-
|
|
206
|
+
document.getElementById("status-count").textContent = `${allFiles.length} file${
|
|
207
|
+
allFiles.length !== 1 ? "s" : ""
|
|
208
|
+
}`;
|
|
193
209
|
const tree = pathsToTree(allFiles);
|
|
194
|
-
renderTree(container, tree, 0,
|
|
210
|
+
renderTree(container, tree, 0, "");
|
|
195
211
|
} else {
|
|
196
212
|
const q = query.toLowerCase();
|
|
197
|
-
const matches = allFiles.filter(f => f.toLowerCase().includes(q));
|
|
213
|
+
const matches = allFiles.filter((f) => f.toLowerCase().includes(q));
|
|
198
214
|
renderSearch(container, matches, query);
|
|
199
215
|
}
|
|
200
216
|
}
|
|
201
217
|
|
|
218
|
+
function focusSelectedFileInTree(path) {
|
|
219
|
+
document.querySelectorAll(".t-item.selected").forEach((el) => el.classList.remove("selected"));
|
|
220
|
+
const el = document.querySelector(`.t-item.file[data-path="${CSS.escape(path)}"]`);
|
|
221
|
+
if (el) {
|
|
222
|
+
el.classList.add("selected");
|
|
223
|
+
el.scrollIntoView({ block: "nearest" });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
202
227
|
// ── Open / display a file ─────────────────────────────────────────────────────
|
|
203
228
|
function openFile(path) {
|
|
204
229
|
selectedFile = path;
|
|
230
|
+
expandToFile(path);
|
|
231
|
+
|
|
232
|
+
if (currentQuery) {
|
|
233
|
+
currentQuery = "";
|
|
234
|
+
document.getElementById("search-input").value = "";
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
rebuildTree("");
|
|
205
238
|
|
|
206
239
|
// Highlight in tree (works for both tree and search views)
|
|
207
|
-
|
|
208
|
-
const el = document.querySelector(`.t-item.file[data-path="${CSS.escape(path)}"]`);
|
|
209
|
-
if (el) el.classList.add('selected');
|
|
240
|
+
focusSelectedFileInTree(path);
|
|
210
241
|
|
|
211
242
|
// Tab
|
|
212
|
-
const fname = path.split(
|
|
213
|
-
document.getElementById(
|
|
214
|
-
|
|
243
|
+
const fname = path.split("/").pop();
|
|
244
|
+
document.getElementById(
|
|
245
|
+
"tabbar",
|
|
246
|
+
).innerHTML = `<div class="tab active"><span>📄</span><span>${escHtml(fname)}</span></div>`;
|
|
215
247
|
|
|
216
248
|
// Breadcrumb
|
|
217
|
-
const parts = path.split(
|
|
218
|
-
document.getElementById(
|
|
219
|
-
|
|
249
|
+
const parts = path.split("/");
|
|
250
|
+
document.getElementById("breadcrumb").innerHTML = parts
|
|
251
|
+
.map((p, i) =>
|
|
220
252
|
i < parts.length - 1
|
|
221
253
|
? `<span>${escHtml(p)}</span><span class="bc-sep">›</span>`
|
|
222
|
-
: `<span style="color:var(--text-primary)">${escHtml(p)}</span
|
|
223
|
-
)
|
|
254
|
+
: `<span style="color:var(--text-primary)">${escHtml(p)}</span>`,
|
|
255
|
+
)
|
|
256
|
+
.join("");
|
|
224
257
|
|
|
225
258
|
// Status
|
|
226
|
-
document.getElementById(
|
|
259
|
+
document.getElementById("status-path").textContent = path;
|
|
227
260
|
|
|
228
261
|
// Update URL hash for deep-linking
|
|
229
|
-
history.replaceState(null,
|
|
262
|
+
window.history.replaceState(null, "", "#" + path);
|
|
230
263
|
|
|
231
264
|
// Fetch
|
|
232
265
|
fetch(path)
|
|
233
|
-
.then(r => {
|
|
234
|
-
|
|
235
|
-
|
|
266
|
+
.then((r) => {
|
|
267
|
+
if (!r.ok) throw new Error("HTTP " + r.status);
|
|
268
|
+
return r.json();
|
|
269
|
+
})
|
|
270
|
+
.then((data) => applyContent(JSON.stringify(data, null, 2)))
|
|
271
|
+
.catch((err) => applyContent(`// Error loading file\n// ${err.message}`));
|
|
236
272
|
}
|
|
237
273
|
|
|
238
274
|
// ── Expand all ancestor folders for a given file path ────────────────────────
|
|
239
275
|
function expandToFile(path) {
|
|
240
|
-
const parts = path.split(
|
|
241
|
-
let folderPath =
|
|
276
|
+
const parts = path.split("/");
|
|
277
|
+
let folderPath = "";
|
|
242
278
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
243
279
|
folderPath = folderPath ? `${folderPath}/${parts[i]}` : parts[i];
|
|
244
280
|
expandedSet.add(folderPath);
|
|
@@ -247,63 +283,68 @@ function expandToFile(path) {
|
|
|
247
283
|
|
|
248
284
|
// ── Search handler ────────────────────────────────────────────────────────────
|
|
249
285
|
let searchTimer = null;
|
|
250
|
-
document.getElementById(
|
|
286
|
+
document.getElementById("search-input").addEventListener("input", function () {
|
|
251
287
|
clearTimeout(searchTimer);
|
|
252
288
|
searchTimer = setTimeout(() => rebuildTree(this.value.trim()), 150);
|
|
253
289
|
});
|
|
254
290
|
|
|
255
291
|
// ── Resizer ───────────────────────────────────────────────────────────────────
|
|
256
|
-
(function () {
|
|
257
|
-
const resizer = document.getElementById(
|
|
258
|
-
const sidebar = document.getElementById(
|
|
259
|
-
let dragging = false
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
292
|
+
(function initResizer() {
|
|
293
|
+
const resizer = document.getElementById("resizer");
|
|
294
|
+
const sidebar = document.getElementById("sidebar");
|
|
295
|
+
let dragging = false;
|
|
296
|
+
let startX = 0;
|
|
297
|
+
let startW = 0;
|
|
298
|
+
|
|
299
|
+
resizer.addEventListener("mousedown", (e) => {
|
|
300
|
+
dragging = true;
|
|
301
|
+
startX = e.clientX;
|
|
302
|
+
startW = sidebar.offsetWidth;
|
|
303
|
+
resizer.classList.add("active");
|
|
304
|
+
document.body.style.cursor = "col-resize";
|
|
305
|
+
document.body.style.userSelect = "none";
|
|
266
306
|
e.preventDefault();
|
|
267
307
|
});
|
|
268
|
-
document.addEventListener(
|
|
308
|
+
document.addEventListener("mousemove", (e) => {
|
|
269
309
|
if (!dragging) return;
|
|
270
310
|
const w = Math.max(120, Math.min(window.innerWidth * 0.6, startW + e.clientX - startX));
|
|
271
|
-
sidebar.style.width = w +
|
|
311
|
+
sidebar.style.width = w + "px";
|
|
272
312
|
if (monacoEditor) monacoEditor.layout();
|
|
273
313
|
});
|
|
274
|
-
document.addEventListener(
|
|
314
|
+
document.addEventListener("mouseup", () => {
|
|
275
315
|
if (!dragging) return;
|
|
276
316
|
dragging = false;
|
|
277
|
-
resizer.classList.remove(
|
|
278
|
-
document.body.style.cursor =
|
|
279
|
-
document.body.style.userSelect =
|
|
317
|
+
resizer.classList.remove("active");
|
|
318
|
+
document.body.style.cursor = "";
|
|
319
|
+
document.body.style.userSelect = "";
|
|
280
320
|
});
|
|
281
|
-
}()
|
|
321
|
+
})();
|
|
282
322
|
|
|
283
323
|
// ── Init ──────────────────────────────────────────────────────────────────────
|
|
284
|
-
fetch(
|
|
285
|
-
.then(r => r.json())
|
|
286
|
-
.then(files => {
|
|
324
|
+
fetch("files.json")
|
|
325
|
+
.then((r) => r.json())
|
|
326
|
+
.then((files) => {
|
|
287
327
|
allFiles = files;
|
|
288
328
|
// Pre-expand to second degree by default
|
|
289
329
|
const tree = pathsToTree(allFiles);
|
|
290
|
-
preExpand(tree, 0,
|
|
330
|
+
preExpand(tree, 0, "", 2);
|
|
291
331
|
// Handle deep-link hash
|
|
292
332
|
const hash = window.location.hash.slice(1);
|
|
293
333
|
if (hash && allFiles.includes(hash)) {
|
|
294
334
|
expandToFile(hash);
|
|
295
335
|
}
|
|
296
|
-
rebuildTree(
|
|
336
|
+
rebuildTree("");
|
|
297
337
|
if (hash && allFiles.includes(hash)) {
|
|
298
338
|
openFile(hash);
|
|
299
339
|
}
|
|
300
340
|
})
|
|
301
|
-
.catch(err => {
|
|
302
|
-
document.getElementById(
|
|
303
|
-
|
|
341
|
+
.catch((err) => {
|
|
342
|
+
document.getElementById(
|
|
343
|
+
"file-tree",
|
|
344
|
+
).innerHTML = `<div class="tree-msg" style="color:#f88">Failed to load file index: ${err.message}</div>`;
|
|
304
345
|
});
|
|
305
346
|
|
|
306
|
-
window.addEventListener(
|
|
347
|
+
window.addEventListener("hashchange", () => {
|
|
307
348
|
const hash = window.location.hash.slice(1);
|
|
308
349
|
if (hash && allFiles.includes(hash)) openFile(hash);
|
|
309
350
|
});
|
package/src/html/index.html
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>ESSEntial Source of Schemas and Examples (ESSE)</title>
|
|
7
|
+
<link rel="icon" href="https://materials-designer.mat3ra.com/favicon.ico">
|
|
7
8
|
<link rel="stylesheet" href="style.css">
|
|
8
9
|
</head>
|
|
9
10
|
<body>
|