JSON formatter

Format JSON data in a beautiful way.

  1. // ==UserScript==
  2. // @name JSON formatter
  3. // @namespace https://gera2ld.space
  4. // @author Gerald <gera2ld@live.com>
  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 2.0.13
  9. // @require https://cdn.jsdelivr.net/npm/@violentmonkey/dom@2
  10. // @match *://*/*
  11. // @match file:///*
  12. // @grant GM_addElement
  13. // @grant GM_registerMenuCommand
  14. // @grant GM_xmlhttpRequest
  15. // ==/UserScript==
  16.  
  17. (function () {
  18. 'use strict';
  19.  
  20. const JSON_VIEWER_EMBED = 'https://rally.pore.run/embed';
  21. let iframe;
  22. if (window === window.top && window.location.origin + window.location.pathname === JSON_VIEWER_EMBED) {
  23. handleViewerAfterRedirection();
  24. } else {
  25. if (testRules([
  26. // text/javascript - file:///foo/bar.js
  27. /^(?:text|application)\/(?:.*?\+)?(?:plain|json|javascript)$/], document.contentType)) handleViewerIframe();
  28. GM_registerMenuCommand('Format JSON', handleViewerIframe);
  29. }
  30. function testRules(rules, contentType) {
  31. for (const rule of rules) {
  32. if (typeof rule === 'string') {
  33. if (rule === contentType) return true;
  34. } else if (typeof (rule == null ? void 0 : rule.test) === 'function') {
  35. if (rule.test(contentType)) return true;
  36. }
  37. }
  38. return false;
  39. }
  40. function handleViewerIframe() {
  41. if (iframe) return;
  42. const content = JSON.parse(document.body.textContent);
  43. document.body.innerHTML = '';
  44. iframe = GM_addElement(document.body, 'iframe', {
  45. sandbox: 'allow-scripts allow-same-origin',
  46. src: JSON_VIEWER_EMBED,
  47. style: `position:fixed;width:100vw;height:100vh;inset:0;border:none`
  48. });
  49. let initiated = false;
  50. const setData = () => {
  51. initiated = true;
  52. handleInitData(content, iframe.contentWindow);
  53. };
  54. iframe.addEventListener('load', () => {
  55. setTimeout(() => {
  56. if (!initiated) {
  57. // JS blocked, redirect to the embed page
  58. const url = new URL(JSON_VIEWER_EMBED);
  59. url.searchParams.set('from', 'userscript');
  60. url.searchParams.set('json_url', window.location.href);
  61. window.location.assign(url);
  62. }
  63. }, 2000);
  64. });
  65. window.addEventListener('message', e => {
  66. const {
  67. type
  68. } = e.data;
  69. switch (type) {
  70. case 'ready':
  71. {
  72. setData();
  73. break;
  74. }
  75. }
  76. });
  77. }
  78. async function handleViewerAfterRedirection() {
  79. const url = new URL(window.location.href);
  80. if (url.searchParams.get('from') !== 'userscript') return;
  81. const jsonUrl = url.searchParams.get('json_url');
  82. const content = await new Promise((resolve, reject) => {
  83. GM_xmlhttpRequest({
  84. url: jsonUrl,
  85. responseType: 'json',
  86. onload: res => {
  87. resolve(res.response);
  88. },
  89. onerror: res => {
  90. reject(res);
  91. }
  92. });
  93. });
  94. const setData = () => {
  95. handleInitData(content, window, jsonUrl);
  96. };
  97. window.addEventListener('load', () => {
  98. setData();
  99. });
  100. window.addEventListener('message', e => {
  101. const {
  102. type
  103. } = e.data;
  104. switch (type) {
  105. case 'ready':
  106. {
  107. setData();
  108. break;
  109. }
  110. }
  111. });
  112. }
  113. function handleInitData(content, window, jsonUrl) {
  114. window.postMessage({
  115. type: 'setReadOnly',
  116. payload: true
  117. }, '*');
  118. window.postMessage({
  119. type: 'setData',
  120. payload: content
  121. }, '*');
  122. if (jsonUrl) {
  123. window.postMessage({
  124. type: 'setBanner',
  125. payload: {
  126. html: `JSON URL: <a href="${jsonUrl}">${jsonUrl}</a>`
  127. }
  128. });
  129. }
  130. }
  131.  
  132. })();