@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mat3ra/esse",
3
- "version": "2026.5.18-0",
3
+ "version": "2026.5.18-2",
4
4
  "description": "Mat3ra's Excellent Source of Schemas and Examples (ESSE) for Digital Materials R&D",
5
5
  "scripts": {
6
6
  "transpile": "tsc -p tsconfig-transpile.json",
package/src/html/app.js CHANGED
@@ -1,23 +1,24 @@
1
- 'use strict';
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: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.44.0/min/vs' } });
8
- require(['vs/editor/editor.main'], function () {
9
- monacoEditor = monaco.editor.create(document.getElementById('monaco-editor'), {
10
- value: '',
11
- language: 'json',
12
- theme: 'vs-dark',
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: 'on',
18
- wordWrap: 'off',
18
+ lineNumbers: "on",
19
+ wordWrap: "off",
19
20
  automaticLayout: true,
20
- renderLineHighlight: 'all',
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) { pendingContent = text; return; }
30
- document.getElementById('welcome').style.display = 'none';
31
- document.getElementById('monaco-editor').style.display = 'block';
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
- let expandedSet = new Set(); // set of folder paths that are expanded
39
- let selectedFile = null; // currently selected file path
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).filter(k => k !== '__files').sort();
65
- folderKeys.forEach(key => {
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('div');
70
- row.className = 't-item folder';
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 ? '▾' : '▸'}</span>` +
76
- `<span class="t-icon" style="color:var(--icon-folder)">📁</span>` +
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('div');
80
- children.className = 't-children' + (isOpen ? ' open' : '');
81
- children.dataset.rendered = isOpen ? 'true' : 'false';
87
+ const children = document.createElement("div");
88
+ children.className = "t-children" + (isOpen ? " open" : "");
89
+ children.dataset.rendered = "false";
82
90
 
83
- row.addEventListener('click', e => {
91
+ row.addEventListener("click", (e) => {
84
92
  e.stopPropagation();
85
- const opening = !children.classList.contains('open');
86
- children.classList.toggle('open', opening);
87
- row.querySelector('.t-chevron').textContent = opening ? '▾' : '▸';
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 === 'false') {
100
+ if (children.dataset.rendered === "false") {
91
101
  renderTree(children, node[key], depth + 1, fp);
92
- children.dataset.rendered = 'true';
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 === 'false') {
109
+ if (isOpen && children.dataset.rendered === "false") {
100
110
  renderTree(children, node[key], depth + 1, fp);
101
- children.dataset.rendered = 'true';
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('div');
115
- row.className = 't-item file' + (path === selectedFile ? ' selected' : '');
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('click', () => openFile(path));
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('status-count').textContent = `${matches.length} result${matches.length !== 1 ? 's' : ''}`;
136
-
137
- matches.forEach(path => {
138
- const fileName = path.split('/').pop();
139
- const dirPart = path.slice(0, path.length - fileName.length - 1);
140
- const row = document.createElement('div');
141
- row.className = 't-item file flat-result' + (path === selectedFile ? ' selected' : '');
142
- row.style.paddingLeft = '8px';
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
- `<span class="t-filename">${hl(fileName)}</span>` +
151
- `<span class="t-filepath">${hl(dirPart)}</span>` +
162
+ `<span class="t-filename">${hl(fileName)}</span>` +
163
+ `<span class="t-filepath">${hl(dirPart)}</span>` +
152
164
  `</span>`;
153
165
 
154
- row.addEventListener('click', () => openFile(path));
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 escHtml(text.slice(0, idx)) +
165
- `<mark>${escHtml(text.slice(idx, idx + query.length))}</mark>` +
166
- escHtml(text.slice(idx + query.length));
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,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
184
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
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 !== '__files')
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
- const container = document.getElementById('file-tree');
188
- container.innerHTML = '';
201
+ currentQuery = query;
202
+ const container = document.getElementById("file-tree");
203
+ container.innerHTML = "";
189
204
 
190
205
  if (!query) {
191
- document.getElementById('status-count').textContent =
192
- `${allFiles.length} file${allFiles.length !== 1 ? 's' : ''}`;
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
- document.querySelectorAll('.t-item.selected').forEach(el => el.classList.remove('selected'));
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('/').pop();
213
- document.getElementById('tabbar').innerHTML =
214
- `<div class="tab active"><span>📄</span><span>${escHtml(fname)}</span></div>`;
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('breadcrumb').innerHTML =
219
- parts.map((p, i) =>
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
- ).join('');
254
+ : `<span style="color:var(--text-primary)">${escHtml(p)}</span>`,
255
+ )
256
+ .join("");
224
257
 
225
258
  // Status
226
- document.getElementById('status-path').textContent = path;
259
+ document.getElementById("status-path").textContent = path;
227
260
 
228
261
  // Update URL hash for deep-linking
229
- history.replaceState(null, '', '#' + path);
262
+ window.history.replaceState(null, "", "#" + path);
230
263
 
231
264
  // Fetch
232
265
  fetch(path)
233
- .then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
234
- .then(data => applyContent(JSON.stringify(data, null, 2)))
235
- .catch(err => applyContent(`// Error loading file\n// ${err.message}`));
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('search-input').addEventListener('input', function () {
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('resizer');
258
- const sidebar = document.getElementById('sidebar');
259
- let dragging = false, startX = 0, startW = 0;
260
-
261
- resizer.addEventListener('mousedown', e => {
262
- dragging = true; startX = e.clientX; startW = sidebar.offsetWidth;
263
- resizer.classList.add('active');
264
- document.body.style.cursor = 'col-resize';
265
- document.body.style.userSelect = 'none';
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('mousemove', e => {
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 + 'px';
311
+ sidebar.style.width = w + "px";
272
312
  if (monacoEditor) monacoEditor.layout();
273
313
  });
274
- document.addEventListener('mouseup', () => {
314
+ document.addEventListener("mouseup", () => {
275
315
  if (!dragging) return;
276
316
  dragging = false;
277
- resizer.classList.remove('active');
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('files.json')
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, '', 2);
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('file-tree').innerHTML =
303
- `<div class="tree-msg" style="color:#f88">Failed to load file index: ${err.message}</div>`;
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('hashchange', () => {
347
+ window.addEventListener("hashchange", () => {
307
348
  const hash = window.location.hash.slice(1);
308
349
  if (hash && allFiles.includes(hash)) openFile(hash);
309
350
  });
@@ -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>