您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
JSON editor dialog (intended as a library for userscripts)
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/441035/1250389/Json%20edit.js
// ==UserScript== // @name Json edit // @namespace https://agregen.gitlab.io/ // @version 0.0.1 // @description JSON editor dialog (intended as a library for userscripts) // @author agreg // @license MIT // @match http://localhost:* // @require https://cdnjs.cloudflare.com/ajax/libs/prism/1.27.0/prism.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/prism/1.27.0/components/prism-json.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/prism/1.27.0/plugins/match-braces/prism-match-braces.min.js // ==/UserScript== var Prism, $jsonEdit = (() => { const [TAB, NEWLINE, AMPERSAND, LESS_THAN, NONBREAKING_SPACE] = ['\t', '\n', "&", "<", " "]; let {isArray} = Array, isColl = x => x && (typeof x === 'object'); // ignoring non-JSON types let spaces = n => Array.from({length: n}, _ => " ").join(""); let compactItems = (items, {width, indent}) => items.slice(1).reduce((ss, s) => { (ss[0].length + 2 + s.length > width ? ss.unshift(s) : (ss[0] += ", " + s)); return ss; }, [items[0]]).reverse().join(`,\n${indent}`); let pformat = (x, {indent="", width=80, sparse=false, compact=false}={}) => { if (!isColl(x)) return JSON.stringify(x); let [bLeft, bRight] = (isArray(x) ? ["[", "]"] : ["{", "}"]); let _indent = isArray(x) ? (k => spaces(sparse ? 0 : 1)) : (k => spaces(sparse ? 2 : 3+k.length)); let kvs = Object.entries(x).map(([k, v]) => [JSON.stringify(!isArray(x) ? k : Number(k)), v]); let items = kvs.map(([k, v]) => [k, pformat(v, {width, sparse, compact, indent: indent+_indent(k)})]) .map(([k, v]) => (isArray(x) ? v : `${k}: ${v}`)); let len = indent.length + items.map(s => 2+s.length).reduce((a, b) => a+b, 0); let flat = !items.some(s => s.includes(NEWLINE)); let _wrap = (s, wrap=s.includes(NEWLINE) && !(isArray(x) && !flat)) => !wrap ? s : `\n${indent} ${s}\n${indent}`; let _compact = (sep, {_sparse=sparse, _indent=indent+" ", _width=width-_indent.length+1}={}) => (_sparse ? _wrap(_compact(sep, {_sparse: false, _width: _width-2, _indent: (flat ? _indent+" " : indent)})) : !compact || !flat ? items.join((sparse && !flat) || (flat && (len <= width)) ? ", " : sep) : compactItems(items, {width: _width, indent: _indent})); return bLeft + (flat && (items.length < 2) ? items[0] || "" : !sparse ? _compact(`,\n ${indent}`) : isArray(x) ? _compact(`,\n ${indent}`) : _wrap(items.join(`,\n ${indent}`), true)) + bRight; }; class ParseError extends Error { constructor(msg, {position, ...options}) {super(msg, options); this.position = Number(position)} } let _humanize = s => JSON.stringify( `${s||""}`.replace(/^[a-z]/, c => c.toUpperCase()) ).slice(1, -1); let parseJson = s => { try { return JSON.parse(s); } catch (e) { let match, _msg = s => _humanize( `${s||""}`.replaceAll(TAB, "\tab").replaceAll(NEWLINE, "\newline") ); // [sic] if (match = e.message.match(/^([^]*) in JSON at position ([0-9]+)$|^(Unexpected end of JSON input)$/)) { let [_, msg, pos, altMsg] = match; return new ParseError(_msg(msg||altMsg), {cause: e, position: pos||s.length}); } else if (match = e.message.match(/^JSON\.parse: ([^]*) at line ([0-9]+) column ([0-9]+) of the JSON data$/)) { let [_, msg, row, col] = match; return new ParseError(_msg(msg), {cause: e, position: Number(col) + (row == 1 ? -1 : s.split('\n').slice(0, row-1).join('\n').length)}); } else { console.warn(e); return new ParseError(_msg(e.message), {cause: e, position: 0}); } } }; let captureTab = editor => event => { if (event.key == "Tab") { event.preventDefault(); let before = editor.value.slice(0, editor.selectionStart); let after = editor.value.slice(editor.selectionEnd, editor.value.length); let pos = editor.selectionEnd + 1; editor.value = before + TAB + after; editor.selectionStart = editor.selectionEnd = pos; update(editor.value); } } let $e = (tag, attrs, ...children) => { let e = Object.assign(document.createElement(tag), attrs); children.forEach(child => e.append(typeof child != 'string' ? child : document.createTextNode(child))); return e; }; let theme = selector => `${selector} pre {color:white; background:black} ${selector} .token.string {color:slateblue} ${selector} .token.property {color:orange} ${selector} .token.number {color:green} ${selector} .token:is(.boolean, .null) {color:deeppink} ${selector} .token.operator {color:yellowgreen} ${selector} .token.punctuation {color:grey} ${selector} .token:is(.brace-level-2, .brace-level-6, .brace-level-10) {color:#388} ${selector} .token:is(.brace-level-3, .brace-level-7, .brace-level-11) {color:#838} ${selector} .token:is(.brace-level-4, .brace-level-8, .brace-level-12) {color:#883}`; let createEditorModal = (id, {maxWidth='90%'}={}) => { const ID = '#'+id; let style = $e('style', {}, `${ID} {position:fixed; height:calc(90vh - 2em); top:5vh; width:calc(90vw - 2em); max-width:${maxWidth}; left:0; right:0; margin:0 auto; z-index:1000} ${ID}, ${ID} .window {display:flex; flex-direction:column} ${ID} .title {padding:0 1em; background:lightgrey; font-weight:bold; font-size:larger} ${ID} .window {position: relative; padding:1em; padding-top:0; background:grey} ${ID} :is(.title, .window > *) {flex:0} ${ID} :is(.window, .editor) {flex-grow:1} ${ID} .editor {position:relative; height:calc(100% - 6em)} ${ID} .editor > * {position:absolute; top:0; left:0; width:calc(100% - 6px); height:100%; overflow:auto; margin:0} ${ID} .editor :is(textarea, pre) {font-family:monospace; font-size:15pt; line-height:20pt; border-radius:5px; white-space:pre; hyphens:none} ${ID}.text .editor :is(textarea, pre) {white-space:pre-wrap; word-wrap:break-word} ${ID} .editor textarea {resize:none; z-index:2; background:transparent; color:transparent; caret-color:white} ${ID} .editor pre {z-index:1; margin:0; overflow:auto; padding:3px} @-moz-document url-prefix() {${ID} .editor pre {padding:4px}} ${theme(ID)} ${ID} :is(.toolbar, .buttons) {margin-top:1em; display:flex; justify-content:space-evenly} ${ID} .toolbar input[type=number] {width:4em; background:white} ${ID} .error {color:yellow; font-weight:bold; font-family:monospace}`); setTimeout(() => document.head.append(style)); let modal, title, error, editor, overlay, content, toolbar, sparse, compact, width, redraw, cancel, ok; modal = $e('div', {id, className: 'modal', style: "display:none", mode: 'json'}, title = $e('div', {className: 'title'}, ""), $e('div', {className: 'window'}, $e('div', {className: 'error', innerHTML: "​"}, error = $e('span')), $e('div', {className: 'editor'}, editor = $e('textarea'), overlay = $e('pre', {}, content = $e('code', {className: "highlighting language-json match-braces"}))), toolbar = $e('div', {className: 'toolbar'}, $e('label', {title: "Don't inline dicts"}, sparse = $e('input', {type: 'checkbox', className: 'sparse'}), " Sparse"), $e('label', {title: "Compact long lists"}, compact = $e('input', {type: 'checkbox', className: 'compact'}), " Compact"), $e('label', {title: "Width limit (0 = unlimited)"}, "Width ", width = $e('input', {type: 'number', className: 'width', value: 100, min: 0})), redraw = $e('button', {className: 'redraw'}, "Check / Reformat")), $e('div', {className: 'buttons'}, cancel = $e('button', {className: 'cancel'}, "Cancel"), ok = $e('button', {className: 'ok'}, "OK")))); let _isValid, _width = Number(width.value)||Infinity; let render = (e, text, mode=modal.mode) => { e.innerHTML = text.replace(/&/g, AMPERSAND).replace(/</g, LESS_THAN); // can't use innerText here (mode === 'json') && Prism && Prism.highlightElement(e); }; let update = text => render(content, text + (text.slice(-1) != NEWLINE ? "" : " ")); let syncScroll = () => {[overlay.scrollTop, overlay.scrollLeft] = [editor.scrollTop, editor.scrollLeft]}; let detectWidth = () => { let style = "font-family:monospace; font-size:15pt; line-height:20pt; position:fixed; top:0; left:0"; let e = $e('span', {style, innerHTML: NONBREAKING_SPACE}); modal.append(e); width.value = _width = Math.floor(editor.clientWidth / e.clientWidth); e.remove(); }; let reformat = (s = editor.value, o = parseJson(s)) => { if (o instanceof Error) { error.innerText = o.message || "Syntax error"; console.warn(o, {position: o.position}); editor.focus(); editor.selectionStart = editor.selectionEnd = o.position||0; } else { error.innerText = ""; editor.value = pformat(o, {sparse: sparse.checked, compact: compact.checked, width: _width}); editor.selectionStart = editor.selectionEnd = 0; update(editor.value); syncScroll(); } }; let _visible = () => modal.style.display !== 'none'; let toggle = (visible=!_visible()) => {modal.style.display = (visible ? '' : 'none')}; let _resolve, [editJson, editText] = ['json', 'text'].map(mode => (value=editor.value, options={}) => new Promise(resolve => { [modal.mode, _resolve, _isValid] = [mode, resolve, options.validator]; toolbar.style.display = (mode == 'json' ? '' : 'none'); modal.classList[mode == 'json' ? 'remove' : 'add']('text'); [error.innerText, editor.value, title.innerText] = ["", value, options.title||`Enter ${mode}`]; render(content, value); toggle(true); detectWidth(); editor.focus(); })); let editAsJson = (value, options={}) => (setTimeout(reformat), editJson(JSON.stringify(value), options)); let resolve = (value=editor.value) => (toggle(false), _resolve && _resolve(value)); document.addEventListener('keydown', ({key}) => (key == 'Escape') && toggle(false)); cancel.onclick = () => toggle(); ok.onclick = () => { if (modal.mode != 'json') resolve(); else { let s = editor.value, o = parseJson(s); if (!(o instanceof Error)) resolve(o) else { try {if (_isValid(s)) return resolve()} catch (e) {} reformat(s, o) } } }; editor.oninput = () => {update(editor.value); syncScroll()}; editor.onscroll = syncScroll; editor.onkeydown = captureTab(editor); redraw.onclick = sparse.onchange = compact.onchange = () => reformat(); width.onchange = () => { if (!width.value || !Number.isInteger( Number(width.value) )) alert(`Invalid width value: ${width.value}`); else { _width = Number(width.value)||Infinity; reformat(); } }; return Object.assign(modal, {toggle, editText, editJson, editAsJson, render}); }; return {pformat, ParseError, parseJson, theme, createEditorModal}; })();