'use strict';
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
// ==UserScript==
// @name JSON formatter
// @namespace http://gerald.top
// @author Gerald <[email protected]>
// @icon http://cn.gravatar.com/avatar/a0ad718d86d21262ccd6ff271ece08a3?s=80
// @description Format JSON data in a beautiful way.
// @description:zh-CN 更加漂亮地显示JSON数据。
// @version 1.4.0
// @match *://*/*
// @match file:///*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// ==/UserScript==
var id = 0;
var getId = function getId() {
return id += 1;
};
var SINGLELINE = getId();
var MULTILINE = getId();
var KEY = getId();
var gap = 5;
var createQuote = function createQuote() {
return createElement('span', {
className: 'subtle quote',
textContent: '"'
});
};
var createComma = function createComma() {
return createElement('span', {
className: 'subtle comma',
textContent: ','
});
};
var createSpace = function createSpace() {
var n = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
return createElement('span', {
textContent: ' '.repeat(n)
});
};
var createIndent = function createIndent() {
var n = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
return createSpace(2 * n);
};
var createBr = function createBr() {
return createElement('br');
};
var formatter = {
options: [{
key: 'hide-quotes',
title: '"',
def: false
}, {
key: 'hide-commas',
title: ',',
def: false
}]
};
var config = GM_getValue('config', formatter.options.reduce(function (res, item) {
res[item.key] = item.def;
return res;
}, {}));
if (['application/json', 'text/plain', 'application/javascript', 'text/javascript'].includes(document.contentType)) formatJSON();
GM_registerMenuCommand('Toggle JSON format', formatJSON);
function safeHTML(html) {
return String(html).replace(/[<&"]/g, function (key) {
return {
'<': '<',
'&': '&',
'"': '"'
}[key];
});
}
function createElement(tag, props) {
var el = document.createElement(tag);
if (props) {
Object.keys(props).forEach(function (key) {
el[key] = props[key];
});
}
return el;
}
function join(rendered) {
var level = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
var arr = [];
for (var i = 0; i < rendered.length; i += 1) {
var item = rendered[i];
var next = rendered[i + 1];
if (item.data) arr.push.apply(arr, _toConsumableArray(item.data));
if (next) {
if (item.separator) arr.push.apply(arr, _toConsumableArray(item.separator));
if (next.type === KEY || item.type !== KEY && (item.type === SINGLELINE || next.type === SINGLELINE)) {
arr.push(createBr(), createIndent(level));
} else {
arr.push(createSpace(1));
}
}
}
return arr;
}
function createNodes(data) {
var valueType = typeof data.value;
var type = data.type || valueType;
var el = createElement('span', {
className: data.cls || `item ${type}`,
textContent: `${data.value}`
});
el.dataset.type = valueType;
el.dataset.value = data.value;
var els = [el];
if (data.type === 'key' || !data.cls && type === 'string') {
els.unshift(createQuote());
els.push(createQuote());
}
return els;
}
function render(data) {
var level = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
if (Array.isArray(data)) {
var arr = [];
var ret = {
type: MULTILINE,
separator: [createComma()]
};
arr.push.apply(arr, _toConsumableArray(createNodes({ value: '[', cls: 'bracket' })));
if (data.length) {
var rendered = data.reduce(function (res, item) {
return res.concat([render(item, level + 1)]);
}, []);
arr.push.apply(arr, [createBr(), createIndent(level + 1)].concat(_toConsumableArray(join(rendered, level + 1)), [createBr(), createIndent(level)]));
} else {
arr.push.apply(arr, _toConsumableArray(createNodes({ value: '', cls: 'subtle' })));
ret.type = SINGLELINE;
}
arr.push.apply(arr, _toConsumableArray(createNodes({ value: ']', cls: 'bracket' })));
ret.data = arr;
return ret;
}
if (data === null) {
return {
type: SINGLELINE,
separator: [createComma()],
data: createNodes({ value: data, type: 'null' })
};
}
if (typeof data === 'object') {
var _arr = [];
var _ret = {
type: MULTILINE,
separator: [createComma()]
};
_arr.push.apply(_arr, _toConsumableArray(createNodes({ value: '{', cls: 'bracket' })));
var _rendered = Object.keys(data).reduce(function (res, key) {
return res.concat([{
type: KEY,
data: createNodes({ value: key, type: 'key' }),
separator: createNodes({ value: ':', cls: 'subtle' })
}, render(data[key], level + 1)]);
}, []);
if (_rendered.length) {
_arr.push.apply(_arr, [createBr(), createIndent(level + 1)].concat(_toConsumableArray(join(_rendered, level + 1)), [createBr(), createIndent(level)]));
} else {
_arr.push.apply(_arr, _toConsumableArray(createNodes({ value: '', cls: 'subtle' })));
_ret.type = SINGLELINE;
}
_arr.push.apply(_arr, _toConsumableArray(createNodes({ value: '}', cls: 'bracket' })));
_ret.data = _arr;
return _ret;
}
return {
type: SINGLELINE,
separator: [createComma()],
data: createNodes({ value: data })
};
}
function loadJSON() {
var text = document.body.innerText;
try {
// JSON
var content = JSON.parse(text);
return { prefix: '', suffix: '', content };
} catch (e) {
// not JSON
}
try {
// JSONP
var parts = text.match(/^(.*?\w\s*\()(.+)(\)[;\s]*)$/);
var _content = JSON.parse(parts[2]);
var prefix = parts[1];
var suffix = parts[3];
return { prefix, content: _content, suffix };
} catch (e) {
// not JSONP
}
}
function formatJSON() {
if (formatter.formatted) {
formatter.tips.hide();
formatter.menu.detach();
document.body.innerHTML = formatter.raw;
formatter.formatted = false;
} else {
if (!('raw' in formatter)) {
formatter.raw = document.body.innerHTML;
formatter.data = loadJSON();
if (!formatter.data) return;
formatter.style = GM_addStyle(".tips-val {\n color: dodgerblue;\n}* {\n margin: 0;\n padding: 0;\n}\n\nhtml, body {\n font-family: Menlo, \"Microsoft YaHei\", Tahoma;\n font-size: 14px;\n}\n\n#root {\n position: relative;\n margin: 0;\n padding: 1rem;\n}\n\n#root > pre {\n white-space: pre-wrap;\n}\n\n.subtle {\n color: #999;\n}\n.number {\n color: darkorange;\n}\n.null {\n color: gray;\n}\n.key {\n color: brown;\n}\n.string {\n color: green;\n}\n.boolean {\n color: dodgerblue;\n}\n.bracket {\n color: blue;\n}\n.item {\n cursor: pointer;\n}\n\n.tips {\n position: absolute;\n padding: .5em;\n border-radius: .5em;\n box-shadow: 0 0 1em gray;\n background: white;\n z-index: 1;\n white-space: nowrap;\n color: black\n}\n\n.tips-key {\n font-weight: bold;\n}\n.menu {\n position: fixed;\n top: 0;\n right: 0;\n background: white;\n padding: 5px;\n user-select: none;\n}\n.menu > span {\n margin-right: 5px;\n}\n.menu .btn {\n display: inline-block;\n width: 18px;\n height: 18px;\n line-height: 18px;\n text-align: center;\n background: #ddd;\n border-radius: 4px;\n cursor: pointer\n}\n.menu .btn.active {\n color: white;\n background: #444;\n}\n\n.hide-quotes .quote, .hide-commas .comma {\n font-size: 0;\n}\n");
initTips();
initMenu();
formatter.render = function () {
var pre = formatter.pre;
var _formatter$data = formatter.data,
prefix = _formatter$data.prefix,
content = _formatter$data.content,
suffix = _formatter$data.suffix;
pre.innerHTML = '';
[createElement('span', {
className: 'subtle',
textContent: prefix
})].concat(_toConsumableArray(render(content).data), [createElement('span', {
className: 'subtle',
textContent: suffix
})]).forEach(function (el) {
pre.appendChild(el);
});
formatter.update();
};
formatter.update = function () {
formatter.options.forEach(function (_ref) {
var key = _ref.key;
formatter.pre.classList[config[key] ? 'add' : 'remove'](key);
});
};
}
formatter.formatted = true;
formatter.root = createElement('div', { id: 'root' });
document.body.innerHTML = '';
document.body.appendChild(formatter.root);
formatter.pre = createElement('pre');
formatter.root.appendChild(formatter.pre);
formatter.menu.attach();
bindEvents();
formatter.render();
}
}
function removeEl(el) {
if (el && el.parentNode) el.parentNode.removeChild(el);
}
function initMenu() {
var menu = createElement('div', {
className: 'menu'
});
formatter.options.forEach(function (item) {
var span = createElement('span', {
className: `btn${config[item.key] ? ' active' : ''}`,
innerHTML: item.title
});
span.dataset.key = item.key;
menu.appendChild(span);
});
menu.addEventListener('click', function (e) {
var el = e.target;
var key = el.dataset.key;
if (key) {
config[key] = !config[key];
GM_setValue('config', config);
el.classList.toggle('active');
formatter.update();
}
}, false);
formatter.menu = {
node: menu,
attach() {
formatter.root.appendChild(menu);
},
detach() {
removeEl(menu);
}
};
}
function initTips() {
var tips = createElement('div', {
className: 'tips'
});
var hide = function hide() {
return removeEl(tips);
};
tips.addEventListener('click', function (e) {
e.stopPropagation();
}, false);
document.addEventListener('click', hide, false);
formatter.tips = {
node: tips,
hide,
show(range) {
var scrollTop = document.body.scrollTop;
var rects = range.getClientRects();
var rect = void 0;
if (rects[0].top < 100) {
rect = rects[rects.length - 1];
tips.style.top = `${rect.bottom + scrollTop + gap}px`;
tips.style.bottom = '';
} else {
rect = rects[0];
tips.style.top = '';
tips.style.bottom = `${formatter.root.offsetHeight - rect.top - scrollTop + gap}px`;
}
tips.style.left = `${rect.left}px`;
tips.innerHTML = `<span class="tips-key">type</span>: <span class="tips-val">${safeHTML(range.startContainer.dataset.type)}</span>`;
formatter.root.appendChild(tips);
}
};
}
function selectNode(node) {
var selection = window.getSelection();
selection.removeAllRanges();
var range = document.createRange();
range.setStartBefore(node.firstChild);
range.setEndAfter(node.firstChild);
selection.addRange(range);
return range;
}
function bindEvents() {
formatter.root.addEventListener('click', function (e) {
e.stopPropagation();
var target = e.target;
if (target.classList.contains('item')) {
formatter.tips.show(selectNode(target));
} else {
formatter.tips.hide();
}
}, false);
}