JSON Viewer

格式化显示JSON使数据看起来更加漂亮,支持折叠/展开格式化后的数据,支持JSON脑图让调用层级看着更清晰,支持复制JSON脑图节点路径

当前为 2024-10-07 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @license MIT
  3. // @name JSON Viewer
  4. // @namespace http://tampermonkey.net/
  5. // @version 0.5.4
  6. // @note v0.5.4 单击复制修改为CTRL+单击复制JSONPath功能,JSON格式化风格增加table格式
  7. // @note v0.5.3 增加暗黑主题色
  8. // @note v0.5.2 单击JSON格式化的key可复制JSONPath
  9. // @note v0.5.1 修复JSONPath提示有误
  10. // @note v0.5.0 解决chrome 120+以上内核JSON格式化不执行和引入layer报错问题
  11. // @note v0.4.9 布局修改,增加保存JSON/脑图为文件,增加JSON过滤,鼠标移入key提示JSONPath
  12. // @note v0.4.8 代码优化
  13. // @note v0.4.7 增加对JSONP的判断,代码优化
  14. // @note v0.4.6 增加复制按钮,JSON脑图CSS样式细节优化,JSON脑图增加收起/展开子节点按钮
  15. // @note v0.4.5 在json-viewer-updated原基础上进行了一些修改,主要有CSS样式修改,新增折叠/展开全部功能,新增JSON脑图功能,脑图节点点击显示调用路径
  16. // @description 格式化显示JSON使数据看起来更加漂亮,支持折叠/展开格式化后的数据,支持JSON脑图让调用层级看着更清晰,支持复制JSON脑图节点路径
  17. // @author Feny
  18. // @match *://*/*
  19. // @grant GM_addStyle
  20. // @grant GM_getValue
  21. // @grant GM_setValue
  22. // @grant unsafeWindow
  23. // @grant GM_setClipboard
  24. // @grant GM_getResourceText
  25. // @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==
  26. // @require https://code.jquery.com/jquery.min.js
  27. // @require https://unpkg.com/jsmind@0.8.5/es6/jsmind.js
  28. // @require https://unpkg.com/dom-to-image@2.6.0/dist/dom-to-image.min.js
  29. // @require https://unpkg.com/jsmind@0.8.5/es6/jsmind.screenshot.js
  30. // @require https://cdn.jsdelivr.net/npm/@kanety/jquery-simple-tree-table@0.5.1/dist/jquery-simple-tree-table.min.js
  31. // @resource swalStyle https://unpkg.com/jsmind@0.8.5/style/jsmind.css
  32. // ==/UserScript==
  33.  
  34.  
  35. (function() {
  36. 'use strict';
  37.  
  38. const Utils = {
  39. // 检查字符串是否为URL
  40. isUrl: function (string) {
  41. var regexp = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
  42. return regexp.test(string);
  43. },
  44.  
  45. // 检查是否是图片链接
  46. isImg: function (pathImg) {
  47. // var regexp = /^(http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?\/([\w#!:.?+=&%@!\-\/])*\.(gif|jpg|jpeg|png|GIF|JPG|PNG)([\w#!:.?+=&%@!\-\/])?/;
  48. let regexp = /\.(ico|bmp|gif|jpg|jpeg|png|svg|webp|GIF|JPG|PNG|WEBP|SVG)([\w#!:.?+=&%@!\-\/])?/i
  49. return regexp.test(pathImg);
  50. },
  51. // 检验内容是否是json格式的内容
  52. isJSON: function (str) {
  53. try {
  54. JSON.parse(str)
  55. return true
  56. } catch(e) {
  57. console.log("is not json", e)
  58. return false
  59. }
  60. },
  61. // 获取数据类型
  62. getType: function (value){
  63. return Object.prototype.toString.call(value).match(/\s(.+)]/)[1].toLowerCase();
  64. },
  65. // JSON 过滤
  66. filterJson: function(json, filter) {
  67. if(!filter){
  68. return json
  69. }
  70.  
  71. filter = filter.toLowerCase()
  72. let newJSON = Utils.getType(json) == 'array' ? [] : {}
  73. for (let key in json) {
  74. let val = json[key]
  75. if (typeof val === 'object') {
  76. let subJSON = this.filterJson(val, filter);
  77. if (Object.keys(subJSON).length > 0) {
  78. newJSON[key] = subJSON;
  79. }
  80. }else{
  81. if(key.toLowerCase().includes(filter.toLowerCase())){
  82. newJSON[key] = val
  83. }
  84.  
  85. if(val !== null && val.toString().toLowerCase().includes(filter.toLowerCase())){
  86. newJSON[key] = val
  87. }
  88. }
  89. }
  90. return newJSON;
  91. }
  92. }
  93.  
  94. // jquery.json-viewer 插件 开始
  95. // 解决和原网页jquery版本冲突
  96. var _jQuery = jQuery.noConflict(true);
  97. (function($){
  98. /**
  99. * 检查 arg 是否为至少包含 1 个元素的数组或至少包含 1 个键的字典
  100. */
  101. function isCollapsable(arg) {
  102. return arg instanceof Object && Object.keys(arg).length > 0;
  103. }
  104.  
  105. /**
  106. * 将 JSON 对象转换为 HTML 表示形式
  107. * @return string
  108. */
  109. function json2html(json, parentPath = '') {
  110. let html = '', type = Utils.getType(json)
  111. switch(type){
  112. case 'array':
  113. case 'object':
  114. let len = json.length || Object.keys(json).length;
  115. if (len > 0) {
  116. html += `<span class="json-brackets ${type == 'array' ? 'json-square-brackets' : 'json-curly-brackets'}">`;
  117. html += type === 'array' ? '[</span><ol class="json-array">' : '{</span><ul class="json-object">';
  118. for (var key in json) {
  119. if (json.hasOwnProperty(key)) {
  120. let comma = --len > 0 ? ',' : '',
  121. jsonPath = parentPath + '.' + key,
  122. collapse = isCollapsable(json[key]) ? '<a href class="json-toggle"></a>' : '',
  123. res = json2html(json[key], jsonPath)
  124. let toHtml = type === 'array' ? res : `<span class="json-key">"${key}"</span>: ${res}`
  125. html += [`<li json-path="${jsonPath}">`, collapse, toHtml, comma, '</li>'].join('')
  126. }
  127. }
  128.  
  129. if(type === 'array'){
  130. html += `</ol><span class="json-brackets ${type == 'array' ? 'json-square-brackets' : 'json-curly-brackets'}">]</span>`
  131. }else{
  132. html += `</ul><span class="json-brackets ${type == 'array' ? 'json-square-brackets' : 'json-curly-brackets'}">}</span>`
  133. }
  134. }else{
  135. html += `<span class="json-brackets ${type == 'array' ? 'json-square-brackets' : 'json-curly-brackets'}">`
  136. html += (type === 'array') ? '[]' : '{}'
  137. html += '</span>'
  138. }
  139. break
  140. default:
  141. /* Escape tags */
  142. json = type === 'string' ? json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') : json
  143. if (Utils.isUrl(json)){
  144. html += `<a target="_blank" href="${json}" class="json-string">"${json}"</a>`;
  145. }else{
  146. json = type === 'string' ? `"${json}"` : json
  147. html += `<span class="json-${type}">${json}</span>`;
  148. }
  149. break
  150. }
  151. return html;
  152. }
  153.  
  154. $.fn.jsonViewer = function(json, jsonpFun) {
  155. return this.each(function() {
  156. /* Transform to HTML */
  157. var html = json2html(json);
  158. /** is JSONP */
  159. if(jsonpFun !== undefined && jsonpFun !== null){
  160. html = `<div>${jsonpFun}(</div>${html}<div>)</div>`
  161. }
  162. /* Insert HTML in target DOM element */
  163. $(this).html(html);
  164.  
  165. /* Bind click on toggle buttons */
  166. $(this).off('click');
  167. $(this).on('click', 'a.json-toggle', function() {
  168. var target = $(this).toggleClass('collapsed').siblings('ul.json-object, ol.json-array');
  169. target.toggle();
  170. if (target.is(':visible')) {
  171. target.siblings('.json-placeholder').remove();
  172. }else {
  173. var count = target.children('li:not([class*="hidden"])').length;
  174. var placeholder = count + (count > 1 ? ' items' : ' item');
  175. target.after('<a href class="json-placeholder">' + placeholder + '</a>');
  176. }
  177. return false;
  178. });
  179.  
  180. /* Simulate click on toggle button when placeholder is clicked */
  181. $(this).on('click', 'a.json-placeholder', function() {
  182. $(this).siblings('a.json-toggle').click();
  183. $(this).siblings('a.json-placeholder').remove();
  184. return false;
  185. });
  186. });
  187. };
  188. })(_jQuery);
  189. // jquery.json-viewer 插件 结束
  190.  
  191. (function($){
  192. var source = $('pre').first();
  193. if(source.length === 0){
  194. return
  195. }
  196. let rawText = source.text()
  197. if(!rawText){
  198. return
  199. }
  200.  
  201. // 判断是否为jsonp格式
  202. let jsonpFun = null, tokens = rawText.match(/^([^\s(]*)\s*\(([\s\S]*)\)\s*;?$/)
  203. if (tokens && tokens[1] && tokens[2]) {
  204. jsonpFun = tokens[1]
  205. rawText= tokens[2]
  206. }
  207.  
  208. if(!Utils.isJSON(rawText)){
  209. return
  210. }
  211.  
  212. // 随机rgb颜色
  213. let rgbaColor = `${Math.random()*256}, ${Math.random()*256}, ${Math.random()*256}`
  214. // 添加样式
  215. GM_addStyle(GM_getResourceText('swalStyle'))
  216. $("head").append(`<link rel="stylesheet" type="text/css" href="https://unpkg.com/layer-src@3.5.1/dist/theme/default/layer.css"/>`)
  217. .append('<script src="https://unpkg.com/layui@2.9.17/dist/layui.js" type="text/javascript" ></script>')
  218.  
  219. GM_addStyle(`
  220. body, html{
  221. margin: 0;
  222. padding: 0;
  223. font-size: 14px;
  224. }
  225. td{
  226. font-size: 14px;
  227. }
  228. li::marker {
  229. content: '';
  230. }
  231. .hidden{
  232. display: none !important;
  233. }
  234. .scroll-top{
  235. width: 48px;
  236. height: 48px;
  237. z-index: 999;
  238. position: fixed;
  239. right: 30px;
  240. bottom: 30px;
  241. display: none;
  242. 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=)
  243. }
  244. /** 工具栏样式 START **/
  245. .flex-container{
  246. z-index: 10;
  247. position: fixed;
  248. width: 100vw;
  249. height: 100vh;
  250. display: flex;
  251. flex-direction: column;
  252. }
  253. .tabs, .toolbar{
  254. display: flex;
  255. line-height: 28px;
  256. background: #f3f3f3;
  257. border-bottom: 1px solid #e0e0e2;
  258. }
  259. .toolbar{
  260. line-height: 23px;
  261. }
  262. .searchbox{
  263. display: flex;
  264. flex-grow: 1;
  265. }
  266. .toolbar input{
  267. flex-grow: 1;
  268. border: none;
  269. outline: none;
  270. font-size: 12px;
  271. padding-left: 23px;
  272. background-size: 12px;
  273. background-repeat: no-repeat;
  274. background-position: 7px center;
  275. background-image: url(data:image/svg+xml;base64,PCEtLSBUaGlzIFNvdXJjZSBDb2RlIEZvcm0gaXMgc3ViamVjdCB0byB0aGUgdGVybXMgb2YgdGhlIE1vemlsbGEgUHVibGljCiAgIC0gTGljZW5zZSwgdi4gMi4wLiBJZiBhIGNvcHkgb2YgdGhlIE1QTCB3YXMgbm90IGRpc3RyaWJ1dGVkIHdpdGggdGhpcwogICAtIGZpbGUsIFlvdSBjYW4gb2J0YWluIG9uZSBhdCBodHRwOi8vbW96aWxsYS5vcmcvTVBMLzIuMC8uIC0tPgo8c3ZnIGZpbGw9InJnYmEoMTM1LCAxMzUsIDEzNywgMC45KSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTIgMTIiPgogIDxwYXRoIGZpbGw9ImNvbnRleHQtZmlsbCIgb3BhY2l0eT0iLjQiIGQ9Ik01IDkuMmwyIDEuNlY2LjFMOC41NSA0aC01LjFMNSA2LjF2My4xeiIvPgogIDxwYXRoIGZpbGw9ImNvbnRleHQtZmlsbCIgZD0iTTEuMTggMi42QTEgMSAwIDAgMSAyIDFIMTBhMSAxIDAgMCAxIC44IDEuNkw4IDYuNHY0LjgyYzAgLjYzLS43Mi45OC0xLjIyLjZsLTIuNS0xLjk5QS43NS43NSAwIDAgMSA0IDkuMjVWNi40MUwxLjE4IDIuNnpNMiAyTDUgNi4wOXYzLjA0bDIgMS41OVY2LjA5TDEwLjAxIDJIMnoiLz4KPC9zdmc+Cg==);
  276. }
  277. .clear {
  278. flex: 0 0 auto;
  279. align-self: center;
  280. margin: 0 4px;
  281. padding: 0;
  282. border: 0;
  283. width: 16px;
  284. height: 16px;
  285. background-color: transparent;
  286. background-image: url(data:image/svg+xml;base64,PCEtLSBUaGlzIFNvdXJjZSBDb2RlIEZvcm0gaXMgc3ViamVjdCB0byB0aGUgdGVybXMgb2YgdGhlIE1vemlsbGEgUHVibGljCiAgIC0gTGljZW5zZSwgdi4gMi4wLiBJZiBhIGNvcHkgb2YgdGhlIE1QTCB3YXMgbm90IGRpc3RyaWJ1dGVkIHdpdGggdGhpcwogICAtIGZpbGUsIFlvdSBjYW4gb2J0YWluIG9uZSBhdCBodHRwOi8vbW96aWxsYS5vcmcvTVBMLzIuMC8uIC0tPgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIGZpbGw9ImNvbnRleHQtZmlsbCIgZmlsbC1vcGFjaXR5PSJjb250ZXh0LWZpbGwtb3BhY2l0eSI+CiAgPHBhdGggZD0iTTYuNTg2IDhsLTIuMjkzIDIuMjkzYTEgMSAwIDAgMCAxLjQxNCAxLjQxNEw4IDkuNDE0bDIuMjkzIDIuMjkzYTEgMSAwIDAgMCAxLjQxNC0xLjQxNEw5LjQxNCA4bDIuMjkzLTIuMjkzYTEgMSAwIDEgMC0xLjQxNC0xLjQxNEw4IDYuNTg2IDUuNzA3IDQuMjkzYTEgMSAwIDAgMC0xLjQxNCAxLjQxNEw2LjU4NiA4ek04IDBhOCA4IDAgMSAxIDAgMTZBOCA4IDAgMCAxIDggMHoiLz4KPC9zdmc+Cg==);
  287. }
  288. .tabs-item{
  289. border-top: 2px solid #f3f3f3;
  290. }
  291. .tabs-item:hover{
  292. background: #e9e9e9;
  293. border-top: 2px solid #c3c3c6;
  294. }
  295. .tabs-item, .toolbar-item{
  296. cursor: pointer;
  297. padding: 0 10px;
  298. font-size: 12px;
  299. }
  300. .tabs-item.active{
  301. color: #0060df;
  302. border-top: 2px solid #0060df !important;
  303. background: #e9e9e9;
  304. }
  305.  
  306. .tabs-item:hover, .toolbar-item:hover{
  307. background: #e9e9e9;
  308. }
  309. /** 工具栏样式 END **/
  310. /** JSON 格式化样式 START **/
  311. ul.json-object,
  312. ul.json-array {
  313. list-style-type: none;
  314. margin: 0 0 0 2px;
  315. border-left: 1px dotted #5D6D7E;
  316. padding-left: 24px;
  317. }
  318. .json-brackets {
  319. font-weight: 700;
  320. }
  321. .json-key {
  322. /* color: #A31515;*/
  323. color: #910F93;
  324. cursor: pointer;
  325. }
  326. .json-string, .json-string a{
  327. /* color: #0b7500;*/
  328. color: #4B8A4C;
  329. }
  330. .json-number{
  331. /* color: #164FF0;*/
  332. color: #1a01cc;
  333. font-weight: 600;
  334. }
  335. .json-boolean{
  336. color: #905;
  337. font-weight: 600;
  338. }
  339. .json-null {
  340. /* color: #F1592A;*/
  341. color: #0031BC;
  342. font-weight: 600;
  343. }
  344. a.json-toggle {
  345. position: rElative;
  346. color: inherit;
  347. opacity: 0.2;
  348. text-decoration: none;
  349. }
  350. a.json-toggle:hover {
  351. opacity: 0.35;
  352. }
  353. a.json-toggle:active {
  354. opacity: 0.5;
  355. }
  356. a.json-toggle:focus {
  357. outline: none;
  358. }
  359. a.json-toggle:before {
  360. top: 2.5px;
  361. left: -15px;
  362. position: absolute;
  363. content: "";
  364. display: block;
  365. width: 0;
  366. height: 0;
  367. border-style: solid;
  368. border-width: 5px 0 5px 8px;
  369. border-color: transparent transparent transparent currentColor;
  370. transform: rotate(90deg);
  371. }
  372. a.json-toggle.collapsed:before {
  373. transform: rotate(0deg);
  374. }
  375. a.json-placeholder {
  376. color: #aaa;
  377. font-size: 12px;
  378. padding: 0 1em;
  379. text-decoration: none;
  380. }
  381. a.json-placeholder:hover {
  382. text-decoration: underline;
  383. }
  384. .json-curly-brackets {
  385. color: #6D9331;
  386. }
  387. .json-square-brackets{
  388. color: #8E9331;
  389. }
  390. /** 暗黑主题 START **/
  391. body.dark{
  392. background: #333333;
  393. }
  394. .dark li, .dark pre{
  395. color: #CCC;
  396. }
  397. .dark .json-toggle {
  398. opacity: 0.35;
  399. }
  400. .dark .json-toggle:hover {
  401. opacity: 0.5;
  402. }
  403. .dark .json-curly-brackets {
  404. color: #CE70D6;
  405. }
  406. .dark .json-square-brackets{
  407. color: #F1D700;
  408. }
  409. .dark .json-key {
  410. color: #7CDCFE;
  411. }
  412. .dark .json-string, .dark .json-string a{
  413. color: #CE9178;
  414. }
  415. .dark .json-number{
  416. color: #B5CEA8;
  417. }
  418. .dark .json-boolean{
  419. color: #569CD6;
  420. }
  421. .dark .json-null {
  422. color: #2D7AD6;
  423. }
  424. .dark jmnode {
  425. color: #7CDCFE !important;
  426. }
  427. /** 暗黑主题 END **/
  428. /** JSON 格式化样式 END **/
  429. /** 脑图样式 START **/
  430. #jmContainer{
  431. width: 100vw;
  432. height: calc(100vh - 57px);
  433. /* background:#F7F7F7 */
  434. }
  435. jmnode{
  436. display: flex;
  437. align-items: center;
  438. padding: 0 7px 0 22px;
  439. }
  440. jmnode{
  441. color: #475872 !important;
  442. box-shadow: none !important;
  443. background-color: transparent !important;
  444. }
  445. jmnode:hover{
  446. text-shadow: 1px 1px 1px currentColor;
  447. }
  448. jmnode.root {
  449. padding: 0;
  450. color: transparent !important;
  451. }
  452. jmnode:not(.root)::before, jmnode.root::before{
  453. content: " ";
  454. top: 50%;
  455. position: absolute;
  456. border-radius: 50%;
  457. transform: translateY(-50%);
  458. }
  459. jmnode:not(.root)::before{
  460. left: 0;
  461. width: 15px;
  462. height: 15px;
  463. background: rgba(${rgbaColor}, 0.5);
  464. }
  465. jmnode.root::before{
  466. left: 50%;
  467. width: 18px;
  468. height: 18px;
  469. transform: translate(-18px, -50%);
  470. background: rgba(${rgbaColor}, 0.7);
  471. }
  472. jmexpander{
  473. margin-top: 1px;
  474. line-height: 9px;
  475. background: #dfdfdf;
  476. }
  477. .mind-array{
  478. opacity: 0.5;
  479. font-size: 12px;
  480. padding-left: 5px;
  481. }
  482. /** 脑图样式 END **/
  483. /** 容器样式 START **/
  484. .tabs-container{
  485. overflow: auto;
  486. line-height: 1.5;
  487. font-family: monospace;
  488. }
  489. .tabs-container > div{
  490. display: none;
  491. }
  492. .tabs-container > div.active{
  493. display: block;
  494. }
  495. .tabs-container #formatContainer{
  496. padding: 10px;
  497. }
  498. .tabs-container #rawTextContainer{
  499. padding: 0 10px;
  500. }
  501. .tabs-container #rawTextContainer pre{
  502. display: block !important;
  503. overflow-wrap: break-word;
  504. white-space: pre-wrap;
  505. }
  506. /** 容器样式 END **/
  507. .layui-layer-tips{
  508. width: auto !important;
  509. }
  510. .tabs .selectbox{
  511. position: absolute;
  512. right: 200px;
  513. display: flex;
  514. }
  515. /** 表格 START **/
  516. table {
  517. width: 100%;
  518. margin-left: 20px;
  519. border-collapse: collapse;
  520. }
  521. table tr:hover{
  522. background: #f0f9fe;
  523. }
  524. .dark table tr:hover{
  525. background: #353B48
  526. }
  527. table tr.selected td, table tr.selected td a{
  528. color: #fff !important;
  529. background-color: #3875d7;
  530. }
  531. table tbody tr td:first-child{
  532. width: 120px;
  533. }
  534. .simple-tree-table-icon{
  535. color: #000;
  536. opacity: 0.2;
  537. width: 0 !important;
  538. margin: 0 !important;
  539. line-height: 0 !important;
  540. }
  541. .simple-tree-table-icon:hover {
  542. opacity: 0.35;
  543. }
  544. .simple-tree-table-icon:active {
  545. opacity: 0.5;
  546. }
  547. .simple-tree-table-icon:after{
  548. content: "" !important;
  549. }
  550. .simple-tree-table-icon:before {
  551. top: 0.5px;
  552. left: -15px;
  553. position: relative;
  554. content: "";
  555. width: 0;
  556. height: 0;
  557. display: none;
  558. border-style: solid;
  559. border-width: 5px 0 5px 8px;
  560. border-color: transparent transparent transparent currentColor;
  561. transform: rotate(90deg);
  562. }
  563. .simple-tree-table-opened .simple-tree-table-icon:before {
  564. display: block;
  565. }
  566. .simple-tree-table-closed .simple-tree-table-icon:before {
  567. display: block;
  568. transform: rotate(0deg);
  569. }
  570. .dark .simple-tree-table-icon{
  571. color: #FFF;
  572. opacity: 0.5;
  573. }
  574. /** 表格 END **/
  575. `)
  576.  
  577. source.hide()
  578. // 将内容用eval函数处理下
  579. const jsonObject = eval(`(${rawText})`);
  580.  
  581. $("body").addClass('dark').append(`
  582. <div class="scroll-top"></div>
  583. <div class="flex-container">
  584. <div class="panel">
  585. <div class="tabs">
  586. <div class="tabs-item btn active" id="format">JSON格式化</div>
  587. <div class="tabs-item btn" id="viewJsonMind">JSON脑图</div>
  588. <div class="tabs-item btn" id="viewRawText">原始数据</div>
  589. <div class="selectbox">
  590. <div class="formatStyle">
  591. <label>风格:</label>
  592. <select>
  593. <option value="default">默认</option>
  594. <option value="treaTable">表格</option>
  595. </select>
  596. </div>
  597. <div class="theme" style="margin-left: 10px">
  598. <label>主题: </label>
  599. <select>
  600. <option value="default">默认</option>
  601. <option value="dark">暗黑</option>
  602. </select>
  603. </div>
  604. </div>
  605. </div>
  606. <div class="toolbar">
  607. <div class="toolbar-item btn" id="saveJson">保存</div>
  608. <div class="toolbar-item btn" id="copyJson">复制</div>
  609. <div class="toolbar-item btn" id="collapseAll">全部折叠</div>
  610. <div class="toolbar-item btn" id="expandAll">全部展开</div>
  611. <div class="toolbar-item btn" id="beautify" style="display: none">美化输出</div>
  612. <div class="searchbox">
  613. <input type="text" placeholder="过滤 JSON "/>
  614. <button class="clear" hidden></button>
  615. </div>
  616. </div>
  617. </div>
  618. <div class="tabs-container">
  619. <div class="active" id="formatContainer"></div>
  620. <div id="jmContainer"></div>
  621. <div id="rawTextContainer"><pre></pre></div>
  622. </div>
  623. </div>`)
  624.  
  625. let btnEvent = {
  626. isFormater: false,
  627. $rawText: $('#rawTextContainer'),
  628. /**
  629. * 保存为文件
  630. */
  631. download: {
  632. download: function(content, filename) {
  633. const link = document.createElement("a")
  634. link.href = content
  635. link.download = filename
  636. link.click()
  637. },
  638. saveJSON: function (text) {
  639. // 创建一个 Blob 对象,包含要下载的文本内容
  640. const blob = new Blob([text], { type: "text/plain;charset=utf-8" });
  641. const url = URL.createObjectURL(blob)
  642. let filename = new Date().getTime() + '.json';
  643. this.download(url, filename)
  644. URL.revokeObjectURL(url);
  645. },
  646. savePNG: () => jm.shoot(),
  647. },
  648. saveJson:function(){
  649. if($('#jmContainer').is(':visible')){
  650. this.download.savePNG()
  651. }else{
  652. this.download.saveJSON(this.$rawText.text())
  653. }
  654. },
  655. // 复制JSON文本内容
  656. copyJson: function(){
  657. GM_setClipboard(this.$rawText.text())
  658. layer.msg('复制成功', {time: 1500})
  659. },
  660. // 全部折叠
  661. collapseAll: function(){
  662. if($('#formatContainer').is(':visible')){
  663. try{
  664. $('.json-toggle').not('.collapsed').click()
  665. }catch(e){}
  666. }else{
  667. jm.collapse_all()
  668. }
  669. },
  670. // 全部展开
  671. expandAll: function(){
  672. if($('#formatContainer').is(':visible')){
  673. try{
  674. $('a.json-placeholder').click().remove()
  675. }catch(e){}
  676. }else{
  677. jm.expand_all()
  678. jm.scroll_node_to_center(jm.get_root())
  679. }
  680. },
  681. format: function(){},
  682. // 显示JSON脑图
  683. viewJsonMind: function(){},
  684. // 查看原始JSON内容
  685. viewRawText: function(){
  686. this.$rawText.html(source.clone())
  687. },
  688. // 美化
  689. beautify: function(){
  690. this.isFormater = !this.isFormater
  691. if(this.isFormater){
  692. this.$rawText.find('pre').text(JSON.stringify(jsonObject, null, 2))
  693. }else{
  694. this.viewRawText()
  695. }
  696. },
  697. init: function(){
  698. this.viewRawText()
  699.  
  700. // 按钮点击事件
  701. $('.btn').click(e => {
  702. const target = e.target, id = target.id
  703. if(target.classList.contains('tabs-item')){
  704. let index = $(target).index()
  705. $(target).addClass('active').siblings().removeClass("active")
  706. $('.tabs-container > div').removeClass("active").eq(index).addClass('active')
  707.  
  708. let beautifyEl = $('#beautify'),
  709. searchEl= $('.searchbox'),
  710. copyJsonEl= $('#copyJson'),
  711. aEl = $('#collapseAll, #expandAll')
  712. id === 'format' ? searchEl.show(): searchEl.hide()
  713. id === 'viewJsonMind' ? copyJsonEl.hide(): copyJsonEl.show()
  714. id === 'viewRawText' ? (beautifyEl.show() && aEl.hide()) : (beautifyEl.hide() && aEl.show())
  715. }
  716. this[id](target)
  717. })
  718.  
  719. return this
  720. }
  721. },
  722. jsonMind = {
  723. // JSON数据转换为jsMind所需要的数据结构
  724. convert: function(json){
  725. let children = []
  726. if(typeof json === 'object'){
  727. for(let key in json){
  728. let val = json[key],
  729. isArray = Array.isArray(val)
  730.  
  731. children.push({
  732. isArray,
  733. chain: key,
  734. id: key + '_' + Math.random(),
  735. topic: isArray ? `${key}<span class="mind-array">[${val.length}]</span>` : `${key}`,
  736. // children: this.convert(val)
  737. children: isArray ? this.convert(val[0]) : this.convert(val)
  738. })
  739. }
  740. }
  741. return children;
  742. },
  743. // 脑图节点调用链
  744. mindChain: function (node){
  745. let chain = node.data.chain
  746. if(!node.parent){
  747. return chain
  748. }
  749.  
  750. let parent = node.parent, parentChain = this.mindChain(parent)
  751. chain = parent.data.isArray ? `${parentChain}[0].${chain}` : `${parentChain}.${chain}`
  752. return chain
  753. },
  754. // 显示脑图
  755. show: function(json, isArr){
  756. jm.show({
  757. "meta":{
  758. "name":"JSON脑图",
  759. "author":"1220301855@qq.com",
  760. "version":"1.0"
  761. },
  762. "format":"node_tree",
  763. /* 数据内容 */
  764. "data": {
  765. "id": "root",
  766. "topic": 'Response',
  767. "direction": "left",
  768. "children": this.convert(json),
  769. "chain": isArr ? 'Response[0]' : 'Response'
  770. }
  771. })
  772.  
  773. setTimeout(() => jm.scroll_node_to_center(jm.get_root()), 300)
  774. return this
  775. },
  776. // 脑图节点事件
  777. event:function(){
  778. $("jmnode").on('dblclick mouseover mouseout', function(event){
  779. let that = $(this), node = jm.get_node(that.attr('nodeid'))
  780. if(!node.parent){
  781. return
  782. }
  783.  
  784. switch(event.type){
  785. case 'dblclick':
  786. GM_setClipboard(jsonMind.mindChain(node))
  787. layer.msg('节点路径复制成功', {time: 1500})
  788. break;
  789. case 'mouseover':
  790. let s = `<b>节点路径(双击复制)</b><br/>${jsonMind.mindChain(node)}`
  791. layer.tips(s, that, {
  792. time: 0,
  793. tips: [2, '#2e59a7']
  794. });
  795. break;
  796. default:
  797. layer.closeAll()
  798. break;
  799. }
  800. })
  801. return this
  802. },
  803. collapseOrExpand: function(){
  804. $("jmnode").on('click', function(){
  805. let node = jm.get_node($(this).attr('nodeid'))
  806. jm.toggle_node(node)
  807. })
  808. return this
  809. },
  810. init: function(json){
  811. let isArr = Array.isArray(json);
  812. if(isArr){
  813. if(typeof json[0] !== 'object'){
  814. layer.msg('数据结构无法生成脑图', {time: 1000})
  815. return
  816. }
  817. json = json[0]
  818. }
  819.  
  820. if(!window.jm){
  821. window.jm = new jsMind({
  822. mode :'side',
  823. editable: false,
  824. container:'jmContainer',
  825. view: {
  826. hmargin: 50, // 思维导图距容器外框的最小水平距离
  827. vmargin: 50, // 思维导图距容器外框的最小垂直距离
  828. engine: 'svg', // 思维导图各节点之间线条的绘制引擎
  829. draggable: true, // 当容器不能完全容纳思维导图时,是否允许拖动画布代替鼠标滚动
  830. support_html : false,
  831. line_color: '#C4C9D0',
  832. },
  833. layout: {
  834. vspace: 7, // 节点之间的垂直间距
  835. hspace: 150, // 节点之间的水平空间
  836. },
  837. });
  838. }
  839.  
  840. this.show(json, isArr).event().collapseOrExpand()
  841. }
  842. },
  843. otherOperate = {
  844. // 过滤 JSON
  845. filterJSON: function(filter) {
  846. let style = GM_getValue('formatStyle') || 'default'
  847. if(!filter){
  848. style == 'default' ? $('#formatContainer li').removeClass('hidden') : $('.json-key').parent().removeClass('hidden')
  849. return
  850. }
  851.  
  852. let chainSet= new Set()
  853. /**
  854. * 模糊匹配 JSON key
  855. * 假如`filter`值为`id`, querySelectorAll得到DOM节点
  856. * 得到:['.feedList.0.images.0.user_id', '.feedList.0.images.0', '.feedList.0.images', '.feedList.0', '.feedList']
  857. */
  858. document.querySelectorAll('#formatContainer *[json-path]').forEach(el => {
  859. let chain = $(el).attr('json-path')
  860. if(!chain){
  861. return
  862. }
  863. let newChain = chain.substr(chain.lastIndexOf('.'))
  864. if(!newChain.toLowerCase().includes(filter.toLowerCase())){
  865. return
  866. }
  867. chainSet.add(chain)
  868. while(chain = chain.substr(0, chain.lastIndexOf('.'))){
  869. chainSet.add(chain)
  870. }
  871. })
  872.  
  873. /**
  874. * 模糊匹配 JSON value
  875. */
  876. document.querySelectorAll("#formatContainer *[class*='json-']:not([class*='json-key']):not([class*='json-brackets'])")
  877. .forEach(el =>{
  878. let target = $(el),
  879. chain = style == 'default' ? target.parent().attr('json-path') : target.siblings('.json-key').attr('json-path')
  880. if(!chain){
  881. return
  882. }
  883. let text = target.text()
  884. if(!text.toLowerCase().includes(filter.toLowerCase())){
  885. return
  886. }
  887. chainSet.add(chain)
  888. while(chain = chain.substr(0, chain.lastIndexOf('.'))){
  889. chainSet.add(chain)
  890. }
  891. })
  892. console.log(chainSet)
  893. style == 'default' ? $('#formatContainer li').addClass('hidden') : $('.json-key').parent().addClass('hidden')
  894. chainSet.forEach(chain => {
  895. style == 'default' ? $(`#formatContainer *[json-path="${chain}"]`).removeClass('hidden')
  896. : $(`#formatContainer *[json-path="${chain}"]`).parent().removeClass('hidden')
  897. })
  898. },
  899. // JSON 过滤
  900. input: function(){
  901. let that = this
  902. $('input').on('input', function(){
  903. let val = $(this).val()
  904. val === '' ? $('.clear').attr('hidden', true) : $('.clear').attr('hidden', false)
  905. that.filterJSON(val)
  906. })
  907. return that
  908. },
  909. // 清空输入框内容
  910. clear: function(){
  911. let that = this
  912. $('.clear').click(function(){
  913. that.filterJSON()
  914. $('input').val('')
  915. $(this).attr('hidden', true)
  916. })
  917. return this
  918. },
  919. // 返回顶部
  920. scrollTop: function(){
  921. $('.scroll-top').click(function(){
  922. $('.tabs-container').animate({
  923. scrollTop: '0'
  924. }, 1000);
  925. })
  926. $('.tabs-container').scroll(function(e) {
  927. let scrollTop = $(this).scrollTop()
  928. scrollTop > 500 ? $('.scroll-top').fadeIn() : $('.scroll-top').fadeOut()
  929. });
  930. return this
  931. },
  932. // 所有a标签,看是否是图片,是图片生成预览图
  933. urlHover:function(){
  934. $("#formatContainer").on({
  935. mouseenter: function(){
  936. var that = $(this),
  937. href = that.attr('href')
  938. if(Utils.isImg(href)){
  939. layer.tips(`<img src="${href}" />`, that, {
  940. time: 0,
  941. anim: 5,
  942. maxWidth: 500,
  943. tips: [3, '#d9d9d9']
  944. });
  945. }
  946. },
  947. mouseleave: () => layer.closeAll()
  948. }, 'a[href]')
  949. return this
  950. },
  951. // 提示key的JSONPath
  952. tipsJsonPath: function(){
  953. var that = this
  954. $("#formatContainer").on({
  955. mouseenter: function(){
  956. let jsonPath = that.getJsonPath(this)
  957. layer.tips(jsonPath, this, {
  958. time: 0,
  959. anim: 5,
  960. maxWidth: 500,
  961. tips: [1, '#2e59a7']
  962. })
  963. },
  964. mouseleave: () => layer.closeAll()
  965. }, '.json-key')
  966. return this
  967. },
  968. // 单击key复制JSONPath
  969. copyJsonPath: function(){
  970. var that = this
  971. $("#formatContainer").on('click', '.json-key', function(event){
  972. if(event.ctrlKey){
  973. let jsonPath = that.getJsonPath(this)
  974. GM_setClipboard(jsonPath)
  975. layer.msg('复制成功', {time: 1500})
  976. }
  977. })
  978. return this
  979. },
  980. getJsonPath: function(element){
  981. let style = GM_getValue('formatStyle') || 'default'
  982. let jsonPath = style == 'default' ? $(element).parent().attr('json-path') : $(element).attr('json-path')
  983. return jsonPath.split('.').reduce((prev, next) => /^\d+$/.test(next) ? prev + `[${next}]` : prev + '.' + next)
  984. },
  985. init:function(){
  986. this.input().clear().scrollTop().urlHover().tipsJsonPath().copyJsonPath()
  987. }
  988. },
  989. theme = {
  990. // 切换主题
  991. changeTheme: function(){
  992. let that = this
  993. $('.theme select').change(function(e){
  994. let val = $(e.target).val()
  995. GM_setValue('theme', val)
  996. that.setTheme()
  997. })
  998. return this
  999. },
  1000. // 设置主题
  1001. setTheme: function(){
  1002. let theme = GM_getValue('theme') || 'default'
  1003. $('body').removeClass().addClass(theme)
  1004. $('.theme select').val(theme)
  1005. return this
  1006. },
  1007. init:function(){
  1008. this.setTheme().changeTheme()
  1009. }
  1010. },
  1011. formatStyle = {
  1012. // 切换风格
  1013. changeStyle: function(json){
  1014. let that = this
  1015. $('.formatStyle select').change(function(e){
  1016. let val = $(e.target).val()
  1017. GM_setValue('formatStyle', val)
  1018. that.setStyle(json)
  1019. })
  1020. return this
  1021. },
  1022. // 设置风格
  1023. setStyle: function(json){
  1024. let style = GM_getValue('formatStyle') || 'default'
  1025. $('.formatStyle select').val(style)
  1026.  
  1027. $('input').val('')
  1028. $('#formatContainer').html('')
  1029. if(style === 'default'){
  1030. $('#formatContainer').jsonViewer(json, jsonpFun)
  1031. }else{
  1032. let trHtml= treeTableHtml(json)
  1033. $('#formatContainer').append(`<table id="treeTable">${trHtml}</table>`)
  1034. $('#treeTable').simpleTreeTable({
  1035. expander: '#expandAll',
  1036. collapser: '#collapseAll',
  1037. })
  1038.  
  1039. // Highlight selected row
  1040. $('#treeTable').on("mousedown", "tr", function() {
  1041. $(".selected").not(this).removeClass("selected");
  1042. $(this).toggleClass("selected")
  1043. })
  1044. }
  1045. return this
  1046. },
  1047. init:function(json){
  1048. this.setStyle(json).changeStyle(json)
  1049. theme.init()
  1050. btnEvent.init()
  1051. otherOperate.init()
  1052. jsonMind.init(json)
  1053. }
  1054. }
  1055.  
  1056. formatStyle.init(jsonObject)
  1057.  
  1058. /**
  1059. * 表格
  1060. */
  1061. function treeTableHtml(json, level = 0, pId = '', pChain = ''){
  1062. let tr = ''
  1063. for(let key in json){
  1064. let val = json[key],
  1065. type = Utils.getType(val),
  1066. tId = key + '_' + Math.random(),
  1067. chain = pChain + "." + key
  1068. if(['array', 'object'].includes(type)){
  1069. let brackets = '',
  1070. len = val.length,
  1071. res = treeTableHtml(val, level + 1, tId, chain)
  1072.  
  1073. if(!res){
  1074. if(type ==='array'){
  1075. brackets = `<span class="json-brackets ${type == 'array' ? 'json-square-brackets' : 'json-curly-brackets'}">[]</span>`
  1076. }else{
  1077. brackets = `<span class="json-brackets ${type == 'array' ? 'json-square-brackets' : 'json-curly-brackets'}">{}</span>`
  1078. }
  1079. }
  1080.  
  1081. tr += `
  1082. <tr data-node-id="${tId}" data-node-pid="${pId}" type="${type}">
  1083. <td colspan="${len > 0 ? 2 : 0}" class="json-key" json-path="${chain}" style="padding-left: ${ level * 19 }px">${key}:</td>
  1084. <td>${brackets}</td>
  1085. </tr>`
  1086. tr += res
  1087. }else{
  1088. val = (type === 'string') ? val.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') : val
  1089. tr += `<tr data-node-id="${tId}" data-node-pid="${pId}" type="${type}">
  1090. <td class="json-key" json-path="${chain}" style="padding-left: ${ level * 19 }px">${key}:</td>`
  1091. if (Utils.isUrl(val)){
  1092. tr += `<td class="json-${type}"><a target="_blank" href="${val}">"${val}"</a></td>`
  1093. }else{
  1094. val = (type === 'string') ? `"${val}"`: val
  1095. tr += `<td class="json-${type}">${val}</td>`
  1096. }
  1097. tr += '</tr>'
  1098. }
  1099. }
  1100. return tr;
  1101. }
  1102.  
  1103. })(_jQuery)
  1104. })();