index.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. /**
  2. * FeHelper Json Format Tools
  3. */
  4. // 一些全局变量
  5. let editor = {};
  6. let LOCAL_KEY_OF_LAYOUT = 'local-layout-key';
  7. let JSON_LINT = 'jsonformat:json-lint-switch';
  8. let EDIT_ON_CLICK = 'jsonformat:edit-on-click';
  9. let AUTO_DECODE = 'jsonformat:auto-decode';
  10. new Vue({
  11. el: '#pageContainer',
  12. data: {
  13. defaultResultTpl: '<div class="x-placeholder"><img src="../json-format/json-demo.jpg" alt="json-placeholder"></div>',
  14. placeHolder: '',
  15. jsonFormattedSource: '',
  16. errorMsg: '',
  17. errorJsonCode: '',
  18. errorPos: '',
  19. jfCallbackName_start: '',
  20. jfCallbackName_end: '',
  21. jsonLintSwitch: true,
  22. autoDecode: false,
  23. fireChange: true,
  24. overrideJson: false,
  25. isInUSAFlag: false,
  26. autoUnpackJsonString: false
  27. },
  28. mounted: function () {
  29. // 自动开关灯控制
  30. DarkModeMgr.turnLightAuto();
  31. this.placeHolder = this.defaultResultTpl;
  32. this.autoDecode = localStorage.getItem(AUTO_DECODE);
  33. this.autoDecode = this.autoDecode === 'true';
  34. this.isInUSAFlag = this.isInUSA();
  35. this.jsonLintSwitch = (localStorage.getItem(JSON_LINT) !== 'false');
  36. this.overrideJson = (localStorage.getItem(EDIT_ON_CLICK) === 'true');
  37. this.changeLayout(localStorage.getItem(LOCAL_KEY_OF_LAYOUT));
  38. editor = CodeMirror.fromTextArea(this.$refs.jsonBox, {
  39. mode: "text/javascript",
  40. lineNumbers: true,
  41. matchBrackets: true,
  42. styleActiveLine: true,
  43. lineWrapping: true
  44. });
  45. //输入框聚焦
  46. editor.focus();
  47. // 格式化以后的JSON,点击以后可以重置原内容
  48. window._OnJsonItemClickByFH = (jsonTxt) => {
  49. if (this.overrideJson) {
  50. this.disableEditorChange(jsonTxt);
  51. }
  52. };
  53. editor.on('change', (editor, changes) => {
  54. this.jsonFormattedSource = editor.getValue().replace(/\n/gm, ' ');
  55. this.fireChange && this.format();
  56. });
  57. // 在tab创建或者更新时候,监听事件,看看是否有参数传递过来
  58. if (location.protocol === 'chrome-extension:') {
  59. chrome.tabs.query({currentWindow: true,active: true, }, (tabs) => {
  60. let activeTab = tabs.filter(tab => tab.active)[0];
  61. chrome.runtime.sendMessage({
  62. type: 'fh-dynamic-any-thing',
  63. thing: 'request-page-content',
  64. tabId: activeTab.id
  65. }).then(resp => {
  66. if(!resp || !resp.content) return ;
  67. editor.setValue(resp.content || '');
  68. this.format();
  69. });
  70. });
  71. }
  72. },
  73. methods: {
  74. isInUSA: function () {
  75. // 通过时区判断是否在美国
  76. const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
  77. const isUSTimeZone = /^America\/(New_York|Chicago|Denver|Los_Angeles|Anchorage|Honolulu)/.test(timeZone);
  78. // 通过语言判断
  79. const language = navigator.language || navigator.userLanguage;
  80. const isUSLanguage = language.toLowerCase().indexOf('en-us') > -1;
  81. // 如果时区和语言都符合美国特征,则认为在美国
  82. return (isUSTimeZone && isUSLanguage);
  83. },
  84. format: function () {
  85. this.errorMsg = '';
  86. this.placeHolder = this.defaultResultTpl;
  87. this.jfCallbackName_start = '';
  88. this.jfCallbackName_end = '';
  89. let source = editor.getValue().replace(/\n/gm, ' ');
  90. if (!source) {
  91. return false;
  92. }
  93. // JSONP形式下的callback name
  94. let funcName = null;
  95. // json对象
  96. let jsonObj = null;
  97. // 下面校验给定字符串是否为一个合法的json
  98. try {
  99. // 再看看是不是jsonp的格式
  100. let reg = /^([\w\.]+)\(\s*([\s\S]*)\s*\)$/igm;
  101. let matches = reg.exec(source);
  102. if (matches != null) {
  103. funcName = matches[1];
  104. source = matches[2];
  105. }
  106. // 这里可能会throw exception
  107. jsonObj = JSON.parse(source);
  108. } catch (ex) {
  109. // new Function的方式,能自动给key补全双引号,但是不支持bigint,所以是下下策,放在try-catch里搞
  110. try {
  111. jsonObj = new Function("return " + source)();
  112. } catch (exx) {
  113. try {
  114. // 再给你一次机会,是不是下面这种情况: "{\"ret\":\"0\", \"msg\":\"ok\"}"
  115. jsonObj = new Function("return '" + source + "'")();
  116. if (typeof jsonObj === 'string') {
  117. try {
  118. // 确保bigint不会失真
  119. jsonObj = JSON.parse(jsonObj);
  120. } catch (ie) {
  121. // 最后给你一次机会,是个字符串,老夫给你再转一次
  122. jsonObj = new Function("return " + jsonObj)();
  123. }
  124. }
  125. } catch (exxx) {
  126. this.errorMsg = exxx.message;
  127. }
  128. }
  129. }
  130. try{
  131. // 这里多做一个动作,给没有携带双引号的Key都自动加上,防止Long类型失真
  132. const regex = /([{,]\s*)(\w+)(\s*:)/g;
  133. source = source.replace(regex, '$1"$2"$3');
  134. jsonObj = JSON.parse(source);
  135. }catch(e){
  136. // 这里什么动作都不需要做,这种情况下转换失败的,肯定是Value被污染了,抛弃即可
  137. }
  138. // 新增:自动解包嵌套JSON字符串
  139. if (this.autoUnpackJsonString && jsonObj != null && typeof jsonObj === 'object') {
  140. jsonObj = deepParseJSONStrings(jsonObj);
  141. source = JSON.stringify(jsonObj);
  142. }
  143. // 是json格式,可以进行JSON自动格式化
  144. if (jsonObj != null && typeof jsonObj === "object" && !this.errorMsg.length) {
  145. try {
  146. let sortType = document.querySelectorAll('[name=jsonsort]:checked')[0].value;
  147. if (sortType !== '0') {
  148. jsonObj = JsonABC.sortObj(jsonObj, parseInt(sortType), true);
  149. }
  150. source = JSON.stringify(jsonObj);
  151. } catch (ex) {
  152. // 通过JSON反解不出来的,一定有问题
  153. this.errorMsg = ex.message;
  154. }
  155. if (!this.errorMsg.length) {
  156. if (this.autoDecode) {
  157. (async () => {
  158. let txt = await JsonEnDecode.urlDecodeByFetch(source);
  159. source = JsonEnDecode.uniDecode(txt);
  160. await Formatter.format(source);
  161. })();
  162. } else {
  163. (async () => {
  164. await Formatter.format(source);
  165. })();
  166. }
  167. this.placeHolder = '';
  168. this.jsonFormattedSource = source;
  169. // 如果是JSONP格式的,需要把方法名也显示出来
  170. if (funcName != null) {
  171. this.jfCallbackName_start = funcName + '(';
  172. this.jfCallbackName_end = ')';
  173. } else {
  174. this.jfCallbackName_start = '';
  175. this.jfCallbackName_end = '';
  176. }
  177. this.$nextTick(() => {
  178. this.updateWrapperHeight();
  179. })
  180. }
  181. }
  182. if (this.errorMsg.length) {
  183. if (this.jsonLintSwitch) {
  184. return this.lintOn();
  185. } else {
  186. this.placeHolder = '<span class="x-error">' + this.errorMsg + '</span>';
  187. return false;
  188. }
  189. }
  190. return true;
  191. },
  192. compress: function () {
  193. if (this.format()) {
  194. let jsonTxt = this.jfCallbackName_start + this.jsonFormattedSource + this.jfCallbackName_end;
  195. this.disableEditorChange(jsonTxt);
  196. }
  197. },
  198. autoDecodeFn: function () {
  199. this.$nextTick(() => {
  200. localStorage.setItem(AUTO_DECODE, this.autoDecode);
  201. this.format();
  202. });
  203. },
  204. uniEncode: function () {
  205. editor.setValue(JsonEnDecode.uniEncode(editor.getValue()));
  206. },
  207. uniDecode: function () {
  208. editor.setValue(JsonEnDecode.uniDecode(editor.getValue()));
  209. },
  210. urlDecode: function () {
  211. JsonEnDecode.urlDecodeByFetch(editor.getValue()).then(text => editor.setValue(text));
  212. },
  213. updateWrapperHeight: function () {
  214. let curLayout = localStorage.getItem(LOCAL_KEY_OF_LAYOUT);
  215. let elPc = document.querySelector('#pageContainer');
  216. if (curLayout === 'up-down') {
  217. elPc.style.height = 'auto';
  218. } else {
  219. elPc.style.height = Math.max(elPc.scrollHeight, document.body.scrollHeight) + 'px';
  220. }
  221. },
  222. changeLayout: function (type) {
  223. let elPc = document.querySelector('#pageContainer');
  224. if (type === 'up-down') {
  225. elPc.classList.remove('layout-left-right');
  226. elPc.classList.add('layout-up-down');
  227. this.$refs.btnLeftRight.classList.remove('selected');
  228. this.$refs.btnUpDown.classList.add('selected');
  229. } else {
  230. elPc.classList.remove('layout-up-down');
  231. elPc.classList.add('layout-left-right');
  232. this.$refs.btnLeftRight.classList.add('selected');
  233. this.$refs.btnUpDown.classList.remove('selected');
  234. }
  235. localStorage.setItem(LOCAL_KEY_OF_LAYOUT, type);
  236. this.updateWrapperHeight();
  237. },
  238. setCache: function () {
  239. this.$nextTick(() => {
  240. localStorage.setItem(EDIT_ON_CLICK, this.overrideJson);
  241. });
  242. },
  243. lintOn: function () {
  244. this.$nextTick(() => {
  245. localStorage.setItem(JSON_LINT, this.jsonLintSwitch);
  246. });
  247. if (!editor.getValue().trim()) {
  248. return true;
  249. }
  250. this.$nextTick(() => {
  251. if (!this.jsonLintSwitch) {
  252. return;
  253. }
  254. let lintResult = JsonLint.lintDetect(editor.getValue());
  255. if (!isNaN(lintResult.line)) {
  256. this.placeHolder = '<div id="errorTips">' +
  257. '<div id="tipsBox">错误位置:' + (lintResult.line + 1) + '行,' + (lintResult.col + 1) + '列;缺少字符或字符不正确</div>' +
  258. '<div id="errorCode">' + lintResult.dom + '</div></div>';
  259. }
  260. });
  261. return false;
  262. },
  263. disableEditorChange: function (jsonTxt) {
  264. this.fireChange = false;
  265. this.$nextTick(() => {
  266. editor.setValue(jsonTxt);
  267. this.$nextTick(() => {
  268. this.fireChange = true;
  269. })
  270. })
  271. },
  272. openOptionsPage: function(event){
  273. event.preventDefault();
  274. event.stopPropagation();
  275. chrome.runtime.openOptionsPage();
  276. },
  277. openDonateModal: function(event){
  278. event.preventDefault();
  279. event.stopPropagation();
  280. chrome.runtime.sendMessage({
  281. type: 'fh-dynamic-any-thing',
  282. thing: 'open-donate-modal',
  283. params: { toolName: 'json-format' }
  284. });
  285. },
  286. setDemo: function () {
  287. let demo = '{"BigIntSupported":995815895020119788889,"date":"20180322","url":"https://www.baidu.com?wd=fehelper","img":"http://gips0.baidu.com/it/u=1490237218,4115737545&fm=3028&app=3028&f=JPEG&fmt=auto?w=1280&h=720","message":"Success !","status":200,"city":"北京","count":632,"data":{"shidu":"34%","pm25":73,"pm10":91,"quality":"良","wendu":"5","ganmao":"极少数敏感人群应减少户外活动","yesterday":{"date":"21日星期三","sunrise":"06:19","high":"高温 11.0℃","low":"低温 1.0℃","sunset":"18:26","aqi":85,"fx":"南风","fl":"<3级","type":"多云","notice":"阴晴之间,谨防紫外线侵扰"},"forecast":[{"date":"22日星期四","sunrise":"06:17","high":"高温 17.0℃","low":"低温 1.0℃","sunset":"18:27","aqi":98,"fx":"西南风","fl":"<3级","type":"晴","notice":"愿你拥有比阳光明媚的心情"},{"date":"23日星期五","sunrise":"06:16","high":"高温 18.0℃","low":"低温 5.0℃","sunset":"18:28","aqi":118,"fx":"无持续风向","fl":"<3级","type":"多云","notice":"阴晴之间,谨防紫外线侵扰"},{"date":"24日星期六","sunrise":"06:14","high":"高温 21.0℃","low":"低温 7.0℃","sunset":"18:29","aqi":52,"fx":"西南风","fl":"<3级","type":"晴","notice":"愿你拥有比阳光明媚的心情"},{"date":"25日星期日","sunrise":"06:13","high":"高温 22.0℃","low":"低温 7.0℃","sunset":"18:30","aqi":71,"fx":"西南风","fl":"<3级","type":"晴","notice":"愿你拥有比阳光明媚的心情"},{"date":"26日星期一","sunrise":"06:11","high":"高温 21.0℃","low":"低温 8.0℃","sunset":"18:31","aqi":97,"fx":"西南风","fl":"<3级","type":"多云","notice":"阴晴之间,谨防紫外线侵扰"}]}}';
  288. editor.setValue(demo);
  289. this.$nextTick(() => {
  290. this.format();
  291. })
  292. },
  293. autoUnpackJsonStringFn: function () {
  294. this.$nextTick(() => {
  295. localStorage.setItem('jsonformat:auto-unpack-json-string', this.autoUnpackJsonString);
  296. this.format();
  297. });
  298. }
  299. }
  300. });
  301. // 新增:递归解包嵌套JSON字符串的函数
  302. function deepParseJSONStrings(obj) {
  303. if (Array.isArray(obj)) {
  304. return obj.map(deepParseJSONStrings);
  305. } else if (typeof obj === 'object' && obj !== null) {
  306. const newObj = {};
  307. for (const key in obj) {
  308. if (!obj.hasOwnProperty(key)) continue;
  309. const val = obj[key];
  310. if (typeof val === 'string') {
  311. try {
  312. const parsed = JSON.parse(val);
  313. // 只递归对象或数组
  314. if (typeof parsed === 'object' && parsed !== null) {
  315. newObj[key] = deepParseJSONStrings(parsed);
  316. continue;
  317. }
  318. } catch (e) {}
  319. }
  320. newObj[key] = deepParseJSONStrings(val);
  321. }
  322. return newObj;
  323. }
  324. return obj;
  325. }