format-lib.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968
  1. /**
  2. * FeHelper Json Format Lib
  3. */
  4. let JsonFormatEntrance = (function () {
  5. "use strict";
  6. let jfContent,
  7. jfPre,
  8. jfStyleEl,
  9. jfOptEl,
  10. jfPathEl,
  11. formattingMsg;
  12. let lastKvovIdGiven = 0;
  13. let cachedJsonString = '';
  14. let _initElements = function () {
  15. jfContent = $('#jfContent');
  16. if (!jfContent[0]) {
  17. jfContent = $('<div id="jfContent" />').appendTo('body');
  18. }
  19. jfPre = $('#jfContent_pre');
  20. if (!jfPre[0]) {
  21. jfPre = $('<pre id="jfContent_pre" />').appendTo('body');
  22. }
  23. jfStyleEl = $('#jfStyleEl');
  24. if (!jfStyleEl[0]) {
  25. jfStyleEl = $('<style id="jfStyleEl" />').appendTo('head');
  26. }
  27. formattingMsg = $('#formattingMsg');
  28. if (!formattingMsg[0]) {
  29. formattingMsg = $('<div id="formattingMsg"><span class="x-loading"></span>格式化中...</div>').appendTo('body');
  30. }
  31. try {
  32. jfContent.html('').show();
  33. jfPre.html('').hide();
  34. jfPathEl && jfPathEl.hide();
  35. formattingMsg.hide();
  36. } catch (e) {
  37. }
  38. };
  39. // Add listener to receive response from BG when ready
  40. let postMessage = function (msg) {
  41. switch (msg[0]) {
  42. case 'NOT JSON' :
  43. jfPre.show();
  44. jfContent.html('<span class="x-json-tips">JSON不合法,请检查:</span>');
  45. break;
  46. case 'FORMATTING' :
  47. formattingMsg.show();
  48. break;
  49. case 'FORMATTED' :
  50. formattingMsg.hide();
  51. jfContent.html(msg[1]);
  52. _buildOptionBar();
  53. // 事件绑定
  54. _addEvents();
  55. // 支持文件下载
  56. _downloadSupport(cachedJsonString);
  57. break;
  58. default :
  59. throw new Error('Message not understood: ' + msg[0]);
  60. }
  61. };
  62. /**
  63. * HTML特殊字符格式化
  64. * @param str
  65. * @returns {*}
  66. */
  67. let htmlspecialchars = function (str) {
  68. str = str.replace(/&/g, '&amp;');
  69. str = str.replace(/</g, '&lt;');
  70. str = str.replace(/>/g, '&gt;');
  71. str = str.replace(/"/g, '&quot;');
  72. str = str.replace(/'/g, '&#039;');
  73. return str;
  74. };
  75. /**
  76. * 执行代码格式化
  77. * @param {[type]} jsonStr [description]
  78. * @return {[type]}
  79. */
  80. let format = function (jsonStr) {
  81. cachedJsonString = JSON.stringify(JSON.parse(jsonStr), null, 4);
  82. _initElements();
  83. jfPre.html(htmlspecialchars(cachedJsonString));
  84. JsonFormatDealer.postMessage({
  85. type: "SENDING TEXT",
  86. text: jsonStr,
  87. length: jsonStr.length
  88. });
  89. };
  90. /**
  91. * 直接下载,能解决中文乱码
  92. * @param content
  93. * @private
  94. */
  95. let _downloadSupport = function (content) {
  96. // 下载链接
  97. let dt = (new Date()).format('yyyyMMddHHmmss');
  98. let blob = new Blob([content], {type: 'application/octet-stream'});
  99. let button = $('<button class="xjf-btn xjf-btn-right">下载JSON</button>').appendTo('#optionBar');
  100. if (typeof chrome === 'undefined' || !chrome.permissions) {
  101. button.click(function (e) {
  102. let aLink = $('#aLinkDownload');
  103. if (!aLink[0]) {
  104. aLink = $('<a id="aLinkDownload" target="_blank" title="保存到本地">下载JSON数据</a>').appendTo('body');
  105. aLink.attr('download', 'FeHelper-' + dt + '.json');
  106. aLink.attr('href', URL.createObjectURL(blob));
  107. }
  108. aLink[0].click();
  109. });
  110. } else {
  111. button.click(function (e) {
  112. // 请求权限
  113. chrome.permissions.request({
  114. permissions: ['downloads']
  115. }, (granted) => {
  116. if (granted) {
  117. chrome.downloads.download({
  118. url: URL.createObjectURL(blob),
  119. saveAs: true,
  120. conflictAction: 'overwrite',
  121. filename: 'FeHelper-' + dt + '.json'
  122. });
  123. } else {
  124. toast('必须接受授权,才能正常下载!');
  125. }
  126. });
  127. });
  128. }
  129. };
  130. /**
  131. * chrome 下复制到剪贴板
  132. * @param text
  133. */
  134. let _copyToClipboard = function (text) {
  135. let input = document.createElement('textarea');
  136. input.style.position = 'fixed';
  137. input.style.opacity = 0;
  138. input.value = text;
  139. document.body.appendChild(input);
  140. input.select();
  141. document.execCommand('Copy');
  142. document.body.removeChild(input);
  143. toast('Json片段复制成功,随处粘贴可用!')
  144. };
  145. /**
  146. * 从el中获取json文本
  147. * @param el
  148. * @returns {string}
  149. */
  150. let getJsonText = function (el) {
  151. let txt = el.text().replace(/":\s/gm, '":').replace(/,$/, '').trim();
  152. if (!(/^{/.test(txt) && /\}$/.test(txt)) && !(/^\[/.test(txt) && /\]$/.test(txt))) {
  153. txt = '{' + txt + '}';
  154. }
  155. try {
  156. txt = JSON.stringify(JSON.parse(txt), null, 4);
  157. } catch (err) {
  158. }
  159. return txt;
  160. };
  161. /**
  162. * 给某个节点增加操作项
  163. * @param el
  164. * @private
  165. */
  166. let _addOptForItem = function (el) {
  167. // 下载json片段
  168. let fnDownload = function (ec) {
  169. let txt = getJsonText(el);
  170. // 下载片段
  171. let dt = (new Date()).format('yyyyMMddHHmmss');
  172. let blob = new Blob([txt], {type: 'application/octet-stream'});
  173. if (typeof chrome === 'undefined' || !chrome.permissions) {
  174. // 下载JSON的简单形式
  175. $(this).attr('download', 'FeHelper-' + dt + '.json').attr('href', URL.createObjectURL(blob));
  176. } else {
  177. // 请求权限
  178. chrome.permissions.request({
  179. permissions: ['downloads']
  180. }, (granted) => {
  181. if (granted) {
  182. chrome.downloads.download({
  183. url: URL.createObjectURL(blob),
  184. saveAs: true,
  185. conflictAction: 'overwrite',
  186. filename: 'FeHelper-' + dt + '.json'
  187. });
  188. } else {
  189. toast('必须接受授权,才能正常下载!');
  190. }
  191. });
  192. }
  193. };
  194. // 复制json片段
  195. let fnCopy = function (ec) {
  196. _copyToClipboard(getJsonText(el));
  197. };
  198. // 删除json片段
  199. let fnDel = function (ed) {
  200. if (el.parent().is('#formattedJson')) {
  201. toast('如果连最外层的Json也删掉的话,就没啥意义了哦!');
  202. return false;
  203. }
  204. toast('节点已删除成功!');
  205. el.remove();
  206. jfOptEl.css('top', -1000).hide();
  207. jfPathEl && jfPathEl.hide();
  208. };
  209. jfOptEl = $('#boxOpt');
  210. if (!jfOptEl.length) {
  211. jfOptEl = $('<div id="boxOpt"><a class="opt-download" target="_blank">下载</a>|<a class="opt-copy">复制</a>|<a class="opt-del">删除</a></div>').appendTo(jfContent);
  212. }
  213. jfOptEl.find('a.opt-download').unbind('click').bind('click', fnDownload);
  214. jfOptEl.find('a.opt-copy').unbind('click').bind('click', fnCopy);
  215. jfOptEl.find('a.opt-del').unbind('click').bind('click', fnDel);
  216. jfOptEl.css({
  217. left: el.offset().left + el.width() - 90,
  218. top: el.offset().top
  219. }).show();
  220. };
  221. /**
  222. * 折叠所有
  223. * @param elements
  224. */
  225. function collapse(elements) {
  226. let el;
  227. $.each(elements, function (i) {
  228. el = $(this);
  229. if (el.children('.blockInner').length) {
  230. el.addClass('collapsed');
  231. if (!el.attr('id')) {
  232. el.attr('id', 'kvov' + (++lastKvovIdGiven));
  233. let count = el.children('.blockInner').eq(0).children().length;
  234. // Generate comment text eg "4 items"
  235. let comment = count + (count === 1 ? ' item' : ' items');
  236. // Add CSS that targets it
  237. jfStyleEl[0].insertAdjacentHTML(
  238. 'beforeend',
  239. '\n#kvov' + lastKvovIdGiven + '.collapsed:after{color: #aaa; content:" // ' + comment + '"}'
  240. );
  241. }
  242. }
  243. });
  244. }
  245. /**
  246. * 创建几个全局操作的按钮,置于页面右上角即可
  247. * @private
  248. */
  249. let _buildOptionBar = function () {
  250. let optionBar = $('#optionBar');
  251. if (optionBar.length) {
  252. optionBar.html('');
  253. }else{
  254. optionBar = $('<span id="optionBar" />').appendTo(jfContent.parent());
  255. }
  256. $('<span class="x-split">|</span>').appendTo(optionBar);
  257. let buttonFormatted = $('<button class="xjf-btn xjf-btn-left">元数据</button>').appendTo(optionBar);
  258. let buttonCollapseAll = $('<button class="xjf-btn xjf-btn-mid">折叠所有</button>').appendTo(optionBar);
  259. let plainOn = false;
  260. buttonFormatted.bind('click', function (e) {
  261. if (plainOn) {
  262. plainOn = false;
  263. jfPre.hide();
  264. jfContent.show();
  265. buttonFormatted.text('元数据');
  266. } else {
  267. plainOn = true;
  268. jfPre.show();
  269. jfContent.hide();
  270. buttonFormatted.text('格式化');
  271. }
  272. jfOptEl && jfOptEl.hide();
  273. jfPathEl && jfPathEl.hide();
  274. });
  275. buttonCollapseAll.bind('click', function (e) {
  276. // 如果内容还没有格式化过,需要再格式化一下
  277. if (plainOn) {
  278. buttonFormatted.trigger('click');
  279. }
  280. if (buttonCollapseAll.text() === '折叠所有') {
  281. buttonCollapseAll.text('展开所有');
  282. collapse($('.objProp,.arrElem'));
  283. } else {
  284. buttonCollapseAll.text('折叠所有');
  285. $('.objProp,.arrElem').removeClass('collapsed');
  286. }
  287. jfOptEl && jfOptEl.hide();
  288. jfPathEl && jfPathEl.hide();
  289. });
  290. };
  291. // 显示当前节点的Key
  292. let _showJsonKey = function (curEl) {
  293. let keys = [];
  294. do {
  295. if (curEl.hasClass('arrElem')) {
  296. if (!curEl.hasClass('rootKvov')) {
  297. keys.unshift('[' + curEl.prevAll('.kvov').length + ']');
  298. }
  299. } else {
  300. keys.unshift(curEl.find('>.k').text());
  301. }
  302. if (curEl.parent().hasClass('rootKvov') || curEl.parent().parent().hasClass('rootKvov')) {
  303. break;
  304. }
  305. curEl = curEl.parent().parent();
  306. } while (curEl.length && !curEl.hasClass('rootKvov'));
  307. let path = keys.join('#@#').replace(/#@#\[/g, '[').replace(/#@#/g, '.');
  308. if (!jfPathEl) {
  309. jfPathEl = $('<div/>').css({
  310. position: 'fixed',
  311. bottom: 0,
  312. left: 0,
  313. background: 'rgb(0, 0, 0,0.6)',
  314. color: '#ff0',
  315. fontSize: '12px',
  316. fontWeight: 'bold',
  317. padding: '2px 10px 2px 2px',
  318. zIndex: 10
  319. }).appendTo('body');
  320. }
  321. jfPathEl.html('当前路径:' + path).show();
  322. };
  323. // 附加操作
  324. let _addEvents = function () {
  325. // 折叠、展开
  326. $('#jfContent span.e').bind('click', function (ev) {
  327. ev.preventDefault();
  328. let parentEl = $(this).parent();
  329. parentEl.toggleClass('collapsed');
  330. if (parentEl.hasClass('collapsed')) {
  331. collapse(parentEl);
  332. }
  333. });
  334. // 点击选中:高亮
  335. $('#jfContent .kvov').bind('click', function (e) {
  336. if ($(this).hasClass('x-outline')) {
  337. jfOptEl && jfOptEl.hide();
  338. jfPathEl && jfPathEl.hide();
  339. $(this).removeClass('x-outline');
  340. e.stopPropagation();
  341. return true;
  342. }
  343. $('.x-outline').removeClass('x-outline');
  344. let el = $(this).removeClass('x-hover').addClass('x-outline');
  345. // 增加复制、删除功能
  346. _addOptForItem(el);
  347. // 显示key
  348. _showJsonKey(el);
  349. if (!$(e.target).is('.kvov .e')) {
  350. e.stopPropagation();
  351. } else {
  352. $(e.target).parent().trigger('click');
  353. }
  354. // 触发钩子
  355. if (typeof window._OnJsonItemClickByFH === 'function') {
  356. window._OnJsonItemClickByFH(getJsonText(el));
  357. }
  358. }).bind('mouseover', function (e) {
  359. $(this).addClass('x-hover');
  360. return false;
  361. }).bind('mouseout', function (e) {
  362. $(this).removeClass('x-hover');
  363. });
  364. };
  365. return {
  366. format: format,
  367. postMessage: postMessage
  368. }
  369. })();
  370. let JsonFormatDealer = (function () {
  371. "use strict";
  372. // Constants
  373. let
  374. TYPE_STRING = 1,
  375. TYPE_NUMBER = 2,
  376. TYPE_OBJECT = 3,
  377. TYPE_ARRAY = 4,
  378. TYPE_BOOL = 5,
  379. TYPE_NULL = 6
  380. ;
  381. // Utility functions
  382. function removeComments(str) {
  383. str = ('__' + str + '__').split('');
  384. let mode = {
  385. singleQuote: false,
  386. doubleQuote: false,
  387. regex: false,
  388. blockComment: false,
  389. lineComment: false,
  390. condComp: false
  391. };
  392. for (let i = 0, l = str.length; i < l; i++) {
  393. if (mode.regex) {
  394. if (str[i] === '/' && str[i - 1] !== '\\') {
  395. mode.regex = false;
  396. }
  397. continue;
  398. }
  399. if (mode.singleQuote) {
  400. if (str[i] === "'" && str[i - 1] !== '\\') {
  401. mode.singleQuote = false;
  402. }
  403. continue;
  404. }
  405. if (mode.doubleQuote) {
  406. if (str[i] === '"' && str[i - 1] !== '\\') {
  407. mode.doubleQuote = false;
  408. }
  409. continue;
  410. }
  411. if (mode.blockComment) {
  412. if (str[i] === '*' && str[i + 1] === '/') {
  413. str[i + 1] = '';
  414. mode.blockComment = false;
  415. }
  416. str[i] = '';
  417. continue;
  418. }
  419. if (mode.lineComment) {
  420. if (str[i + 1] === '\n' || str[i + 1] === '\r') {
  421. mode.lineComment = false;
  422. }
  423. str[i] = '';
  424. continue;
  425. }
  426. if (mode.condComp) {
  427. if (str[i - 2] === '@' && str[i - 1] === '*' && str[i] === '/') {
  428. mode.condComp = false;
  429. }
  430. continue;
  431. }
  432. mode.doubleQuote = str[i] === '"';
  433. mode.singleQuote = str[i] === "'";
  434. if (str[i] === '/') {
  435. if (str[i + 1] === '*' && str[i + 2] === '@') {
  436. mode.condComp = true;
  437. continue;
  438. }
  439. if (str[i + 1] === '*') {
  440. str[i] = '';
  441. mode.blockComment = true;
  442. continue;
  443. }
  444. if (str[i + 1] === '/') {
  445. str[i] = '';
  446. mode.lineComment = true;
  447. continue;
  448. }
  449. mode.regex = true;
  450. }
  451. }
  452. return str.join('').slice(2, -2);
  453. }
  454. // Template elements
  455. let templates,
  456. baseDiv = document.createElement('div'),
  457. baseSpan = document.createElement('span');
  458. function getSpanBoth(innerText, className) {
  459. let span = baseSpan.cloneNode(false);
  460. span.className = className;
  461. span.innerText = innerText;
  462. return span;
  463. }
  464. function getSpanText(innerText) {
  465. let span = baseSpan.cloneNode(false);
  466. span.innerText = innerText;
  467. return span;
  468. }
  469. function getSpanClass(className) {
  470. let span = baseSpan.cloneNode(false);
  471. span.className = className;
  472. return span;
  473. }
  474. function getDivClass(className) {
  475. let span = baseDiv.cloneNode(false);
  476. span.className = className;
  477. return span;
  478. }
  479. // Create template nodes
  480. let templatesObj = {
  481. t_kvov: getDivClass('kvov'),
  482. t_key: getSpanClass('k'),
  483. t_string: getSpanClass('s'),
  484. t_number: getSpanClass('n'),
  485. t_exp: getSpanClass('e'),
  486. t_null: getSpanBoth('null', 'nl'),
  487. t_true: getSpanBoth('true', 'bl'),
  488. t_false: getSpanBoth('false', 'bl'),
  489. t_oBrace: getSpanBoth('{', 'b'),
  490. t_cBrace: getSpanBoth('}', 'b'),
  491. t_oBracket: getSpanBoth('[', 'b'),
  492. t_cBracket: getSpanBoth(']', 'b'),
  493. t_ellipsis: getSpanClass('ell'),
  494. t_blockInner: getSpanClass('blockInner'),
  495. t_colonAndSpace: document.createTextNode(':\u00A0'),
  496. t_commaText: document.createTextNode(','),
  497. t_dblqText: document.createTextNode('"')
  498. };
  499. // Core recursive DOM-building function
  500. function getKvovDOM(value, keyName) {
  501. let type,
  502. kvov,
  503. nonZeroSize,
  504. templates = templatesObj, // bring into scope for tiny speed boost
  505. objKey,
  506. keySpan,
  507. valueElement
  508. ;
  509. // Establish value type
  510. if (typeof value === 'string')
  511. type = TYPE_STRING;
  512. else if (typeof value === 'number')
  513. type = TYPE_NUMBER;
  514. else if (value === false || value === true)
  515. type = TYPE_BOOL;
  516. else if (value === null)
  517. type = TYPE_NULL;
  518. else if (value instanceof Array)
  519. type = TYPE_ARRAY;
  520. else
  521. type = TYPE_OBJECT;
  522. // Root node for this kvov
  523. kvov = templates.t_kvov.cloneNode(false);
  524. // Add an 'expander' first (if this is object/array with non-zero size)
  525. if (type === TYPE_OBJECT || type === TYPE_ARRAY) {
  526. if (typeof JSON.BigNumber === 'function' && value instanceof JSON.BigNumber) {
  527. value = JSON.stringify(value);
  528. type = TYPE_NUMBER;
  529. } else {
  530. nonZeroSize = false;
  531. for (objKey in value) {
  532. if (value.hasOwnProperty(objKey)) {
  533. nonZeroSize = true;
  534. break; // no need to keep counting; only need one
  535. }
  536. }
  537. if (nonZeroSize)
  538. kvov.appendChild(templates.t_exp.cloneNode(true));
  539. }
  540. }
  541. // If there's a key, add that before the value
  542. if (keyName !== false) { // NB: "" is a legal keyname in JSON
  543. // This kvov must be an object property
  544. kvov.classList.add('objProp');
  545. // Create a span for the key name
  546. keySpan = templates.t_key.cloneNode(false);
  547. keySpan.textContent = JSON.stringify(keyName).slice(1, -1); // remove quotes
  548. // Add it to kvov, with quote marks
  549. kvov.appendChild(templates.t_dblqText.cloneNode(false));
  550. kvov.appendChild(keySpan);
  551. kvov.appendChild(templates.t_dblqText.cloneNode(false));
  552. // Also add ":&nbsp;" (colon and non-breaking space)
  553. kvov.appendChild(templates.t_colonAndSpace.cloneNode(false));
  554. }
  555. else {
  556. // This is an array element instead
  557. kvov.classList.add('arrElem');
  558. }
  559. // Generate DOM for this value
  560. let blockInner, childKvov;
  561. switch (type) {
  562. case TYPE_STRING:
  563. // If string is a URL, get a link, otherwise get a span
  564. let innerStringEl = baseSpan.cloneNode(false),
  565. escapedString = JSON.stringify(value);
  566. escapedString = escapedString.substring(1, escapedString.length - 1); // remove quotes
  567. if (value[0] === 'h' && value.substring(0, 4) === 'http') { // crude but fast - some false positives, but rare, and UX doesn't suffer terribly from them.
  568. let innerStringA = document.createElement('A');
  569. innerStringA.href = value;
  570. innerStringA.innerText = escapedString;
  571. innerStringEl.appendChild(innerStringA);
  572. }
  573. else {
  574. innerStringEl.innerText = escapedString;
  575. }
  576. valueElement = templates.t_string.cloneNode(false);
  577. valueElement.appendChild(templates.t_dblqText.cloneNode(false));
  578. valueElement.appendChild(innerStringEl);
  579. valueElement.appendChild(templates.t_dblqText.cloneNode(false));
  580. kvov.appendChild(valueElement);
  581. break;
  582. case TYPE_NUMBER:
  583. // Simply add a number element (span.n)
  584. valueElement = templates.t_number.cloneNode(false);
  585. valueElement.innerText = value;
  586. kvov.appendChild(valueElement);
  587. break;
  588. case TYPE_OBJECT:
  589. // Add opening brace
  590. kvov.appendChild(templates.t_oBrace.cloneNode(true));
  591. // If any properties, add a blockInner containing k/v pair(s)
  592. if (nonZeroSize) {
  593. // Add ellipsis (empty, but will be made to do something when kvov is collapsed)
  594. kvov.appendChild(templates.t_ellipsis.cloneNode(false));
  595. // Create blockInner, which indents (don't attach yet)
  596. blockInner = templates.t_blockInner.cloneNode(false);
  597. // For each key/value pair, add as a kvov to blockInner
  598. let count = 0, k, comma;
  599. for (k in value) {
  600. if (value.hasOwnProperty(k)) {
  601. count++;
  602. childKvov = getKvovDOM(value[k], k);
  603. // Add comma
  604. comma = templates.t_commaText.cloneNode();
  605. childKvov.appendChild(comma);
  606. blockInner.appendChild(childKvov);
  607. }
  608. }
  609. // Now remove the last comma
  610. childKvov.removeChild(comma);
  611. // Add blockInner
  612. kvov.appendChild(blockInner);
  613. }
  614. // Add closing brace
  615. kvov.appendChild(templates.t_cBrace.cloneNode(true));
  616. break;
  617. case TYPE_ARRAY:
  618. // Add opening bracket
  619. kvov.appendChild(templates.t_oBracket.cloneNode(true));
  620. // If non-zero length array, add blockInner containing inner vals
  621. if (nonZeroSize) {
  622. // Add ellipsis
  623. kvov.appendChild(templates.t_ellipsis.cloneNode(false));
  624. // Create blockInner (which indents) (don't attach yet)
  625. blockInner = templates.t_blockInner.cloneNode(false);
  626. // For each key/value pair, add the markup
  627. for (let i = 0, length = value.length, lastIndex = length - 1; i < length; i++) {
  628. // Make a new kvov, with no key
  629. childKvov = getKvovDOM(value[i], false);
  630. // Add comma if not last one
  631. if (i < lastIndex)
  632. childKvov.appendChild(templates.t_commaText.cloneNode());
  633. // Append the child kvov
  634. blockInner.appendChild(childKvov);
  635. }
  636. // Add blockInner
  637. kvov.appendChild(blockInner);
  638. }
  639. // Add closing bracket
  640. kvov.appendChild(templates.t_cBracket.cloneNode(true));
  641. break;
  642. case TYPE_BOOL:
  643. if (value)
  644. kvov.appendChild(templates.t_true.cloneNode(true));
  645. else
  646. kvov.appendChild(templates.t_false.cloneNode(true));
  647. break;
  648. case TYPE_NULL:
  649. kvov.appendChild(templates.t_null.cloneNode(true));
  650. break;
  651. }
  652. return kvov;
  653. }
  654. // Function to convert object to an HTML string
  655. function jsonObjToHTML(obj, jsonpFunctionName) {
  656. // Format object (using recursive kvov builder)
  657. let rootKvov = getKvovDOM(obj, false);
  658. // The whole DOM is now built.
  659. // Set class on root node to identify it
  660. rootKvov.classList.add('rootKvov');
  661. // Make div#formattedJson and append the root kvov
  662. let divFormattedJson = document.createElement('DIV');
  663. divFormattedJson.id = 'formattedJson';
  664. divFormattedJson.appendChild(rootKvov);
  665. // Convert it to an HTML string (shame about this step, but necessary for passing it through to the content page)
  666. let returnHTML = divFormattedJson.outerHTML;
  667. // Top and tail with JSONP padding if necessary
  668. if (jsonpFunctionName !== null) {
  669. returnHTML =
  670. '<div id="jsonpOpener">' + jsonpFunctionName + ' ( </div>' +
  671. returnHTML +
  672. '<div id="jsonpCloser">)</div>';
  673. }
  674. // Return the HTML
  675. return returnHTML;
  676. }
  677. // Listen for requests from content pages wanting to set up a port
  678. let postMessage = function (msg) {
  679. let jsonpFunctionName = null;
  680. if (msg.type === 'SENDING TEXT') {
  681. // Try to parse as JSON
  682. let obj,
  683. text = msg.text;
  684. try {
  685. obj = JSON.parse(text);
  686. }
  687. catch (e) {
  688. // Not JSON; could be JSONP though.
  689. // Try stripping 'padding' (if any), and try parsing it again
  690. text = text.trim();
  691. // Find where the first paren is (and exit if none)
  692. let indexOfParen;
  693. if (!(indexOfParen = text.indexOf('('))) {
  694. JsonFormatEntrance.postMessage(['NOT JSON', 'no opening parenthesis']);
  695. return;
  696. }
  697. // Get the substring up to the first "(", with any comments/whitespace stripped out
  698. let firstBit = removeComments(text.substring(0, indexOfParen)).trim();
  699. if (!firstBit.match(/^[a-zA-Z_$][\.\[\]'"0-9a-zA-Z_$]*$/)) {
  700. // The 'firstBit' is NOT a valid function identifier.
  701. JsonFormatEntrance.postMessage(['NOT JSON', 'first bit not a valid function name']);
  702. return;
  703. }
  704. // Find last parenthesis (exit if none)
  705. let indexOfLastParen;
  706. if (!(indexOfLastParen = text.lastIndexOf(')'))) {
  707. JsonFormatEntrance.postMessage(['NOT JSON', 'no closing paren']);
  708. return;
  709. }
  710. // Check that what's after the last parenthesis is just whitespace, comments, and possibly a semicolon (exit if anything else)
  711. let lastBit = removeComments(text.substring(indexOfLastParen + 1)).trim();
  712. if (lastBit !== "" && lastBit !== ';') {
  713. JsonFormatEntrance.postMessage(['NOT JSON', 'last closing paren followed by invalid characters']);
  714. return;
  715. }
  716. // So, it looks like a valid JS function call, but we don't know whether it's JSON inside the parentheses...
  717. // Check if the 'argument' is actually JSON (and record the parsed result)
  718. text = text.substring(indexOfParen + 1, indexOfLastParen);
  719. try {
  720. obj = JSON.parse(text);
  721. }
  722. catch (e2) {
  723. // Just some other text that happens to be in a function call.
  724. // Respond as not JSON, and exit
  725. JsonFormatEntrance.postMessage(['NOT JSON', 'looks like a function call, but the parameter is not valid JSON']);
  726. return;
  727. }
  728. jsonpFunctionName = firstBit;
  729. }
  730. // If still running, we now have obj, which is valid JSON.
  731. // Ensure it's not a number or string (technically valid JSON, but no point prettifying it)
  732. if (typeof obj !== 'object' && typeof obj !== 'array') {
  733. JsonFormatEntrance.postMessage(['NOT JSON', 'technically JSON but not an object or array']);
  734. return;
  735. }
  736. JsonFormatEntrance.postMessage(['FORMATTING']);
  737. try {
  738. // 有的页面设置了 安全策略,连localStorage都不能用,setTimeout开启多线程就更别说了
  739. localStorage.getItem('just test : Blocked script execution in xxx?');
  740. // 在非UI线程中操作:异步。。。
  741. setTimeout(function () {
  742. // Do formatting
  743. let html = jsonObjToHTML(obj, jsonpFunctionName);
  744. // Post the HTML string to the content script
  745. JsonFormatEntrance.postMessage(['FORMATTED', html]);
  746. }, 0);
  747. } catch (ex) {
  748. // 错误信息类似:Failed to read the 'localStorage' property from 'Window': The document is sandboxed and lacks the 'allow-same-origin' flag.
  749. let html = jsonObjToHTML(obj, jsonpFunctionName);
  750. JsonFormatEntrance.postMessage(['FORMATTED', html]);
  751. }
  752. }
  753. };
  754. return {
  755. postMessage: postMessage
  756. };
  757. })();
  758. window.Formatter = {
  759. format: JsonFormatEntrance.format
  760. };
  761. /**
  762. * 日期格式化
  763. * @param {Object} pattern
  764. */
  765. Date.prototype.format = function (pattern) {
  766. let pad = function (source, length) {
  767. let pre = "",
  768. negative = (source < 0),
  769. string = String(Math.abs(source));
  770. if (string.length < length) {
  771. pre = (new Array(length - string.length + 1)).join('0');
  772. }
  773. return (negative ? "-" : "") + pre + string;
  774. };
  775. if ('string' !== typeof pattern) {
  776. return this.toString();
  777. }
  778. let replacer = function (patternPart, result) {
  779. pattern = pattern.replace(patternPart, result);
  780. };
  781. let year = this.getFullYear(),
  782. month = this.getMonth() + 1,
  783. date2 = this.getDate(),
  784. hours = this.getHours(),
  785. minutes = this.getMinutes(),
  786. seconds = this.getSeconds(),
  787. milliSec = this.getMilliseconds();
  788. replacer(/yyyy/g, pad(year, 4));
  789. replacer(/yy/g, pad(parseInt(year.toString().slice(2), 10), 2));
  790. replacer(/MM/g, pad(month, 2));
  791. replacer(/M/g, month);
  792. replacer(/dd/g, pad(date2, 2));
  793. replacer(/d/g, date2);
  794. replacer(/HH/g, pad(hours, 2));
  795. replacer(/H/g, hours);
  796. replacer(/hh/g, pad(hours % 12, 2));
  797. replacer(/h/g, hours % 12);
  798. replacer(/mm/g, pad(minutes, 2));
  799. replacer(/m/g, minutes);
  800. replacer(/ss/g, pad(seconds, 2));
  801. replacer(/s/g, seconds);
  802. replacer(/SSS/g, pad(milliSec, 3));
  803. replacer(/S/g, milliSec);
  804. return pattern;
  805. };
  806. /**
  807. * 自动消失的Alert弹窗
  808. * @param content
  809. */
  810. window.toast = function (content) {
  811. window.clearTimeout(window.feHelperAlertMsgTid);
  812. let elAlertMsg = document.querySelector("#fehelper_alertmsg");
  813. if (!elAlertMsg) {
  814. let elWrapper = document.createElement('div');
  815. elWrapper.innerHTML = '<div id="fehelper_alertmsg" style="position:fixed;top:5px;right:5px;z-index:1000000">' +
  816. '<p style="background:#000;display:inline-block;color:#fff;text-align:center;' +
  817. 'padding:10px 10px;margin:0 auto;font-size:14px;border-radius:4px;">' + content + '</p></div>';
  818. elAlertMsg = elWrapper.childNodes[0];
  819. document.body.appendChild(elAlertMsg);
  820. } else {
  821. elAlertMsg.querySelector('p').innerHTML = content;
  822. elAlertMsg.style.display = 'block';
  823. }
  824. window.feHelperAlertMsgTid = window.setTimeout(function () {
  825. elAlertMsg.style.display = 'none';
  826. }, 3000);
  827. };