// ==UserScript== // @license MIT // @name JSON Viewer // @namespace http://tampermonkey.net/ // @version 0.6.1 // @note v0.6.1 增加多一种浅色主题 // @note v0.6.0 增加简单HTTP 请求功能,可请求GET/POST/PUT/DELETE的API接口,而不单单只能GET请求使用 // @note v0.5.9 jsonp格式小优化 // @note v0.5.8 增加JSON手动输入 // @note v0.5.7 一些小细节优化 // @note v0.5.6 修复BUG // @note v0.5.5 解决@require jquery-simple-tree-table.min.js依赖加载失败问题 // @note v0.5.4 单击复制修改为CTRL+单击复制JSONPath功能,JSON格式化风格增加table格式 // @note v0.5.3 增加暗黑主题色 // @note v0.5.2 单击JSON格式化的key可复制JSONPath // @note v0.5.1 修复JSONPath提示有误 // @note v0.5.0 解决chrome 120+以上内核JSON格式化不执行和引入layer报错问题 // @note v0.4.9 布局修改,增加保存JSON/脑图为文件,增加JSON过滤,鼠标移入key提示JSONPath // @note v0.4.8 代码优化 // @note v0.4.7 增加对JSONP的判断,代码优化 // @note v0.4.6 增加复制按钮,JSON脑图CSS样式细节优化,JSON脑图增加收起/展开子节点按钮 // @note v0.4.5 在json-viewer-updated原基础上进行了一些修改,主要有CSS样式修改,新增折叠/展开全部功能,新增JSON脑图功能,脑图节点点击显示调用路径 // @description 格式化显示JSON使数据看起来更加漂亮,支持折叠/展开格式化后的数据,支持JSON脑图让调用层级看着更清晰,支持复制JSON脑图节点路径 // @author Feny // @match *://*/* // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant unsafeWindow // @grant GM_openInTab // @grant GM_setClipboard // @grant GM_getResourceText // @grant GM_registerMenuCommand // @icon data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAeAB4AAD/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcGBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAAgACADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9wvjF8bLX4ZrHZx+XNqlwnmKjH5YUyRvb6kEAd8H058d1b47XV5Ir3eoN++bagaXYrN6KOBn2FfPPx5/aEutX8a+KNWhIubhruZLSNj8u1WMcIOP4QoXOOwNS/wDBM79lDTfjh8YNe+IXj63TxRdeHfKjsE1KMTxtdSbmMmxvlAiVV2IBsUybgAyKR9ZTy6lhsM69Xovm2+iPmKmOqYjEKjT6v5WXU9e8beKrjxZpp/s/xFrfhjWIwWs9X0q42z2kmPlZo2zDcxjqYZ0eNv7obDDf/YL/AG8L745eJNc+G3xCttP0n4qeEWZZ2sVZNP8AENsu0rd2ysS0ZKPG7RMSQsisCcukWd+3x4RtfhpqGk+JNPjS0g1mV7a8iT5UM4XesgHYsofdjglQepJPxwPE1x4V/au8LePtNkeK40ma0lmdDgyorvHMhPo9uxjP+y1bUcHRxmGbS1adn1TXT0/4fcipiqmExCi3pfVdGn19f+GLni/w7ceGf2wPFXga+Vo5rW/vDbI3WWI/v4HA/wBqBg3tk+lfWX7AXia1+F/iDV9B1CRbWHXjFLayyHannpuUxk+rqy4zxlMdWAPQft7fsL33x91nQfiD4DutP0r4oeDXVrT7cWWx1y3UsTZ3LKCyAh5FEigkLLIpHzBk5vwj4JuvE2kLJfeHdY8N6lGAl5pepwBZrOT+JRIuYp0ByBNCzxPg4bIICqYyljMKot62Sa6prr6P/gBDC1MLiHJLS90+jT6ev/Dnmn/BVP8Aaw0nxv4/8P8Aw18KXK69qmk3MlxqEdiRMwuivlpbrtPLopkMnZNy5IIYLyPwa/Z8vvEmv+HtHu4/Ovr+5iS6KDcqAtukwe6om7nuEJxX0XoH7PK/bJG0vRoY5rr/AFslvbLGZf8AfcAf+PGvafgv8CbX4byNqFyI5tWmXYCoytsh6qvqx7t+A4yWiWYUsLhlRpbr72318kVHA1MTiHVqbP7kl0P/2Q== // @require https://code.jquery.com/jquery.min.js // @require https://unpkg.com/jsmind@0.8.5/es6/jsmind.js // @require https://unpkg.com/dom-to-image@2.6.0/dist/dom-to-image.min.js // @require https://unpkg.com/jsmind@0.8.5/es6/jsmind.screenshot.js // @resource swalStyle https://unpkg.com/jsmind@0.8.5/style/jsmind.css // @downloadURL none // ==/UserScript== /** * jquery-simple-tree-table.min.js * https://cdn.jsdelivr.net/npm/@kanety/jquery-simple-tree-table@0.5.1/dist/jquery-simple-tree-table.min.js */ !function(e){var t={};function n(o){if(t[o])return t[o].exports;var i=t[o]={i:o,l:!1,exports:{}};return e[o].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(o,i,function(t){return e[t]}.bind(null,i));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist",n(n.s=5)}([function(e,t){e.exports=jQuery},function(e,t,n){var o=n(2),i=n(3);"string"==typeof(i=i.__esModule?i.default:i)&&(i=[[e.i,i,""]]);var a={insert:"head",singleton:!1};o(i,a);e.exports=i.locals||{}},function(e,t,n){"use strict";var o,i=function(){return void 0===o&&(o=Boolean(window&&document&&document.all&&!window.atob)),o},a=function(){var e={};return function(t){if(void 0===e[t]){var n=document.querySelector(t);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(e){n=null}e[t]=n}return e[t]}}(),r=[];function s(e){for(var t=-1,n=0;n0&&void 0!==arguments[0]?arguments[0]:{};a(this,e),this.opts={type:t.type||"session",key:t.key},this.inst=new l(this.opts)}return s(e,[{key:"get",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;return this.inst.get(this.opts.key)||e}},{key:"set",value:function(e){this.inst.set(this.opts.key,e)}},{key:"remove",value:function(){this.inst.remove(this.opts.key)}}]),e}(),l=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};a(this,e),this.storage={local:window.localStorage,session:window.sessionStorage}[t.type]}return s(e,[{key:"get",value:function(e){try{var t=this.storage.getItem(e);return t?JSON.parse(t):null}catch(e){return console.log(e),null}}},{key:"set",value:function(e,t){try{this.storage.setItem(e,JSON.stringify(t))}catch(e){console.log(e)}}},{key:"remove",value:function(e){this.storage.removeItem(e)}}]),e}(),u=(n(1),"simple-tree-table");function d(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function f(e,t){for(var n=0;n :first-child",iconTemplate:"",store:null,storeKey:null},p=function(){function e(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};d(this,e),this.options=i.a.extend({},h,n),this.$table=i()(t),this.$expander=i()(this.options.expander),this.$collapser=i()(this.options.collapser),this.options.store&&this.options.storeKey&&(this.store=new c({type:this.options.store,key:this.options.storeKey})),this.init(),this.load()}var t,n,o;return t=e,o=[{key:"getDefaults",value:function(){return h}},{key:"setDefaults",value:function(e){return i.a.extend(h,e)}}],(n=[{key:"init",value:function(){this.$table.addClass(u),this.build(),this.unbind(),this.bind()}},{key:"destroy",value:function(){var e=function(e,t){var n=new RegExp("".concat(u,"(-\\S+)?"),"g");return(t.match(n)||[]).join(" ")};this.$table.removeClass(e),this.nodes().removeClass(e),this.$table.find(".".concat(u,"-icon")).remove(),this.unbind()}},{key:"build",value:function(){var e=this;this.nodes().not("[data-node-depth]").each((function(t,n){var o=i()(n),a=e.depth(o);o.data("node-depth",a),1==a&&o.addClass("".concat(u,"-root"))})),this.nodes().filter((function(t,n){return 0==i()(n).find(e.options.iconPosition).find(".".concat(u,"-handler")).length})).each((function(t,n){var o=i()(n),a=e.depth(o),r=e.options.margin*(a-1),s=i()(e.options.iconTemplate).addClass("".concat(u,"-handler ").concat(u,"-icon")).css("margin-left","".concat(r,"px"));o.find(e.options.iconPosition).prepend(s)})),this.nodes().not(".".concat(u,"-empty, .").concat(u,"-opened, .").concat(u,"-closed")).each((function(t,n){var o=i()(n);e.hasChildren(o)?e.opensDefault(o)?o.addClass("".concat(u,"-opened")):o.addClass("".concat(u,"-closed")):o.addClass("".concat(u,"-empty"))})),this.nodes().filter(".".concat(u,"-opened")).each((function(t,n){e.show(i()(n))})),this.nodes().filter(".".concat(u,"-closed")).each((function(t,n){e.hide(i()(n))}))}},{key:"opensDefault",value:function(e){var t=this.options.opened;return t&&("all"==t||-1!=t.indexOf(e.data("node-id")))}},{key:"bind",value:function(){var e=this;this.$expander.on("click.".concat(u),(function(t){e.expand()})),this.$collapser.on("click.".concat(u),(function(t){e.collapse()})),this.$table.on("click.".concat(u),"tr .".concat(u,"-handler"),(function(t){var n=i()(t.currentTarget).closest("tr");n.hasClass("".concat(u,"-opened"))?e.close(n):e.open(n)}))}},{key:"unbind",value:function(){this.$expander.off(".".concat(u)),this.$collapser.off(".".concat(u)),this.$table.off(".".concat(u," node:open node:close"))}},{key:"expand",value:function(){var e=this;this.nodes().each((function(t,n){e.show(i()(n))})),this.save()}},{key:"collapse",value:function(){var e=this;this.nodes().each((function(t,n){e.hide(i()(n))})),this.save()}},{key:"nodes",value:function(){return this.$table.find("tr[data-node-id]")}},{key:"depth",value:function(e){var t=e.data("node-depth");if(t)return t;var n=this.findByID(e.data("node-pid"));return 0!=n.length?this.depth(n)+1:1}},{key:"open",value:function(e){this.show(e),this.save(),e.trigger("node:open",[e])}},{key:"show",value:function(e){e.hasClass("".concat(u,"-empty"))||(e.removeClass("".concat(u,"-closed")).addClass("".concat(u,"-opened")),this.showDescs(e))}},{key:"showDescs",value:function(e){var t=this;this.findChildren(e).each((function(e,n){var o=i()(n);o.show(),o.hasClass("".concat(u,"-opened"))&&t.showDescs(o)}))}},{key:"close",value:function(e){this.hide(e),this.save(),e.trigger("node:close",[e])}},{key:"hide",value:function(e){e.hasClass("".concat(u,"-empty"))||(e.removeClass("".concat(u,"-opened")).addClass("".concat(u,"-closed")),this.hideDescs(e))}},{key:"hideDescs",value:function(e){var t=this;this.findChildren(e).each((function(e,n){var o=i()(n);o.hide(),t.hideDescs(o)}))}},{key:"hasChildren",value:function(e){return 0!=this.findChildren(e).length}},{key:"findChildren",value:function(e){var t=e.data("node-id");return this.$table.find('tr[data-node-pid="'.concat(t,'"]'))}},{key:"findDescendants",value:function(e){var t=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],o=this.findChildren(e);return n.push(o),o.each((function(e,o){t.findDescendants(i()(o),n)})),n}},{key:"findByID",value:function(e){return this.$table.find('tr[data-node-id="'.concat(e,'"]'))}},{key:"openByID",value:function(e){this.open(this.findByID(e))}},{key:"closeByID",value:function(e){this.close(this.findByID(e))}},{key:"load",value:function(){var e=this;if(this.store){var t=this.store.get();t&&(this.nodes().each((function(t,n){e.show(i()(n))})),this.nodes().filter((function(e,n){return-1!=t.indexOf(i()(n).data("node-id"))})).each((function(t,n){e.hide(i()(n))})))}}},{key:"save",value:function(){if(this.store){var e=this.nodes().filter(".".concat(u,"-closed")).map((function(e,t){return i()(t).data("node-id")})).get();this.store.set(e)}}}])&&f(t.prototype,n),o&&f(t,o),e}();i.a.fn.simpleTreeTable=function(e){return this.each((function(t,n){var o=i()(n);o.data(u)&&o.data(u).destroy(),o.data(u,new p(o,e))}))},i.a.SimpleTreeTable=p}]); (function () { 'use strict'; const Utils = { // 检查字符串是否为URL isUrl: function (string) { var regexp = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/; return regexp.test(string); }, // 检查是否是图片链接 isImg: function (pathImg) { // var regexp = /^(http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?\/([\w#!:.?+=&%@!\-\/])*\.(gif|jpg|jpeg|png|GIF|JPG|PNG)([\w#!:.?+=&%@!\-\/])?/; let regexp = /\.(ico|bmp|gif|jpg|jpeg|png|svg|webp|GIF|JPG|PNG|WEBP|SVG)([\w#!:.?+=&%@!\-\/])?/i return regexp.test(pathImg); }, // 检验内容是否是json格式的内容 isJSON: function (str) { try { JSON.parse(str) return true } catch (e) { console.log("is not json", e) return false } }, // 获取数据类型 getType: function (value) { return Object.prototype.toString.call(value).match(/\s(.+)]/)[1].toLowerCase(); }, // 获取数组中对象key最多的对象 findMaxKeysObject: function (arr) { let maxKeysCount = 0, maxKeysObject for (let obj of arr) { let keysCount = Object.keys(obj).length if (keysCount > maxKeysCount) { maxKeysCount = keysCount maxKeysObject = obj } } return maxKeysObject }, // 随机rgb颜色 rgbaColor: (opacity) => `rgba(${Math.random() * 256}, ${Math.random() * 256}, ${Math.random() * 256}, ${opacity})` } // jquery.json-viewer 插件 开始 // 解决和原网页jquery版本冲突 var _jQuery = jQuery.noConflict(true); (function ($) { /** * 检查 arg 是否为至少包含 1 个元素的数组或至少包含 1 个键的字典 */ function isCollapsable(arg) { return arg instanceof Object && Object.keys(arg).length > 0; } /** * 将 JSON 对象转换为 HTML 表示形式 * @return string */ function json2html(json, parentPath = '') { let html = '', type = Utils.getType(json) switch (type) { case 'array': case 'object': let len = json.length || Object.keys(json).length; if (len > 0) { html += ``; html += type === 'array' ? '[
    ' : '{
      '; for (var key in json) { if (json.hasOwnProperty(key)) { let comma = --len > 0 ? ',' : '', jsonPath = parentPath + '.' + key, collapse = isCollapsable(json[key]) ? '' : '', res = json2html(json[key], jsonPath) let toHtml = type === 'array' ? res : `"${key}": ${res}` html += [`
    • `, collapse, toHtml, comma, '
    • '].join('') } } if (type === 'array') { html += `
]` } else { html += `}` } } else { html += `` html += (type === 'array') ? '[]' : '{}' html += '' } break default: /* Escape tags */ json = type === 'string' ? json.replace(/&/g, '&').replace(//g, '>') : json if (Utils.isUrl(json)) { html += `"${json}"`; } else { json = type === 'string' ? `"${json}"` : json html += `${json}`; } break } return html; } $.fn.jsonViewer = function (json, jsonpFn) { return this.each(function () { /* Transform to HTML */ var html = json2html(json); /** is JSONP */ if (jsonpFn !== undefined && jsonpFn !== null) { html = `
${jsonpFn}(
${html}
)
` } /* Insert HTML in target DOM element */ $(this).html(html); /* Bind click on toggle buttons */ $(this).off('click'); $(this).on('click', 'a.json-toggle', function () { var target = $(this).toggleClass('collapsed').siblings('ul.json-object, ol.json-array'); target.toggle(); if (target.is(':visible')) { target.siblings('.json-placeholder').remove(); } else { var count = target.children('li:not([class*="hidden"])').length; var placeholder = count + (count > 1 ? ' items' : ' item'); target.after('' + placeholder + ''); } return false; }); /* Simulate click on toggle button when placeholder is clicked */ $(this).on('click', 'a.json-placeholder', function () { $(this).siblings('a.json-toggle').click(); $(this).siblings('a.json-placeholder').remove(); return false; }); }); }; })(_jQuery); // jquery.json-viewer 插件 结束 (function ($) { let docType = [ "application/vnd.api+json", "application/javascript", "application/json", "text/javascript", "text/plain", "text/json", ], contentType = document.contentType if (!docType.includes(contentType)) { return } var source = $('pre').first(); if (source.length === 0) { let text = document.body.innerText if (!Utils.isJSON(text)) { return } let pre = document.createElement('pre') pre.innerText = text document.body.insertAdjacentHTML('afterbegin', pre); source = $(pre) } let rawText = source.text() if (!rawText) { return } // 判断是否为jsonp格式 window.globalJSONPFun = null let tokens = rawText.match(/^([^\s(]*)\s*\(([\s\S]*)\)\s*;?$/) if (tokens && tokens[1] && tokens[2]) { globalJSONPFun = tokens[1] rawText = tokens[2] } if (!Utils.isJSON(rawText)) { return } // 添加样式 GM_addStyle(GM_getResourceText('swalStyle')) $("head").append(``) .append('') GM_addStyle(` :root{ --ft12: 12px; --ft13: 13px; --ft14: 14px; --color-e9: #e9e9e9; --color-gray: #CCC; --tab-color: #ECECEC; } body, html{ margin: 0; padding: 0; font-size: var(--ft14); } td{ font-size: var(--ft14); } li::marker { content: ''; } input:focus, select:focus { outline: 0; } .hidden{ display: none !important; } .jsonp{ color: #93983A; } .dark .jsonp{ color: #F1D700; } .scroll-top{ width: 48px; height: 48px; z-index: 999; position: fixed; right: 30px; bottom: 30px; display: none; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAA39JREFUaEPtmV2ITVEUx39rJjT5KPKC5DPKK6UGZZ7Eq0whNUiKFPfcKW9mnmjm7ssLaSLygEx5xItMESnKC4V8FV4IJUkyS+fMHZ07zjl3733PvT6a+3S6Z629/r+99sfa+wgpPw1YDtxLe9/k/1eI4X5STMkSokXWNlloYjgpMZSmIxPgbxBfS8M4QK0eavT78Qw0uodrtd+wDGjAMqBcEVAQw6NaYnzeNwRAi2xBOQlMrYj6jLJTygz6iMzyyR1Ai+xCGUgMqnTmDZErgBYoIJjMXhY2S4mLeWUiNwAt0INwyEqY0iNleq1saxjlAqAB64BrToKEHVLijJNPgnHdALqPaUziOsoKRzGfaKFD+nng6FdlXj+Ay9AZq1Q5L2W2/jEA3c8sWqOSe3aCiK/A3cr/K4G2FKEbxXDZF6KuDKROXKWXFvqlxJdQmBaZzDDdKZP8thhW/RmAgKfA4qrgSq+U6UkSlAostEuJOz4Q3hnQgPXAld+CCh1pB5DogKTcSBAalhpHmw1wGtgxJuhbMczJEqIBbxLmzFUxbGg2gCYE9AVADF6jwctJDzKd73xI7DG/IQQTmCFH+OiaBT+AAyykhWcpBZv7JA4bGmaRHOV5cwBqX7mc5Qe9coyX0TK6n/m0RnVSV4bA1KuTLCi/DBTYhHCpRm+Fe8Bo/b8JmJxp71lqOwNowB7guGuqLe33iuGEpW1k5gTgVDK7qIjbZmyESU1aA1TOuA99dTn5CQulxAsbH3uAeqpOGyXVNrvEcMrGzR4gIGnjsonhYzMkhg4bx3EAm17ysGlIBh4DSzzE+LgMiGG3jaPLEApv2Q7YNFq3jcOmZg9gt/u6aH8PzEx0aGWu9PHapjFrgKimCaIDTHiQqf4pNxHW2AT8ZaMEiZdgSreUKdm25QRQgRi7nIZzI7xZcPmeNkAL5xjmVpVQx1049HUGiCAKFBH6K8GjFUP3MIW2CGJpjd4bFEPnmM54grA761tYWpteAJXgy1A6w6p09Opcu1nED7ZlAcQP/BqwGZjORAblMO9sh03czhsgLZgW6EL4JoYL0b0RTArPBfFnH6G5ZyAVYKTkGBlWQVR2bxDDgvjzvwQQXqGsDQ/sGkTXKdHzOECsB3LtjdjKMjqE/qMMjJwn5olh+98+hM6gvAqXy0aN+4Yuo/HG/weAPmC1GNrzHDbxtn4Coc0pQNdM3UAAAAAASUVORK5CYII=) } /** 工具栏样式 START **/ .flex-container{ z-index: 10; position: fixed; width: 100vw; height: 100vh; display: flex; flex-direction: column; } .tabs, .toolbar{ display: flex; line-height: 28px; background-color: var(--tab-color); border-bottom: 1px solid var(--color-gray); } .toolbar{ line-height: 23px; } .searchbox{ display: flex; flex-grow: 1; } .toolbar input{ flex-grow: 1; border: none; outline: none; font-size: var(--ft12); padding-left: 23px; background-size: 12px; background-repeat: no-repeat; background-position: 7px center; background-image: url(data:image/svg+xml;base64,PCEtLSBUaGlzIFNvdXJjZSBDb2RlIEZvcm0gaXMgc3ViamVjdCB0byB0aGUgdGVybXMgb2YgdGhlIE1vemlsbGEgUHVibGljCiAgIC0gTGljZW5zZSwgdi4gMi4wLiBJZiBhIGNvcHkgb2YgdGhlIE1QTCB3YXMgbm90IGRpc3RyaWJ1dGVkIHdpdGggdGhpcwogICAtIGZpbGUsIFlvdSBjYW4gb2J0YWluIG9uZSBhdCBodHRwOi8vbW96aWxsYS5vcmcvTVBMLzIuMC8uIC0tPgo8c3ZnIGZpbGw9InJnYmEoMTM1LCAxMzUsIDEzNywgMC45KSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTIgMTIiPgogIDxwYXRoIGZpbGw9ImNvbnRleHQtZmlsbCIgb3BhY2l0eT0iLjQiIGQ9Ik01IDkuMmwyIDEuNlY2LjFMOC41NSA0aC01LjFMNSA2LjF2My4xeiIvPgogIDxwYXRoIGZpbGw9ImNvbnRleHQtZmlsbCIgZD0iTTEuMTggMi42QTEgMSAwIDAgMSAyIDFIMTBhMSAxIDAgMCAxIC44IDEuNkw4IDYuNHY0LjgyYzAgLjYzLS43Mi45OC0xLjIyLjZsLTIuNS0xLjk5QS43NS43NSAwIDAgMSA0IDkuMjVWNi40MUwxLjE4IDIuNnpNMiAyTDUgNi4wOXYzLjA0bDIgMS41OVY2LjA5TDEwLjAxIDJIMnoiLz4KPC9zdmc+Cg==); } .clear { flex: 0 0 auto; align-self: center; margin: 0 4px; padding: 0; border: 0; width: 16px; height: 16px; background-color: transparent; background-image: url(data:image/svg+xml;base64,PCEtLSBUaGlzIFNvdXJjZSBDb2RlIEZvcm0gaXMgc3ViamVjdCB0byB0aGUgdGVybXMgb2YgdGhlIE1vemlsbGEgUHVibGljCiAgIC0gTGljZW5zZSwgdi4gMi4wLiBJZiBhIGNvcHkgb2YgdGhlIE1QTCB3YXMgbm90IGRpc3RyaWJ1dGVkIHdpdGggdGhpcwogICAtIGZpbGUsIFlvdSBjYW4gb2J0YWluIG9uZSBhdCBodHRwOi8vbW96aWxsYS5vcmcvTVBMLzIuMC8uIC0tPgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIGZpbGw9ImNvbnRleHQtZmlsbCIgZmlsbC1vcGFjaXR5PSJjb250ZXh0LWZpbGwtb3BhY2l0eSI+CiAgPHBhdGggZD0iTTYuNTg2IDhsLTIuMjkzIDIuMjkzYTEgMSAwIDAgMCAxLjQxNCAxLjQxNEw4IDkuNDE0bDIuMjkzIDIuMjkzYTEgMSAwIDAgMCAxLjQxNC0xLjQxNEw5LjQxNCA4bDIuMjkzLTIuMjkzYTEgMSAwIDEgMC0xLjQxNC0xLjQxNEw4IDYuNTg2IDUuNzA3IDQuMjkzYTEgMSAwIDAgMC0xLjQxNCAxLjQxNEw2LjU4NiA4ek04IDBhOCA4IDAgMSAxIDAgMTZBOCA4IDAgMCAxIDggMHoiLz4KPC9zdmc+Cg==); } .tabs-item{ border-width: 3px; border-top: 3px solid var(--tab-color); } .tabs-item:hover{ border-top-color: #c3c3c6; } .tabs-item, .toolbar-item{ cursor: pointer; padding: 0 10px; font-size: var(--ft12); } .tabs-item.active{ color: #0060df; border-top-color: #0060df; background-color: var(--color-e9); } .tabs-item:hover, .toolbar-item:hover{ // background-color: var(--color-e9); background-color: #D4D4D4 } /** 工具栏样式 END **/ /** JSON 格式化样式 START **/ ul.json-object, ul.json-array { margin: 0 0 0 2px; list-style-type: none; border-left: 1px dotted #5D6D7E; padding-left: 24px; } .json-brackets { font-weight: 700; } .json-key { color: #910F93; cursor: pointer; } .json-string, .json-string a{ color: #4B8A4C; } .json-number{ color: #1a01cc; } .json-boolean{ color: #905; } .json-null { color: #0031BC; } a.json-toggle { position: rElative; color: inherit; opacity: 0.2; text-decoration: none; } a.json-toggle:hover { opacity: 0.35; } a.json-toggle:active { opacity: 0.5; } a.json-toggle:focus { outline: none; } a.json-toggle:before { top: 2.5px; left: -15px; position: absolute; content: ""; display: block; width: 0; height: 0; border-style: solid; border-width: 5px 0 5px 8px; border-color: transparent transparent transparent currentColor; transform: rotate(90deg); } a.json-toggle.collapsed:before { transform: rotate(0deg); } a.json-placeholder { color: var(--color-gray); font-size: var(--ft12); padding: 0 1em; text-decoration: none; } a.json-placeholder:hover { text-decoration: underline; } .json-curly-brackets { color: #6D9331; } .json-square-brackets{ color: #8E9331; } /**浅色主题 START **/ .light .json-key { color: #0451A5; } .light .json-string, .light .json-string a{ color: #A31515; } .light .json-number{ color: #0B7500; } .light .json-boolean{ color: #0000FF; } .light .json-null { color: #0055FF; } /**浅色主题 END **/ /** 暗黑主题 START **/ body.dark{ background-color: #333333; } .dark li, .dark pre{ color: var(--color-gray); } .dark .json-toggle { opacity: 0.35; } .dark .json-toggle:hover { opacity: 0.5; } .dark .json-curly-brackets { color: #CE70D6; } .dark .json-square-brackets{ color: #F1D700; } .dark .json-key { color: #7CDCFE; } .dark .json-string, .dark .json-string a{ color: #CE9178; } .dark .json-number{ color: #B5CEA8; } .dark .json-boolean{ color: #569CD6; } .dark .json-null { color: #2D7AD6; } .dark jmnode { color: #7CDCFE !important; } /** 暗黑主题 END **/ /** JSON 格式化样式 END **/ /** 脑图样式 START **/ #jmContainer{ width: 100vw; height: calc(100vh - 57px); } jmnode{ display: flex; align-items: center; padding: 0 7px 0 22px; } jmnode{ color: #475872 !important; box-shadow: none !important; background-color: transparent !important; } jmnode:hover{ text-shadow: 0px 0px 1px currentColor; } jmnode.root { padding: 0; color: transparent !important; } jmnode:not(.root)::before, jmnode.root::before{ content: " "; top: 50%; position: absolute; border-radius: 50%; transform: translateY(-50%); } jmnode:not(.root)::before{ left: 0; width: 15px; height: 15px; background-color: ${Utils.rgbaColor(0.5)}; } jmnode.root::before{ left: 50%; width: 18px; height: 18px; transform: translate(-18px, -50%); background-color: ${Utils.rgbaColor(0.5)}; } jmexpander{ margin-top: 1px; line-height: 9px; background-color: #dfdfdf; } .mind-array{ opacity: 0.6; font-size: var(--ft12); padding-left: 5px; } /** 脑图样式 END **/ /** 容器样式 START **/ .tabs-container{ overflow: auto; line-height: 1.4; font-family: monospace; } .tabs-container > div{ display: none; } .tabs-container > div.active{ display: block; } .tabs-container #formatContainer{ padding: 10px; } .tabs-container #rawTextContainer{ padding: 0 10px; } .tabs-container #rawTextContainer pre{ display: block !important; overflow-wrap: break-word; white-space: pre-wrap; } /** 容器样式 END **/ .layui-layer-tips{ width: auto !important; } .tabs .selectbox{ position: absolute; right: 200px; display: flex; font-size: var(--ft13); } /** 表格 START **/ table { width: -webkit-fill-available; margin-left: 20px; border-collapse: collapse; } table tr:hover{ background-color: #f0f9fe; } .dark table tr:hover{ background-color: #353B48 } table tr.selected td, table tr.selected td a{ color: #fff !important; background-color: #3875d7; } table tbody tr td:first-child{ width: 120px; } .simple-tree-table-icon{ color: #000; opacity: 0.2; width: 0 !important; margin: 0 !important; line-height: 0 !important; } .simple-tree-table-icon:hover { opacity: 0.35; } .simple-tree-table-icon:active { opacity: 0.5; } .simple-tree-table-icon:after{ content: "" !important; } .simple-tree-table-icon:before { top: 0.5px; left: -15px; position: relative; content: ""; width: 0; height: 0; display: none; border-style: solid; border-width: 5px 0 5px 8px; border-color: transparent transparent transparent currentColor; transform: rotate(90deg); } .simple-tree-table-opened .simple-tree-table-icon:before { display: block; } .simple-tree-table-closed .simple-tree-table-icon:before { display: block; transform: rotate(0deg); } .dark .simple-tree-table-icon{ color: #FFF; opacity: 0.5; } .tree-len{ color: var(--color-gray); font-size: var(--ft13); } textarea:focus { outline: 0; } .inputJson, .fetchApi{ cursor: pointer; color: #0060df; margin-left: 15px; } /** 表格 END **/ .httpRequest{ padding: 20px; } .httpRequest input, .httpRequest select { border-radius: 0; padding-left: 10px; border: 1px solid var(--color-gray); } .requestbox, .textarea { width: 700px; display: flex; } .requestbox { height: 35px; margin-bottom: 15px; } .textarea input { flex-grow: 1; height: 30px; } .requestbox input { flex-grow: 1; } .requestbox button { cursor: pointer; padding: 0 15px; border: 1px solid var(--color-gray); } .requestbox button:active { background-color: #cfcfcf; } `) source.hide() // 将内容用eval函数处理下 window.globalJSON = eval(`(${rawText})`); $("body").addClass('dark').append(`
JSON格式化
JSON脑图
原始数据
JSON 输入 HTTP 请求
保存
复制
全部折叠
全部展开
`) let btnEvent = { firstFormat: true, isBeautify: false, $rawText: $('#rawTextContainer'), /** * 保存为文件 */ download: { download: function (content, filename) { const link = document.createElement("a") link.href = content link.download = filename link.click() }, saveJSON: function (text) { // 创建一个 Blob 对象,包含要下载的文本内容 const blob = new Blob([text], { type: "text/plain;charset=utf-8" }); const url = URL.createObjectURL(blob) let filename = new Date().getTime() + '.json'; this.download(url, filename) URL.revokeObjectURL(url); }, savePNG: () => jm.shoot(), }, saveJson: function () { if ($('#jmContainer').is(':visible')) { this.download.savePNG() } else { this.download.saveJSON(this.$rawText.text()) } }, // 复制JSON文本内容 copyJson: function () { GM_setClipboard(this.$rawText.text()) layer.msg('复制成功', { time: 1500 }) }, // 全部折叠 collapseAll: function () { if ($('#formatContainer').is(':visible')) { try { $('a.json-toggle').not('.collapsed').click() } catch (e) { } } else { jm.collapse_all() } }, // 全部展开 expandAll: function () { if ($('#formatContainer').is(':visible')) { try { $('a.json-placeholder').click().remove() } catch (e) { } } else { jm.expand_all() jm.scroll_node_to_center(jm.get_root()) } }, format: function () { }, // 显示JSON脑图 viewJsonMind: function () { jsonMind.init(globalJSON) jm.scroll_node_to_center(jm.get_root()) }, // 查看原始JSON内容 viewRawText: function () { if (this.firstFormat) { this.$rawText.html(source.clone()) this.firstFormat = false } }, // 美化 beautify: function () { this.isBeautify = !this.isBeautify if (this.isBeautify) { let str = JSON.stringify(globalJSON, null, 2) if (globalJSONPFun !== undefined && globalJSONPFun !== null) { str = `${globalJSONPFun}(${str})` } this.$rawText.find('pre').text(str) } else { this.$rawText.html(source.clone()) } }, init: function () { this.viewRawText() // 按钮点击事件 $('.btn').click(e => { const target = e.target, id = target.id if (target.classList.contains('tabs-item')) { let index = $(target).index() $(target).addClass('active').siblings().removeClass("active") $('.tabs-container > div').removeClass("active").eq(index).addClass('active') let beautifyEl = $('#beautify'), searchEl = $('.searchbox'), copyJsonEl = $('#copyJson'), aEl = $('#collapseAll, #expandAll') id === 'format' ? searchEl.show() : searchEl.hide() id === 'viewJsonMind' ? copyJsonEl.hide() : copyJsonEl.show() id === 'viewRawText' ? (beautifyEl.show() && aEl.hide()) : (beautifyEl.hide() && aEl.show()) } this[id](target) }) return this } }, jsonMind = { isFirst: true, // JSON数据转换为jsMind所需要的数据结构 convert: function (json) { let children = [] if (typeof json === 'object') { for (let key in json) { let val = json[key], isArray = Array.isArray(val) if (isArray && val.length > 0) { val = Utils.findMaxKeysObject(val) } children.push({ isArray, chain: key, id: key + '_' + Math.random(), topic: `${key}${Utils.getType(json[key])}`, children: this.convert(val) }) } } return children; }, // 脑图节点调用链 mindChain: function (node) { let chain = node.data.chain if (!node.parent) { return chain } let parent = node.parent, parentChain = this.mindChain(parent) chain = parent.data.isArray ? `${parentChain}[0].${chain}` : `${parentChain}.${chain}` return chain }, // 显示脑图 show: function (json) { let isArr = Array.isArray(json); if (isArr) { if (typeof json[0] !== 'object') { layer.msg('数据结构无法生成脑图', { time: 1000 }) return this } json = json[0] } if (!this.isFirst) { return this } jm.show({ "meta": { "name": "JSON脑图", "author": "1220301855@qq.com", "version": "1.0" }, "format": "node_tree", /* 数据内容 */ "data": { "id": "root", "topic": 'Response', "direction": "left", "children": this.convert(json), "chain": isArr ? 'Response[0]' : 'Response' } }) this.isFirst = false return this }, // 脑图节点事件 event: function () { $("jmnode").on('dblclick mouseover mouseout', function (event) { let that = $(this), node = jm.get_node(that.attr('nodeid')) if (!node.parent) { return } switch (event.type) { case 'dblclick': GM_setClipboard(jsonMind.mindChain(node)) layer.msg('节点路径复制成功', { time: 1500 }) break; case 'mouseover': let s = `节点路径(双击复制)
${jsonMind.mindChain(node)}` layer.tips(s, that, { time: 0, tips: [2, '#2e59a7'] }); break; default: layer.closeAll() break; } }) return this }, collapseOrExpand: function () { $("jmnode").on('click', function () { let node = jm.get_node($(this).attr('nodeid')) jm.toggle_node(node) }) return this }, init: function (json) { if (!window.jm) { window.jm = new jsMind({ mode: 'side', editable: false, container: 'jmContainer', view: { hmargin: 50, // 思维导图距容器外框的最小水平距离 vmargin: 50, // 思维导图距容器外框的最小垂直距离 engine: 'svg', // 思维导图各节点之间线条的绘制引擎 draggable: true, // 当容器不能完全容纳思维导图时,是否允许拖动画布代替鼠标滚动 support_html: false, line_color: '#C4C9D0', }, zoom: { // 配置缩放 min: 0.1, // 最小的缩放比例 max: 2.1, // 最大的缩放比例 step: 0.1, // 缩放比例间隔 }, layout: { vspace: 7, // 节点之间的垂直间距 hspace: 150, // 节点之间的水平空间 }, }); } this.show(json).event().collapseOrExpand() } }, otherEvent = { // 过滤 JSON filterJSON: function (filter) { let style = GM_getValue('formatStyle') || 'default' if (!filter) { style == 'default' ? $('#formatContainer li').removeClass('hidden') : $('.json-key').parent().removeClass('hidden') return } let chainSet = new Set() /** * 模糊匹配 JSON key * 假如`filter`值为`id`, querySelectorAll得到DOM节点 * 得到:['.feedList.0.images.0.user_id', '.feedList.0.images.0', '.feedList.0.images', '.feedList.0', '.feedList'] */ document.querySelectorAll('#formatContainer *[json-path]').forEach(el => { let chain = $(el).attr('json-path') if (!chain) { return } let newChain = chain.substr(chain.lastIndexOf('.')) if (!newChain.toLowerCase().includes(filter.toLowerCase())) { return } chainSet.add(chain) while (chain = chain.substr(0, chain.lastIndexOf('.'))) { chainSet.add(chain) } }) /** * 模糊匹配 JSON value */ document.querySelectorAll("#formatContainer *[class*='json-']:not([class*='json-key']):not([class*='json-brackets'])") .forEach(el => { let target = $(el), chain = style == 'default' ? target.parent().attr('json-path') : target.siblings('.json-key').attr('json-path') if (!chain) { return } let text = target.text() if (!text.toLowerCase().includes(filter.toLowerCase())) { return } chainSet.add(chain) while (chain = chain.substr(0, chain.lastIndexOf('.'))) { chainSet.add(chain) } }) // console.log(chainSet) style == 'default' ? $('#formatContainer li').addClass('hidden') : $('.json-key').parent().addClass('hidden') chainSet.forEach(chain => { style == 'default' ? $(`#formatContainer *[json-path="${chain}"]`).removeClass('hidden') : $(`#formatContainer *[json-path="${chain}"]`).parent().removeClass('hidden') }) }, // JSON 过滤 input: function () { let that = this $('input').on('input', function () { let val = $(this).val() val === '' ? $('.clear').attr('hidden', true) : $('.clear').attr('hidden', false) that.filterJSON(val) }) return that }, // 清空输入框内容 clear: function () { let that = this $('.clear').click(function () { that.filterJSON() $('input').val('') $(this).attr('hidden', true) }) return this }, // 返回顶部 scrollTop: function () { $('.scroll-top').click(function () { $('.tabs-container').animate({ scrollTop: '0' }, 1000); }) $('.tabs-container').scroll(function (e) { let scrollTop = $(this).scrollTop() scrollTop > 500 ? $('.scroll-top').fadeIn() : $('.scroll-top').fadeOut() }); return this }, // 所有a标签,看是否是图片,是图片生成预览图 urlHover: function () { $("#formatContainer").on({ mouseenter: function () { var that = $(this), href = that.attr('href') if (Utils.isImg(href)) { layer.tips(``, that, { time: 0, anim: 5, maxWidth: 500, tips: [3, '#d9d9d9'] }); } }, mouseleave: () => layer.closeAll() }, 'a[href]') return this }, // 提示key的JSONPath tipsJsonPath: function () { var that = this $("#formatContainer").on({ mouseenter: function () { let jsonPath = that.getJsonPath(this) let tips = `ctrl + 点击复制
${jsonPath}` layer.tips(tips, this, { time: 0, anim: 5, maxWidth: 500, tips: [1, '#2e59a7'] }) }, mouseleave: () => layer.closeAll() }, '.json-key') return this }, // 单击key复制JSONPath copyJsonPath: function () { var that = this $("#formatContainer").on('click', '.json-key', function (event) { if (event.ctrlKey) { let jsonPath = that.getJsonPath(this) GM_setClipboard(jsonPath) layer.msg('复制成功', { time: 1500 }) } }) return this }, getJsonPath: function (element) { let style = GM_getValue('formatStyle') || 'default' let jsonPath = style == 'default' ? $(element).parent().attr('json-path') : $(element).attr('json-path') return jsonPath.split('.').reduce((prev, next) => /^\d+$/.test(next) ? prev + `[${next}]` : prev + '.' + next) }, inputJson: function () { let that = this $('.inputJson').off('click').click(function () { layer.prompt({ title: "JSON 输入", formType: 2, shadeClose: true, maxlength: 1000000, }, function (text) { if (!text) { layer.msg("内容不能为空", { time: 1500 }) return } let rawText = text, oldGlobalJSONPFun = globalJSONPFun // 判断是否为jsonp格式 let match = text.match(/^([^\s(]*)\s*\(([\s\S]*)\)\s*;?$/) if (match && match[1] && match[2]) { globalJSONPFun = match[1] text = match[2] } try { let json = JSON.parse(JSON.stringify(eval(`(${text})`))) that.reload(json, rawText) } catch (e) { console.log(e) globalJSONPFun = oldGlobalJSONPFun layer.msg("JSON不合法", { time: 1500 }) } }) }) return this }, fetchApi: function () { let that = this $('.fetchApi').off('click').click(function () { layer.open({ type: 1, closeBtn: 0, //不显示关闭按钮 shadeClose: true, //开启遮罩关闭 title: 'HTTP 请求', content: `
` }) $('form').submit(function (event) { event.preventDefault(); let serialize = $(this).serializeArray() let form = {} for (let key in serialize) { let it = serialize[key] form[it.name] = it.value } if (form.url === '') { layer.msg('请求URL不能为空') return } let headers = form.headers let params = form.params try { if (headers) { headers = JSON.parse(headers) } } catch (e) { layer.msg('请求头格式不合法') return } try { if (params) { params = JSON.parse(params) } } catch (e) { layer.msg('请求参数格式不合法') return } layer.load(0, { shade: false }); $.ajax({ url: 'https://fetch-api.feny.ink/httpRequest', type: 'POST', data: JSON.stringify(form), contentType: 'application/json' }).then(response => { globalJSONPFun = null that.reload(response, JSON.stringify(response)) }, error => { layer.closeAll() console.log(error); }) }) }) return this }, reload: function (json, rawText) { globalJSON = json source.text(rawText) btnEvent.isBeautify = false btnEvent.$rawText.html(source.clone()) formatStyle.setStyle() jsonMind.isFirst = true jsonMind.init(globalJSON) layer.closeAll() }, init: function () { this.input().clear().scrollTop().urlHover().tipsJsonPath().copyJsonPath() .inputJson().fetchApi() } }, theme = { // 切换主题 changeTheme: function () { let that = this $('.theme select').change(function (e) { let val = $(e.target).val() GM_setValue('theme', val) that.setTheme() }) return this }, // 设置主题 setTheme: function () { let theme = GM_getValue('theme') || 'default' $('body').removeClass().addClass(theme) $('.theme select').val(theme) return this }, init: function () { this.setTheme().changeTheme() } }, formatStyle = { // 切换风格 changeStyle: function () { let that = this $('.formatStyle select').change(function (e) { let val = $(e.target).val() GM_setValue('formatStyle', val) that.setStyle() }) return this }, // 设置风格 setStyle: function () { let style = GM_getValue('formatStyle') || 'default' $('.formatStyle select').val(style) $('input').val('') $('#formatContainer').html('') if (style === 'default') { $('#formatContainer').jsonViewer(globalJSON, globalJSONPFun) } else { let appendHtml = `${treeTableHtml(globalJSON)}
` if (globalJSONPFun !== undefined && globalJSONPFun !== null) { appendHtml = `
${globalJSONPFun}(
${appendHtml}
)
` } $('#formatContainer').append(appendHtml) $('#treeTable').simpleTreeTable({ expander: '#expandAll', collapser: '#collapseAll', }) // Highlight selected row $('#treeTable').on("mousedown", "tr", function () { $(".selected").not(this).removeClass("selected"); $(this).toggleClass("selected") }) } return this }, init: function () { this.setStyle().changeStyle() theme.init() btnEvent.init() setTimeout(() => otherEvent.init(), 1000) } } formatStyle.init() /** * 表格 */ function treeTableHtml(json, level = 0, pId = '', pChain = '') { let tr = '' for (let key in json) { let val = json[key], type = Utils.getType(val), tId = key + '_' + Math.random(), chain = pChain + "." + key if (['array', 'object'].includes(type)) { let brackets = '', len = Object.keys(val).length, res = treeTableHtml(val, level + 1, tId, chain) if (!res) { if (type === 'array') { brackets = `[]` } else { brackets = `{}` } } tr += ` ${key}: ${len > 0 ? (type === 'array' ? '[' + len + ']' : '{' + len + '}') : ''} ${brackets} ` tr += res } else { val = (type === 'string') ? val.replace(/&/g, '&').replace(//g, '>') : val tr += ` ${key}:` if (Utils.isUrl(val)) { tr += `"${val}"` } else { val = (type === 'string') ? `"${val}"` : val tr += `${val}` } tr += '' } } return tr; } })(_jQuery) const openInTab = () => GM_openInTab('https://fetch-api.feny.ink/example.json') GM_registerMenuCommand('测试JSON( Alt + j )', openInTab); document.addEventListener('keydown', function (event) { if (event.altKey && event.key === 'j') { openInTab() } }) })();