format-lib.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883
  1. /**
  2. * 日期格式化
  3. * @param {Object} pattern
  4. */
  5. Date.prototype.format = function (pattern) {
  6. let pad = function (source, length) {
  7. let pre = "",
  8. negative = (source < 0),
  9. string = String(Math.abs(source));
  10. if (string.length < length) {
  11. pre = (new Array(length - string.length + 1)).join('0');
  12. }
  13. return (negative ? "-" : "") + pre + string;
  14. };
  15. if ('string' !== typeof pattern) {
  16. return this.toString();
  17. }
  18. let replacer = function (patternPart, result) {
  19. pattern = pattern.replace(patternPart, result);
  20. };
  21. let year = this.getFullYear(),
  22. month = this.getMonth() + 1,
  23. date2 = this.getDate(),
  24. hours = this.getHours(),
  25. minutes = this.getMinutes(),
  26. seconds = this.getSeconds(),
  27. milliSec = this.getMilliseconds();
  28. replacer(/yyyy/g, pad(year, 4));
  29. replacer(/yy/g, pad(parseInt(year.toString().slice(2), 10), 2));
  30. replacer(/MM/g, pad(month, 2));
  31. replacer(/M/g, month);
  32. replacer(/dd/g, pad(date2, 2));
  33. replacer(/d/g, date2);
  34. replacer(/HH/g, pad(hours, 2));
  35. replacer(/H/g, hours);
  36. replacer(/hh/g, pad(hours % 12, 2));
  37. replacer(/h/g, hours % 12);
  38. replacer(/mm/g, pad(minutes, 2));
  39. replacer(/m/g, minutes);
  40. replacer(/ss/g, pad(seconds, 2));
  41. replacer(/s/g, seconds);
  42. replacer(/SSS/g, pad(milliSec, 3));
  43. replacer(/S/g, milliSec);
  44. return pattern;
  45. };
  46. /**
  47. * 自动消失的Alert弹窗
  48. * @param content
  49. */
  50. window.toast = function (content) {
  51. window.clearTimeout(window.feHelperAlertMsgTid);
  52. let elAlertMsg = document.querySelector("#fehelper_alertmsg");
  53. if (!elAlertMsg) {
  54. let elWrapper = document.createElement('div');
  55. elWrapper.innerHTML = '<div id="fehelper_alertmsg" style="position:fixed;bottom:25px;left:5px;z-index:1000000">' +
  56. '<p style="background:#000;display:inline-block;color:#fff;text-align:center;' +
  57. 'padding:10px 10px;margin:0 auto;font-size:14px;border-radius:4px;">' + content + '</p></div>';
  58. elAlertMsg = elWrapper.childNodes[0];
  59. document.body.appendChild(elAlertMsg);
  60. } else {
  61. elAlertMsg.querySelector('p').innerHTML = content;
  62. elAlertMsg.style.display = 'block';
  63. }
  64. window.feHelperAlertMsgTid = window.setTimeout(function () {
  65. elAlertMsg.style.display = 'none';
  66. }, 1000);
  67. };
  68. /**
  69. * FeHelper Json Format Lib,入口文件
  70. * @example
  71. * Formatter.format(jsonString)
  72. */
  73. window.Formatter = (function () {
  74. "use strict";
  75. let jfContent,
  76. jfPre,
  77. jfStyleEl,
  78. jfStatusBar,
  79. formattingMsg;
  80. let lastItemIdGiven = 0;
  81. let cachedJsonString = '';
  82. let _initElements = function () {
  83. jfContent = $('#jfContent');
  84. if (!jfContent[0]) {
  85. jfContent = $('<div id="jfContent" />').appendTo('body');
  86. }
  87. jfPre = $('#jfContent_pre');
  88. if (!jfPre[0]) {
  89. jfPre = $('<pre id="jfContent_pre" />').appendTo('body');
  90. }
  91. jfStyleEl = $('#jfStyleEl');
  92. if (!jfStyleEl[0]) {
  93. jfStyleEl = $('<style id="jfStyleEl" />').appendTo('head');
  94. }
  95. formattingMsg = $('#formattingMsg');
  96. if (!formattingMsg[0]) {
  97. formattingMsg = $('<div id="formattingMsg"><span class="x-loading"></span>格式化中...</div>').appendTo('body');
  98. }
  99. try {
  100. jfContent.html('').show();
  101. jfPre.html('').hide();
  102. jfStatusBar && jfStatusBar.hide();
  103. formattingMsg.hide();
  104. } catch (e) {
  105. }
  106. };
  107. /**
  108. * HTML特殊字符格式化
  109. * @param str
  110. * @returns {*}
  111. */
  112. let htmlspecialchars = function (str) {
  113. str = str.replace(/&/g, '&amp;');
  114. str = str.replace(/</g, '&lt;');
  115. str = str.replace(/>/g, '&gt;');
  116. str = str.replace(/"/g, '&quot;');
  117. str = str.replace(/'/g, '&#039;');
  118. return str;
  119. };
  120. /**
  121. * 直接下载,能解决中文乱码
  122. * @param content
  123. * @private
  124. */
  125. let _downloadSupport = function (content) {
  126. // 下载链接
  127. let dt = (new Date()).format('yyyyMMddHHmmss');
  128. let blob = new Blob([content], {type: 'application/octet-stream'});
  129. let button = $('<button class="xjf-btn xjf-btn-right">下载JSON</button>').appendTo('#optionBar');
  130. if (typeof chrome === 'undefined' || !chrome.permissions) {
  131. button.click(function (e) {
  132. let aLink = $('#aLinkDownload');
  133. if (!aLink[0]) {
  134. aLink = $('<a id="aLinkDownload" target="_blank" title="保存到本地">下载JSON数据</a>').appendTo('body');
  135. aLink.attr('download', 'FeHelper-' + dt + '.json');
  136. aLink.attr('href', URL.createObjectURL(blob));
  137. }
  138. aLink[0].click();
  139. });
  140. } else {
  141. button.click(function (e) {
  142. // 请求权限
  143. chrome.permissions.request({
  144. permissions: ['downloads']
  145. }, (granted) => {
  146. if (granted) {
  147. chrome.downloads.download({
  148. url: URL.createObjectURL(blob),
  149. saveAs: true,
  150. conflictAction: 'overwrite',
  151. filename: 'FeHelper-' + dt + '.json'
  152. });
  153. } else {
  154. toast('必须接受授权,才能正常下载!');
  155. }
  156. });
  157. });
  158. }
  159. };
  160. /**
  161. * chrome 下复制到剪贴板
  162. * @param text
  163. */
  164. let _copyToClipboard = function (text) {
  165. let input = document.createElement('textarea');
  166. input.style.position = 'fixed';
  167. input.style.opacity = 0;
  168. input.value = text;
  169. document.body.appendChild(input);
  170. input.select();
  171. document.execCommand('Copy');
  172. document.body.removeChild(input);
  173. toast('Json片段复制成功,随处粘贴可用!')
  174. };
  175. /**
  176. * 从el中获取json文本
  177. * @param el
  178. * @returns {string}
  179. */
  180. let getJsonText = function (el) {
  181. let txt = el.text().replace(/复制\|下载\|删除/gm,'').replace(/":\s/gm, '":').replace(/,$/, '').trim();
  182. if (!(/^{/.test(txt) && /\}$/.test(txt)) && !(/^\[/.test(txt) && /\]$/.test(txt))) {
  183. txt = '{' + txt + '}';
  184. }
  185. try {
  186. txt = JSON.stringify(JSON.parse(txt), null, 4);
  187. } catch (err) {
  188. }
  189. return txt;
  190. };
  191. // 添加json路径
  192. let _showJsonPath = function (curEl) {
  193. let keys = [];
  194. do {
  195. if (curEl.hasClass('item-block')) {
  196. if (!curEl.hasClass('rootItem')) {
  197. keys.unshift('[' + curEl.prevAll('.item').length + ']');
  198. } else {
  199. break;
  200. }
  201. } else {
  202. keys.unshift(curEl.find('>.key').text());
  203. }
  204. if (curEl.parent().hasClass('rootItem') || curEl.parent().parent().hasClass('rootItem')) {
  205. break;
  206. }
  207. curEl = curEl.parent().parent();
  208. } while (curEl.length && !curEl.hasClass('rootItem'));
  209. let path = keys.join('#@#').replace(/#@#\[/g, '[').replace(/#@#/g, '.');
  210. let jfPath = $('#jsonPath');
  211. if (!jfPath.length) {
  212. jfPath = $('<span id="jsonPath"/>').prependTo(jfStatusBar);
  213. }
  214. jfPath.html('当前节点:JSON.' + path);
  215. };
  216. // 给某个节点增加操作项
  217. let _addOptForItem = function (el, show) {
  218. // 下载json片段
  219. let fnDownload = function (event) {
  220. event.stopPropagation();
  221. let txt = getJsonText(el);
  222. // 下载片段
  223. let dt = (new Date()).format('yyyyMMddHHmmss');
  224. let blob = new Blob([txt], {type: 'application/octet-stream'});
  225. if (typeof chrome === 'undefined' || !chrome.permissions) {
  226. // 下载JSON的简单形式
  227. $(this).attr('download', 'FeHelper-' + dt + '.json').attr('href', URL.createObjectURL(blob));
  228. } else {
  229. // 请求权限
  230. chrome.permissions.request({
  231. permissions: ['downloads']
  232. }, (granted) => {
  233. if (granted) {
  234. chrome.downloads.download({
  235. url: URL.createObjectURL(blob),
  236. saveAs: true,
  237. conflictAction: 'overwrite',
  238. filename: 'FeHelper-' + dt + '.json'
  239. });
  240. } else {
  241. toast('必须接受授权,才能正常下载!');
  242. }
  243. });
  244. }
  245. };
  246. // 复制json片段
  247. let fnCopy = function (event) {
  248. event.stopPropagation();
  249. _copyToClipboard(getJsonText(el));
  250. };
  251. // 删除json片段
  252. let fnDel = function (event) {
  253. event.stopPropagation();
  254. if (el.parent().is('#formattedJson')) {
  255. toast('如果连最外层的Json也删掉的话,就没啥意义了哦!');
  256. return false;
  257. }
  258. toast('节点已删除成功!');
  259. el.remove();
  260. jfStatusBar && jfStatusBar.hide();
  261. };
  262. $('.boxOpt').hide();
  263. if (show) {
  264. let jfOptEl = el.children('.boxOpt');
  265. if (!jfOptEl.length) {
  266. jfOptEl = $('<b class="boxOpt">' +
  267. '<a class="opt-copy" title="复制当前选中节点的JSON数据">复制</a>|' +
  268. '<a class="opt-download" target="_blank" title="下载当前选中节点的JSON数据">下载</a>|' +
  269. '<a class="opt-del" title="删除当前选中节点的JSON数据">删除</a></b>').appendTo(el);
  270. } else {
  271. jfOptEl.show();
  272. }
  273. jfOptEl.find('a.opt-download').unbind('click').bind('click', fnDownload);
  274. jfOptEl.find('a.opt-copy').unbind('click').bind('click', fnCopy);
  275. jfOptEl.find('a.opt-del').unbind('click').bind('click', fnDel);
  276. }
  277. };
  278. // 显示当前节点的Key
  279. let _toogleStatusBar = function (curEl, show) {
  280. if (!jfStatusBar) {
  281. jfStatusBar = $('<div id="statusBar"/>').appendTo('body');
  282. }
  283. if (!show) {
  284. jfStatusBar.hide();
  285. return;
  286. } else {
  287. jfStatusBar.show();
  288. }
  289. _showJsonPath(curEl);
  290. };
  291. /**
  292. * 折叠所有
  293. * @param elements
  294. */
  295. function collapse(elements) {
  296. let el;
  297. $.each(elements, function (i) {
  298. el = $(this);
  299. if (el.children('.kv-list').length) {
  300. el.addClass('collapsed');
  301. if (!el.attr('id')) {
  302. el.attr('id', 'item' + (++lastItemIdGiven));
  303. let count = el.children('.kv-list').eq(0).children().length;
  304. // Generate comment text eg "4 items"
  305. let comment = count + (count === 1 ? ' item' : ' items');
  306. // Add CSS that targets it
  307. jfStyleEl[0].insertAdjacentHTML(
  308. 'beforeend',
  309. '\n#item' + lastItemIdGiven + '.collapsed:after{color: #aaa; content:" // ' + comment + '"}'
  310. );
  311. }
  312. }
  313. });
  314. }
  315. /**
  316. * 创建几个全局操作的按钮,置于页面右上角即可
  317. * @private
  318. */
  319. let _buildOptionBar = function () {
  320. let optionBar = $('#optionBar');
  321. if (optionBar.length) {
  322. optionBar.html('');
  323. } else {
  324. optionBar = $('<span id="optionBar" />').appendTo(jfContent.parent());
  325. }
  326. $('<span class="x-split">|</span>').appendTo(optionBar);
  327. let buttonFormatted = $('<button class="xjf-btn xjf-btn-left">元数据</button>').appendTo(optionBar);
  328. let buttonCollapseAll = $('<button class="xjf-btn xjf-btn-mid">折叠所有</button>').appendTo(optionBar);
  329. let plainOn = false;
  330. buttonFormatted.bind('click', function (e) {
  331. if (plainOn) {
  332. plainOn = false;
  333. jfPre.hide();
  334. jfContent.show();
  335. buttonFormatted.text('元数据');
  336. } else {
  337. plainOn = true;
  338. jfPre.show();
  339. jfContent.hide();
  340. buttonFormatted.text('格式化');
  341. }
  342. jfStatusBar && jfStatusBar.hide();
  343. });
  344. buttonCollapseAll.bind('click', function (e) {
  345. // 如果内容还没有格式化过,需要再格式化一下
  346. if (plainOn) {
  347. buttonFormatted.trigger('click');
  348. }
  349. if (buttonCollapseAll.text() === '折叠所有') {
  350. buttonCollapseAll.text('展开所有');
  351. collapse($('.item-object,.item-block'));
  352. } else {
  353. buttonCollapseAll.text('折叠所有');
  354. $('.item-object,.item-block').removeClass('collapsed');
  355. }
  356. jfStatusBar && jfStatusBar.hide();
  357. });
  358. };
  359. // 附加操作
  360. let _addEvents = function () {
  361. // 折叠、展开
  362. $('#jfContent span.expand').bind('click', function (ev) {
  363. ev.preventDefault();
  364. ev.stopPropagation();
  365. let parentEl = $(this).parent();
  366. parentEl.toggleClass('collapsed');
  367. if (parentEl.hasClass('collapsed')) {
  368. collapse(parentEl);
  369. }
  370. });
  371. // 点击选中:高亮
  372. $('#jfContent .item').bind('click', function (e) {
  373. let el = $(this);
  374. if (el.hasClass('x-selected')) {
  375. _toogleStatusBar(el, false);
  376. _addOptForItem(el, false);
  377. el.removeClass('x-selected');
  378. e.stopPropagation();
  379. return true;
  380. }
  381. $('.x-selected').removeClass('x-selected');
  382. el.addClass('x-selected');
  383. // 显示底部状态栏
  384. _toogleStatusBar(el, true);
  385. _addOptForItem(el, true);
  386. if (!$(e.target).is('.item .expand')) {
  387. e.stopPropagation();
  388. } else {
  389. $(e.target).parent().trigger('click');
  390. }
  391. // 触发钩子
  392. if (typeof window._OnJsonItemClickByFH === 'function') {
  393. window._OnJsonItemClickByFH(getJsonText(el));
  394. }
  395. });
  396. };
  397. /**
  398. * 执行代码格式化
  399. */
  400. let format = function (jsonStr, skin) {
  401. cachedJsonString = JSON.stringify(JSON.parse(jsonStr), null, 4);
  402. _initElements();
  403. jfPre.html(htmlspecialchars(cachedJsonString));
  404. // 用webwork的方式来进行格式化,效率更高
  405. let worker = new Worker(URL.createObjectURL(new Blob(["(" + JsonFormatWebWorker.toString() + ")()"], {type: 'text/javascript'})));
  406. worker.onmessage = function (evt) {
  407. let msg = evt.data;
  408. switch (msg[0]) {
  409. case 'FORMATTING' :
  410. formattingMsg.show();
  411. break;
  412. case 'FORMATTED' :
  413. formattingMsg.hide();
  414. jfContent.html(msg[1]);
  415. _buildOptionBar();
  416. // 事件绑定
  417. _addEvents();
  418. // 支持文件下载
  419. _downloadSupport(cachedJsonString);
  420. break;
  421. }
  422. };
  423. worker.postMessage({
  424. jsonString: jsonStr,
  425. skin: skin
  426. });
  427. };
  428. // 同步的方式格式化
  429. let formatSync = function (jsonStr, skin) {
  430. cachedJsonString = JSON.stringify(JSON.parse(jsonStr), null, 4);
  431. _initElements();
  432. jfPre.html(htmlspecialchars(cachedJsonString));
  433. let worker = new JsonFormatWebWorker(true);
  434. worker.getFormattedHtml({
  435. data: {
  436. jsonString: jsonStr,
  437. skin: skin
  438. },
  439. onFormatting: function (msg) {
  440. formattingMsg.show();
  441. },
  442. onFormatted: function (msg) {
  443. formattingMsg.hide();
  444. jfContent.html(msg[1]);
  445. _buildOptionBar();
  446. // 事件绑定
  447. _addEvents();
  448. // 支持文件下载
  449. _downloadSupport(cachedJsonString);
  450. }
  451. });
  452. };
  453. return {
  454. format: format,
  455. formatSync: formatSync
  456. }
  457. })();
  458. /*============================================== web worker =========================================================*/
  459. /**
  460. * 用webworker的形式来进行json格式化,在应对大json的时候,效果会非常明显
  461. * @constructor
  462. */
  463. var JsonFormatWebWorker = function (isUnSupportWorker = false) {
  464. // 引入big-json.js解决大数字的问题
  465. let __importScript = (filename) => {
  466. this.compress && fetch(filename).then(resp => resp.text()).then(jsText => eval(jsText));
  467. };
  468. __importScript('json-bigint.js');
  469. // Constants
  470. let
  471. TYPE_STRING = 1,
  472. TYPE_NUMBER = 2,
  473. TYPE_OBJECT = 3,
  474. TYPE_ARRAY = 4,
  475. TYPE_BOOL = 5,
  476. TYPE_NULL = 6;
  477. /**
  478. * HTML特殊字符格式化
  479. * @param str
  480. * @returns {*}
  481. */
  482. let htmlspecialchars = function (str) {
  483. str = str.replace(/&/g, '&amp;');
  484. str = str.replace(/</g, '&lt;');
  485. str = str.replace(/>/g, '&gt;');
  486. str = str.replace(/"/g, '&quot;');
  487. str = str.replace(/'/g, '&#039;');
  488. return str;
  489. };
  490. /**
  491. * FH 虚拟DOM
  492. * @constructor
  493. */
  494. let FhVDom = function () {
  495. this._id = 'fhvd_' + (new Date * 1);
  496. this.tag = '';
  497. this.innerText = '';
  498. this.textContent = '';
  499. this.childNodes = [];
  500. this.className = '';
  501. this.attributes = [];
  502. this.classList = [];
  503. this.classList.__proto__.add = this.classList.__proto__.push;
  504. this.createElement = tag => {
  505. this.tag = tag;
  506. return this;
  507. };
  508. this.setAttribute = (attr, value) => {
  509. this.attributes.push([attr, value]);
  510. };
  511. this.appendChild = child => {
  512. this.childNodes.push(child);
  513. return this;
  514. };
  515. this.getOuterHTML = () => {
  516. let outerHtml = [];
  517. if (this.tag) {
  518. outerHtml.push(`<${this.tag}`);
  519. let clsName = (this.className || '') + ' ' + this.classList.join(' ');
  520. clsName.replace(/\s/g, '').length && outerHtml.push(` class="${clsName}"`);
  521. this.attributes.length && outerHtml.push(this.attributes.map(attr => ` ${attr[0]}="${attr[1]}"`).join(''));
  522. outerHtml.push(`>`);
  523. if (('' + this.innerText).length) {
  524. outerHtml.push(this.innerText);
  525. } else if (('' + this.textContent).length) {
  526. outerHtml.push(this.textContent);
  527. } else {
  528. outerHtml.push(this.childNodes.map(node => node.getOuterHTML()).join(''))
  529. }
  530. outerHtml.push(`</${this.tag}>`);
  531. } else {
  532. if (('' + this.innerText).length) {
  533. outerHtml.push(this.innerText);
  534. } else if (('' + this.textContent).length) {
  535. outerHtml.push(this.textContent);
  536. }
  537. }
  538. return outerHtml.join('');
  539. };
  540. this.cloneNode = (deep) => {
  541. let newDom = FhVDom.getInstance();
  542. newDom.tag = this.tag;
  543. if (deep || !this.tag) {
  544. newDom.innerText = this.innerText;
  545. newDom.textContent = this.textContent;
  546. } else {
  547. newDom.innerText = '';
  548. newDom.textContent = '';
  549. }
  550. newDom.className = this.className;
  551. newDom.classList = Array.from(this.classList);
  552. newDom.attributes = Array.from(this.attributes);
  553. return newDom;
  554. };
  555. };
  556. // 构造器
  557. FhVDom.getInstance = () => new FhVDom();
  558. function createSpanNode(innerText, className) {
  559. let span = FhVDom.getInstance().createElement('span');
  560. span.className = className || '';
  561. span.innerText = innerText || '';
  562. return span;
  563. }
  564. function createDivNode(className) {
  565. let div = FhVDom.getInstance().createElement('div');
  566. div.className = className || '';
  567. return div;
  568. }
  569. // Create template nodes
  570. let templatesObj = {
  571. t_item: createDivNode('item'),
  572. t_key: createSpanNode('', 'key'),
  573. t_string: createSpanNode('', 'string'),
  574. t_number: createSpanNode('', 'number'),
  575. t_exp: createSpanNode('', 'expand'),
  576. t_null: createSpanNode('null', 'null'),
  577. t_true: createSpanNode('true', 'bool'),
  578. t_false: createSpanNode('false', 'bool'),
  579. t_oBrace: createSpanNode('{', 'brace'),
  580. t_cBrace: createSpanNode('}', 'brace'),
  581. t_oBracket: createSpanNode('[', 'brace'),
  582. t_cBracket: createSpanNode(']', 'brace'),
  583. t_ellipsis: createSpanNode('', 'ellipsis'),
  584. t_kvList: createDivNode('kv-list'),
  585. t_colonAndSpace: createSpanNode(':\u00A0', 'colon'),
  586. t_commaText: createSpanNode(',', 'comma'),
  587. t_dblqText: createSpanNode('"', 'quote')
  588. };
  589. // Core recursive DOM-building function
  590. function getItemDOM(value, keyName) {
  591. let type,
  592. item,
  593. nonZeroSize,
  594. templates = templatesObj,
  595. objKey,
  596. keySpan,
  597. valueElement;
  598. // Establish value type
  599. if (typeof value === 'string')
  600. type = TYPE_STRING;
  601. else if (typeof value === 'number')
  602. type = TYPE_NUMBER;
  603. else if (value === false || value === true)
  604. type = TYPE_BOOL;
  605. else if (value === null)
  606. type = TYPE_NULL;
  607. else if (value instanceof Array)
  608. type = TYPE_ARRAY;
  609. else
  610. type = TYPE_OBJECT;
  611. item = templates.t_item.cloneNode(false);
  612. // Add an 'expander' first (if this is object/array with non-zero size)
  613. if (type === TYPE_OBJECT || type === TYPE_ARRAY) {
  614. if (typeof JSON.BigNumber === 'function' && value instanceof JSON.BigNumber) {
  615. value = JSON.stringify(value);
  616. type = TYPE_NUMBER;
  617. } else {
  618. nonZeroSize = false;
  619. for (objKey in value) {
  620. if (value.hasOwnProperty(objKey)) {
  621. nonZeroSize = true;
  622. break; // no need to keep counting; only need one
  623. }
  624. }
  625. if (nonZeroSize)
  626. item.appendChild(templates.t_exp.cloneNode(true));
  627. }
  628. }
  629. // If there's a key, add that before the value
  630. if (keyName !== false) { // NB: "" is a legal keyname in JSON
  631. item.classList.add(type === TYPE_OBJECT ? 'item-object' : type === TYPE_ARRAY ? 'item-array' : 'item-line');
  632. keySpan = templates.t_key.cloneNode(false);
  633. keySpan.textContent = JSON.stringify(keyName).slice(1, -1); // remove quotes
  634. item.appendChild(templates.t_dblqText.cloneNode(true));
  635. item.appendChild(keySpan);
  636. item.appendChild(templates.t_dblqText.cloneNode(true));
  637. item.appendChild(templates.t_colonAndSpace.cloneNode(true));
  638. }
  639. else {
  640. item.classList.add('item-block');
  641. }
  642. let kvList, childItem;
  643. switch (type) {
  644. case TYPE_STRING:
  645. let innerStringEl = FhVDom.getInstance().createElement('span'),
  646. escapedString = JSON.stringify(value);
  647. escapedString = escapedString.substring(1, escapedString.length - 1); // remove quotes
  648. let isLink = false;
  649. if (/^[\w]+:\/\//.test(value)) {
  650. try {
  651. let url = new URL(value);
  652. let innerStringA = FhVDom.getInstance().createElement('A');
  653. innerStringA.setAttribute('href', url.href);
  654. innerStringA.setAttribute('target', '_blank');
  655. innerStringA.innerText = htmlspecialchars(escapedString);
  656. innerStringEl.appendChild(innerStringA);
  657. isLink = true;
  658. } catch (e) {
  659. }
  660. }
  661. if (!isLink) {
  662. innerStringEl.innerText = htmlspecialchars(escapedString);
  663. }
  664. valueElement = templates.t_string.cloneNode(false);
  665. valueElement.appendChild(templates.t_dblqText.cloneNode(true));
  666. valueElement.appendChild(innerStringEl);
  667. valueElement.appendChild(templates.t_dblqText.cloneNode(true));
  668. item.appendChild(valueElement);
  669. break;
  670. case TYPE_NUMBER:
  671. valueElement = templates.t_number.cloneNode(false);
  672. valueElement.innerText = value;
  673. item.appendChild(valueElement);
  674. break;
  675. case TYPE_OBJECT:
  676. // Add opening brace
  677. item.appendChild(templates.t_oBrace.cloneNode(true));
  678. if (nonZeroSize) {
  679. item.appendChild(templates.t_ellipsis.cloneNode(false));
  680. kvList = templates.t_kvList.cloneNode(false);
  681. let keys = Object.keys(value).filter(k => value.hasOwnProperty(k));
  682. keys.forEach((k, index) => {
  683. childItem = getItemDOM(value[k], k);
  684. if (index < keys.length - 1) {
  685. childItem.appendChild(templates.t_commaText.cloneNode(true));
  686. }
  687. kvList.appendChild(childItem);
  688. });
  689. item.appendChild(kvList);
  690. }
  691. // Add closing brace
  692. item.appendChild(templates.t_cBrace.cloneNode(true));
  693. break;
  694. case TYPE_ARRAY:
  695. item.appendChild(templates.t_oBracket.cloneNode(true));
  696. if (nonZeroSize) {
  697. item.appendChild(templates.t_ellipsis.cloneNode(false));
  698. kvList = templates.t_kvList.cloneNode(false);
  699. for (let i = 0, length = value.length, lastIndex = length - 1; i < length; i++) {
  700. childItem = getItemDOM(value[i], false);
  701. if (i < lastIndex)
  702. childItem.appendChild(templates.t_commaText.cloneNode(true));
  703. kvList.appendChild(childItem);
  704. }
  705. item.appendChild(kvList);
  706. }
  707. // Add closing bracket
  708. item.appendChild(templates.t_cBracket.cloneNode(true));
  709. break;
  710. case TYPE_BOOL:
  711. if (value)
  712. item.appendChild(templates.t_true.cloneNode(true));
  713. else
  714. item.appendChild(templates.t_false.cloneNode(true));
  715. break;
  716. case TYPE_NULL:
  717. item.appendChild(templates.t_null.cloneNode(true));
  718. break;
  719. }
  720. return item;
  721. }
  722. // Listen for requests from content pages wanting to set up a port
  723. // isUnSupportWorker 为true时,表示不支持webworker,不需要监听消息
  724. if (!isUnSupportWorker) {
  725. self.onmessage = function (event) {
  726. // 插件在乎的是json字符串,所以只有json字符串时才进行格式化
  727. if (event.data.jsonString) {
  728. self.postMessage(['FORMATTING']);
  729. let rootItem;
  730. if (event.data.skin && event.data.skin === 'theme-simple') {
  731. rootItem = createDivNode('rootItem');
  732. rootItem.textContent = JSON.stringify(JSON.parse(event.data.jsonString), null, 4);
  733. } else {
  734. rootItem = getItemDOM(JSON.parse(event.data.jsonString), false);
  735. rootItem.classList.add('rootItem');
  736. }
  737. let formattedHtml = `<div id="formattedJson">${rootItem.getOuterHTML()}</div>`;
  738. self.postMessage(['FORMATTED', formattedHtml]);
  739. }
  740. };
  741. }
  742. // 针对不支持webworker的情况,允许直接调用
  743. this.getFormattedHtml = function (options) {
  744. options.onFormatting && options.onFormatting(['FORMATTING']);
  745. let rootItem;
  746. if (options.data.skin && options.data.skin === 'theme-simple') {
  747. rootItem = createDivNode('rootItem');
  748. rootItem.textContent = JSON.stringify(JSON.parse(options.data.jsonString), null, 4);
  749. } else {
  750. rootItem = getItemDOM(JSON.parse(options.data.jsonString), false);
  751. rootItem.classList.add('rootItem');
  752. }
  753. let formattedHtml = `<div id="formattedJson">${rootItem.getOuterHTML()}</div>`;
  754. options.onFormatted && options.onFormatted(['FORMATTED', formattedHtml]);
  755. };
  756. };