Greasy Fork 支持简体中文。

JSON formatter

Format JSON data in a beautiful way.

目前為 2016-04-30 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name JSON formatter
  3. // @namespace http://gerald.top
  4. // @author Gerald <i@gerald.top>
  5. // @icon http://cn.gravatar.com/avatar/a0ad718d86d21262ccd6ff271ece08a3?s=80
  6. // @description Format JSON data in a beautiful way.
  7. // @description:zh-CN 更加漂亮地显示JSON数据。
  8. // @version 1.2
  9. // @match *://*/*
  10. // @match file:///*
  11. // @grant GM_getValue
  12. // @grant GM_setValue
  13. // @grant GM_addStyle
  14. // @grant GM_registerMenuCommand
  15. // ==/UserScript==
  16.  
  17. function safeHTML(html) {
  18. return String(html).replace(/[<&"]/g, function (key) {
  19. return {
  20. '<': '&lt;',
  21. '&': '&amp;',
  22. '"': '&quot;',
  23. }[key];
  24. });
  25. }
  26.  
  27. function join(list) {
  28. function open() {
  29. if (!isOpen) {
  30. html.push('<li>');
  31. isOpen = true;
  32. }
  33. }
  34. function close() {
  35. if (isOpen) {
  36. html.push('</li>');
  37. isOpen = false;
  38. }
  39. }
  40. var html = [];
  41. var isOpen = false;
  42. list.forEach(function (item, i) {
  43. var next = list[i + 1];
  44. open();
  45. item.data && html.push(item.data);
  46. next && item.separator && html.push(item.separator);
  47. if (
  48. !next
  49. || next.type === KEY
  50. || item.type !== KEY && (
  51. item.type === SINGLELINE || next.type === SINGLELINE
  52. )
  53. ) close();
  54. });
  55. return html.join('');
  56. }
  57.  
  58. function getHtml(data) {
  59. var type = typeof data.value;
  60. var html = '<span class="' + (data.cls || 'value ' + type) + '" ' +
  61. 'data-type="' + safeHTML(data.type || type) + '" ' +
  62. 'data-value="' + safeHTML(data.value) + '">' + safeHTML(data.value) + '</span>';
  63. if ((data.cls === 'key' || !data.cls && type === 'string') && config.showQuotes)
  64. html = '"' + html + '"';
  65. return html;
  66. }
  67.  
  68. function render(data) {
  69. var ret;
  70. if (Array.isArray(data)) {
  71. var arr = [];
  72. ret = {
  73. type: MULTILINE,
  74. separator: getHtml({
  75. value: config.showSeparators ? ',' : '',
  76. cls: 'separator',
  77. }),
  78. };
  79. arr.push(getHtml({value: '[', cls: 'operator'}));
  80. if (data.length) {
  81. arr.push('<ul>', join(data.map(render)), '</ul>');
  82. } else {
  83. arr.push(getHtml({value: '', cls: 'separator'}));
  84. ret.type = SINGLELINE;
  85. }
  86. arr.push(getHtml({value: ']', cls: 'operator'}));
  87. ret.data = arr.join('');
  88. } else if (data === null) {
  89. ret = {
  90. type: SINGLELINE,
  91. separator: getHtml({
  92. value: config.showSeparators ? ',' : '',
  93. cls: 'separator',
  94. }),
  95. data: getHtml({value: data, cls: 'value null'}),
  96. };
  97. } else if (typeof data == 'object') {
  98. var arr = [];
  99. ret = {
  100. type: MULTILINE,
  101. separator: getHtml({
  102. value: config.showSeparators ? ',' : '',
  103. cls: 'separator',
  104. }),
  105. };
  106. arr.push(getHtml({value: '{', cls: 'operator'}));
  107. var objdata = [];
  108. for (var key in data) {
  109. objdata.push({
  110. type: KEY,
  111. data: getHtml({value: key, cls: 'key'}),
  112. separator: getHtml({value: ':', cls: 'separator'}),
  113. }, render(data[key]));
  114. }
  115. if (objdata.length) {
  116. arr.push('<ul>', join(objdata), '</ul>');
  117. } else {
  118. arr.push(getHtml({value: '', cls: 'separator'}));
  119. ret.type = SINGLELINE;
  120. }
  121. arr.push(getHtml({value: '}', cls: 'operator'}));
  122. ret.data = arr.join('');
  123. } else {
  124. ret = {
  125. type: SINGLELINE,
  126. separator: getHtml({
  127. value: config.showSeparators ? ',' : '',
  128. cls: 'separator',
  129. }),
  130. data: getHtml({value: data}),
  131. };
  132. }
  133. return ret;
  134. }
  135.  
  136. function formatJSON() {
  137. if (formatter.formatted) {
  138. formatter.tips.hide();
  139. formatter.menu.detach();
  140. document.body.innerHTML = formatter.raw;
  141. formatter.formatted = false;
  142. } else {
  143. if (!('raw' in formatter)) {
  144. formatter.raw = document.body.innerHTML;
  145. formatter.data = JSON.parse(document.body.innerText);
  146. formatter.style = GM_addStyle([
  147. '*{font-family:Microsoft YaHei,Tahoma;font-size:14px;}',
  148. 'body,ul{margin:0;padding:0;}',
  149. '#root{position:relative;margin:0;padding:1em;}',
  150. '#root>ul ul{padding-left:2em;}',
  151. 'li{list-style:none;}',
  152. '.separator{margin-right:.5em;}',
  153. '.number{color:darkorange;}',
  154. '.null{color:gray;}',
  155. '.key{color:brown;}',
  156. '.string{color:green;}',
  157. '.boolean{color:dodgerblue;}',
  158. '.operator{color:blue;}',
  159. '.value{cursor:pointer;}',
  160. '.tips{position:absolute;padding:.5em;border-radius:.5em;box-shadow:0 0 1em gray;background:white;z-index:1;white-space:nowrap;color:black;}',
  161. '.tips-key{font-weight:bold;}',
  162. '.tips-val{color:dodgerblue;}',
  163. '.menu{position:fixed;top:0;right:0;background:white;padding:5px;}',
  164. '.menu>span{margin-right:5px;}',
  165. '.menu .btn{display:inline-block;width:18px;height:18px;line-height:18px;text-align:center;background:#eee;border-radius:4px;cursor:pointer;}',
  166. '.menu .btn.active{color:white;background:#333;}',
  167. '.hide{display:none;}',
  168. ].join(''));
  169. initTips();
  170. initMenu();
  171. formatter.render = function () {
  172. var root = formatter.root.querySelector('li');
  173. root.innerHTML = render(formatter.data).data;
  174. };
  175. }
  176. document.body.innerHTML = '<div id="root"><ul><li></li></ul></div>';
  177. formatter.formatted = true;
  178. formatter.root = document.querySelector('#root');
  179. formatter.menu.attach();
  180. bindEvents();
  181. formatter.render();
  182. }
  183. }
  184.  
  185. function removeEl(el) {
  186. el && el.parentNode && el.parentNode.removeChild(el);
  187. }
  188.  
  189. function initMenu() {
  190. var menu = document.createElement('div');
  191. menu.className = 'menu';
  192. menu.innerHTML = [
  193. '<span class="btn" data-key="showQuotes">"</span>',
  194. '<span class="btn" data-key="showSeparators">,</span>',
  195. ].join('');
  196. [].forEach.call(menu.querySelectorAll('[data-key]'), function (el) {
  197. if (config[el.dataset.key]) el.classList.add('active');
  198. });
  199. menu.addEventListener('click', function (e) {
  200. var el = e.target;
  201. var key = el.dataset.key;
  202. if (key) {
  203. config[key] = !config[key];
  204. GM_setValue('config', config);
  205. el.classList.toggle('active');
  206. formatter.render();
  207. }
  208. }, false);
  209. formatter.menu = {
  210. node: menu,
  211. attach: function () {
  212. formatter.root.appendChild(menu);
  213. },
  214. detach: function () {
  215. removeEl(menu);
  216. },
  217. };
  218. }
  219.  
  220. function initTips() {
  221. function hide() {
  222. removeEl(tips);
  223. }
  224. var tips = document.createElement('div');
  225. tips.className = 'tips';
  226. tips.addEventListener('click', function (e) {
  227. e.stopPropagation();
  228. }, false);
  229. document.addEventListener('click', hide, false);
  230. formatter.tips = {
  231. node: tips,
  232. hide: hide,
  233. show: function (range) {
  234. var gap = 5;
  235. var scrollTop = document.body.scrollTop;
  236. var rects = range.getClientRects(), rect;
  237. if (rects[0].top < 100) {
  238. rect = rects[rects.length - 1];
  239. tips.style.top = rect.bottom + scrollTop + gap + 'px';
  240. tips.style.bottom = '';
  241. } else {
  242. rect = rects[0];
  243. tips.style.top = '';
  244. tips.style.bottom = formatter.root.offsetHeight - rect.top - scrollTop + gap + 'px';
  245. }
  246. tips.style.left = rect.left + 'px';
  247. tips.innerHTML = '<span class="tips-key">type</span>: <span class="tips-val">' + safeHTML(range.startContainer.dataset.type) + '</span>';
  248. formatter.root.appendChild(tips);
  249. },
  250. };
  251. }
  252.  
  253. function selectNode(node) {
  254. var selection = window.getSelection();
  255. selection.removeAllRanges();
  256. var range = document.createRange();
  257. range.setStartBefore(node.firstChild);
  258. range.setEndAfter(node.firstChild);
  259. selection.addRange(range);
  260. return range;
  261. }
  262.  
  263. function bindEvents() {
  264. formatter.root.addEventListener('click', function (e) {
  265. e.stopPropagation();
  266. var target = e.target;
  267. if (target.classList.contains('value')) {
  268. formatter.tips.show(selectNode(target));
  269. } else
  270. formatter.tips.hide();
  271. }, false);
  272. }
  273.  
  274. var getId = function () {
  275. var id = 0;
  276. return function () {
  277. return ++ id;
  278. };
  279. }();
  280. var SINGLELINE = getId();
  281. var MULTILINE = getId();
  282. var KEY = getId();
  283.  
  284. var formatter = {};
  285. var config = GM_getValue('config', {
  286. showSeparators: true,
  287. showQuotes: true,
  288. });
  289.  
  290. ~[
  291. 'application/json',
  292. 'text/plain',
  293. ].indexOf(document.contentType) && formatJSON();
  294. GM_registerMenuCommand('Toggle JSON format', formatJSON);