Files
sovereign_browser_rust/www/json-viewer/json-viewer.js
T

234 lines
12 KiB
JavaScript

(function () {
'use strict';
if (document.contentType !== 'application/json' || document.querySelector('.json-viewer')) {
return;
}
var CSS = `:root{color-scheme:light dark;--jv-bg:#fff;--jv-fg:#24292f;--jv-muted:#6e7781;--jv-border:#d0d7de;--jv-guide:#d8dee4;--jv-hover:#f3f4f6;--jv-control-bg:#f6f8fa;--jv-control-hover:#eaeef2;--jv-string:#16803a;--jv-number:#0969da;--jv-boolean:#bc4c00;--jv-null:#6e7781;--jv-key:#8250df;--jv-match:#fff3a3}*{box-sizing:border-box}html,body{margin:0;min-height:100%;background:var(--jv-bg);color:var(--jv-fg)}body.json-viewer-page{font-family:Menlo,Consolas,"Liberation Mono",monospace;font-size:14px;line-height:1.55}.json-viewer{min-height:100vh}.json-viewer__toolbar{position:sticky;z-index:10;top:0;display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:10px 14px;border-bottom:1px solid var(--jv-border);background:var(--jv-bg);box-shadow:0 1px 4px rgb(0 0 0/8%)}.json-viewer__title{margin-right:auto;font-weight:700}.json-viewer__button,.json-viewer__search{min-height:32px;border:1px solid var(--jv-border);border-radius:6px;background:var(--jv-control-bg);color:var(--jv-fg);font:inherit}.json-viewer__button{padding:4px 10px;cursor:pointer}.json-viewer__button:hover{background:var(--jv-control-hover)}.json-viewer__button:focus-visible,.json-viewer__search:focus-visible{outline:2px solid var(--jv-number);outline-offset:1px}.json-viewer__helper-apps{display:flex;align-items:center;gap:6px}.json-viewer__helper-label{color:var(--jv-muted);font-size:12px}.json-viewer__helper-link{display:inline-flex;align-items:center;text-decoration:none}.json-viewer__search{width:min(280px,50vw);padding:5px 10px}.json-viewer__status{min-width:9em;color:var(--jv-muted);font-size:12px}.json-viewer__tree{overflow-x:auto;overflow-y:visible;padding:14px 16px 40px}.json-viewer__node{min-width:0}.json-viewer__children{margin-left:8px;padding-left:17px;border-left:1px solid var(--jv-guide)}.json-viewer__node.is-collapsed>.json-viewer__children{display:none}.json-viewer__row{display:flex;align-items:baseline;min-height:23px;padding:1px 7px 1px 2px;border-radius:4px;white-space:pre}.json-viewer__row:hover{background:var(--jv-hover)}.json-viewer__node.is-filtered-out{display:none}.json-viewer__node.is-match>.json-viewer__row{background:var(--jv-match)}.json-viewer__toggle{width:18px;flex:0 0 18px;padding:0;border:0;background:transparent;color:var(--jv-muted);cursor:pointer;font:inherit;line-height:1}.json-viewer__toggle:before{content:"▼"}.json-viewer__node.is-collapsed>.json-viewer__row>.json-viewer__toggle:before{content:"▶"}.json-viewer__toggle--empty{cursor:default}.json-viewer__toggle--empty:before{content:""}.json-viewer__key{color:var(--jv-key)}.json-viewer__key:after{content:":";color:var(--jv-fg)}.json-viewer__punctuation{color:var(--jv-fg)}.json-viewer__string{min-width:0;color:var(--jv-string);white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word;word-break:break-all}.json-viewer__number{color:var(--jv-number)}.json-viewer__boolean{color:var(--jv-boolean);font-weight:600}.json-viewer__null{color:var(--jv-null);font-style:italic}.json-viewer__badge{display:none;margin-left:7px;padding:0 6px;border:1px solid var(--jv-border);border-radius:999px;color:var(--jv-muted);font-size:11px;line-height:17px}.json-viewer__node.is-collapsed>.json-viewer__row>.json-viewer__badge{display:inline}.json-viewer__preview{display:none;margin-left:7px;color:var(--jv-muted)}.json-viewer__node.is-collapsed>.json-viewer__row>.json-viewer__preview{display:inline}@media(prefers-color-scheme:dark){:root{--jv-bg:#1e1e1e;--jv-fg:#d4d4d4;--jv-muted:#9b9b9b;--jv-border:#454545;--jv-guide:#3f3f46;--jv-hover:#2a2d2e;--jv-control-bg:#2d2d30;--jv-control-hover:#3a3d41;--jv-string:#6a9955;--jv-number:#569cd6;--jv-boolean:#ce9178;--jv-null:#9b9b9b;--jv-key:#9cdcfe;--jv-match:#514b20}}@media(max-width:700px){.json-viewer__title{width:100%}.json-viewer__search{order:2;width:100%}.json-viewer__status{order:3}.json-viewer__helper-apps{width:100%;flex-wrap:wrap}}`;
function element(tag, className, text) {
var node = document.createElement(tag);
if (className) node.className = className;
if (text !== undefined) node.textContent = text;
return node;
}
function valueType(value) {
if (value === null) return 'null';
return typeof value;
}
function primitive(value) {
var type = valueType(value);
var text = type === 'string' ? JSON.stringify(value) : String(value);
return element('span', 'json-viewer__' + type, text);
}
function countLabel(value) {
var count = Array.isArray(value) ? value.length : Object.keys(value).length;
return count + ' ' + (Array.isArray(value) ? (count === 1 ? 'item' : 'items') : (count === 1 ? 'key' : 'keys'));
}
function buildNode(value, key, path) {
var compound = value !== null && typeof value === 'object';
var node = element('div', 'json-viewer__node');
node.dataset.key = key === null ? '' : String(key).toLocaleLowerCase();
node.dataset.path = path.toLocaleLowerCase();
var row = element('div', 'json-viewer__row');
var toggle = element('button', 'json-viewer__toggle' + (compound ? '' : ' json-viewer__toggle--empty'));
toggle.type = 'button';
toggle.tabIndex = compound ? 0 : -1;
toggle.setAttribute('aria-label', compound ? 'Collapse node' : 'Value');
row.appendChild(toggle);
if (key !== null) {
row.appendChild(element('span', 'json-viewer__key', JSON.stringify(String(key))));
row.appendChild(document.createTextNode(' '));
}
if (!compound) {
row.appendChild(primitive(value));
node.appendChild(row);
return node;
}
var isArray = Array.isArray(value);
var opening = isArray ? '[' : '{';
var closing = isArray ? ']' : '}';
row.appendChild(element('span', 'json-viewer__punctuation', opening));
row.appendChild(element('span', 'json-viewer__badge', countLabel(value)));
row.appendChild(element('span', 'json-viewer__preview', '… ' + closing));
node.appendChild(row);
var children = element('div', 'json-viewer__children');
var entries = isArray ? value.map(function (item, index) { return [index, item]; }) : Object.keys(value).map(function (itemKey) { return [itemKey, value[itemKey]]; });
entries.forEach(function (entry) {
var childPath = path ? path + '.' + entry[0] : String(entry[0]);
children.appendChild(buildNode(entry[1], isArray ? null : entry[0], childPath));
});
children.appendChild(element('div', 'json-viewer__row json-viewer__punctuation', ' ' + closing));
node.appendChild(children);
toggle.addEventListener('click', function () {
var collapsed = node.classList.toggle('is-collapsed');
toggle.setAttribute('aria-label', collapsed ? 'Expand node' : 'Collapse node');
});
return node;
}
function setAllCollapsed(root, collapsed) {
root.querySelectorAll('.json-viewer__node').forEach(function (node) {
if (node.querySelector(':scope > .json-viewer__children')) {
node.classList.toggle('is-collapsed', collapsed);
var button = node.querySelector(':scope > .json-viewer__row > .json-viewer__toggle');
if (button) button.setAttribute('aria-label', collapsed ? 'Expand node' : 'Collapse node');
}
});
}
function copyText(text, button) {
var copied = function () {
var old = button.textContent;
button.textContent = 'Copied!';
window.setTimeout(function () { button.textContent = old; }, 1200);
};
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(copied).catch(function () { fallbackCopy(text, copied); });
} else {
fallbackCopy(text, copied);
}
}
function fallbackCopy(text, done) {
var area = element('textarea');
area.value = text;
area.style.position = 'fixed';
area.style.opacity = '0';
document.body.appendChild(area);
area.select();
try { document.execCommand('copy'); done(); } catch (ignore) { /* no-op */ }
area.remove();
}
function addNostrHelperApps(toolbar) {
var match = location.href.match(/^nostr:\/\/([^/?#]+)/i);
if (!match || match[1] === 'helper-apps') return;
var entity = decodeURIComponent(match[1]);
fetch('nostr://' + encodeURIComponent(entity) + '/helper-apps')
.then(function (response) {
if (!response.ok) throw new Error('Unable to load Nostr helper apps');
return response.json();
})
.then(function (apps) {
if (!Array.isArray(apps) || !apps.length) return;
var group = element('span', 'json-viewer__helper-apps');
group.appendChild(element('span', 'json-viewer__helper-label', 'Open in:'));
apps.forEach(function (app) {
if (!app || typeof app.name !== 'string' || typeof app.url_template !== 'string') return;
var url = app.url_template.split('{entity}').join(entity);
var link = element('a', 'json-viewer__button json-viewer__helper-link', app.name);
link.href = url;
link.target = '_blank';
link.rel = 'noopener noreferrer';
link.addEventListener('click', function (event) {
event.preventDefault();
window.open(url, '_blank', 'noopener');
});
group.appendChild(link);
});
if (group.children.length > 1) toolbar.appendChild(group);
})
.catch(function () { /* Keep the standard JSON toolbar on endpoint failure. */ });
}
var source = document.querySelector('body > pre') || document.querySelector('pre');
if (!source) return;
var raw = source.textContent;
var data;
try {
data = JSON.parse(raw);
} catch (error) {
return;
}
var style = element('style');
style.id = 'sovereign-json-viewer-styles';
style.textContent = CSS;
(document.head || document.documentElement).appendChild(style);
var viewer = element('main', 'json-viewer');
var toolbar = element('div', 'json-viewer__toolbar');
toolbar.appendChild(element('span', 'json-viewer__title', 'JSON Viewer'));
var copyButton = element('button', 'json-viewer__button', 'Copy JSON');
copyButton.type = 'button';
copyButton.addEventListener('click', function () { copyText(raw, copyButton); });
toolbar.appendChild(copyButton);
var collapseButton = element('button', 'json-viewer__button', 'Collapse all');
collapseButton.type = 'button';
toolbar.appendChild(collapseButton);
var expandButton = element('button', 'json-viewer__button', 'Expand all');
expandButton.type = 'button';
toolbar.appendChild(expandButton);
addNostrHelperApps(toolbar);
var search = element('input', 'json-viewer__search');
search.type = 'search';
search.placeholder = 'Filter keys…';
search.setAttribute('aria-label', 'Filter JSON keys');
toolbar.appendChild(search);
var status = element('span', 'json-viewer__status');
status.setAttribute('aria-live', 'polite');
toolbar.appendChild(status);
viewer.appendChild(toolbar);
var tree = element('div', 'json-viewer__tree');
tree.setAttribute('role', 'tree');
tree.appendChild(buildNode(data, null, ''));
viewer.appendChild(tree);
collapseButton.addEventListener('click', function () { setAllCollapsed(tree, true); });
expandButton.addEventListener('click', function () { setAllCollapsed(tree, false); });
search.addEventListener('input', function () {
var query = search.value.trim().toLocaleLowerCase();
var nodes = Array.prototype.slice.call(tree.querySelectorAll('.json-viewer__node'));
var matches = 0;
nodes.forEach(function (node) {
node.classList.remove('is-filtered-out', 'is-match');
});
if (!query) {
status.textContent = '';
return;
}
nodes.forEach(function (node) {
var directMatch = node.dataset.key.indexOf(query) !== -1;
node.classList.toggle('is-match', directMatch);
if (directMatch) {
matches += 1;
var parent = node.parentElement;
while (parent && parent !== tree) {
if (parent.classList.contains('json-viewer__node')) parent.classList.remove('is-collapsed');
parent = parent.parentElement;
}
}
});
nodes.forEach(function (node) {
var visible = node.classList.contains('is-match') || !!node.querySelector('.json-viewer__node.is-match');
node.classList.toggle('is-filtered-out', !visible);
});
status.textContent = matches + (matches === 1 ? ' matching key' : ' matching keys');
});
document.body.className = 'json-viewer-page';
document.body.replaceChildren(viewer);
})();