diff --git a/extension/css/content.css b/extension/css/content.css index c4e4b22..7938616 100644 --- a/extension/css/content.css +++ b/extension/css/content.css @@ -39,6 +39,7 @@ flex-shrink: 0; margin-right: 4px; margin-left: 4px; + white-space: pre; } .JF_json-container .json-index { @@ -48,6 +49,7 @@ .JF_json-container .json-value { margin-left: 8px; + white-space: pre; } .JF_json-container .json-number { @@ -215,12 +217,12 @@ body.JF_.JF_dark { justify-content: center; } -.JF_button-wrapper > button:first-child { +.JF_button-wrapper>button:first-child { border-radius: 4px 0 0 4px; left: 1px; } -.JF_button-wrapper > button:nth-child(2) { +.JF_button-wrapper>button:nth-child(2) { border-radius: 0; border-width: 0 1px; border-style: solid; @@ -228,7 +230,7 @@ body.JF_.JF_dark { margin: 0; } -.JF_button-wrapper > button:last-child { +.JF_button-wrapper>button:last-child { border-radius: 0 4px 4px 0; } @@ -329,6 +331,7 @@ body.JF_.JF_dark { display: inline-flex; transition: opacity 0.2s ease; } + .JF_word-wrap { /* enable wrapping like in vs code, break anywhere */ white-space: pre-wrap; @@ -344,6 +347,63 @@ body.JF_.JF_dark { pointer-events: none; display: none; } + +.JF_context_menu { + background-color: #fff !important; + padding: 6px 0; + color: #202124; + font-size: 0.8rem; + position: fixed; + visibility: hidden; + left: -9999px; + top: -9999px; + appearance: none; + border: none; + background: none; + z-index: 100; + border-radius: 8px; + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.12), 0 12px 24px 0 rgba(0, 0, 0, 0.24); +} + +body.JF_dark .JF_context_menu { + background-color: #202124 !important; + color: rgb(232, 234, 237); + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.12), + 0 19px 38px 0 rgba(0, 0, 0, 0.24); +} + +.JF_context_menu .JF_context_menu_item { + padding: 8px 24px; + cursor: pointer; + transition: background-color 100ms ease; +} + +.JF_context_menu .JF_context_menu_item:hover { + background-color: #f1f3f4; +} + +body.JF_dark #JF_context_menu .JF_context_menu_item:hover { + background-color: #2d2f31; +} + +.JF_context_menu .JF_context_menu_item .JF_context_menu_item_shortcut { + font-size: 0.7rem; + background-color: #f1f3f4; + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2); + border: 1px solid rgba(0, 0, 0, 0.2); + padding: 2px 6px; + border-radius: 4px; + margin-left: 8px; + font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; +} + +body.JF_dark #JF_context_menu .JF_context_menu_item .JF_context_menu_item_shortcut { + background-color: #202124; + color: rgb(232, 234, 237); + border: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: 0 1px 0 rgba(255, 255, 255, 0.1); +} + /* Disable what's new popup */ /* #JF_whats_new { position: fixed; diff --git a/extension/js/content.js b/extension/js/content.js index d58b971..8bc57e4 100644 --- a/extension/js/content.js +++ b/extension/js/content.js @@ -33,6 +33,8 @@ SOFTWARE. parsedCode, formattedRawCode, rawCode, + currentView, + contextMenu, tree, isDark = true, isToolbarOpen = false, @@ -44,7 +46,9 @@ SOFTWARE. parsed: "p", formatted_raw: "r", raw: "r", - dark: "d" + dark: "d", + collapse_all: "[", + expand_all: "]", }; let IS_PREPARE_SCRIPT_RUN = false; @@ -132,16 +136,77 @@ SOFTWARE. } - function formatJSON(str) { - var obj, text = str; - try { - obj = JSON.parse(text); - } - catch (e) { - // Not JSON + function setupContextMenu() { + window.addEventListener("contextmenu", function (e) { + if (window.getComputedStyle(contextMenu).visibility === "visible") return false; + e.preventDefault(); + let dimensions = contextMenu.getBoundingClientRect(); + if (e.pageX <= window.innerWidth / 2) { + if (e.pageY <= window.innerHeight / 2) { + contextMenu.style.left = e.pageX + "px"; + contextMenu.style.top = e.pageY + "px"; + } + if (e.pageY > window.innerHeight / 2) { + contextMenu.style.left = e.pageX + "px"; + contextMenu.style.top = (e.pageY - dimensions.height) + "px"; + } + } + if (e.pageX > window.innerWidth / 2) { + if (e.pageY < window.innerHeight / 2) { + contextMenu.style.left = (e.pageX - dimensions.width) + "px"; + contextMenu.style.top = e.pageY + "px"; + } + if (e.pageY > window.innerHeight / 2) { + contextMenu.style.left = (e.pageX - dimensions.width) + "px"; + contextMenu.style.top = (e.pageY - dimensions.height) + "px"; + } + } + toggleContextMenu(true); + }); + window.addEventListener("click", function () { + toggleContextMenu(false); + }); + } + + function setupFormatter(str) { + var code = formatHTML(str); + let sortingFuncton = null + if (options.sortingOrder == "alphabetical") { + // alphabet sorting + sortingFuncton = normalize(); } - if (typeof obj !== 'object' && typeof obj !== 'array') return; - var formated = setupFormatter(JSON.stringify(obj)); + formattedRawCode.innerHTML = + JSON.stringify(JSON.parse(code.replace(/\\u/g, "\u")), sortingFuncton, 2); + + globalThis.code = code; + + let leadingLine = document.createElement('div'); + leadingLine.className = 'line emptyLine'; + leadingLine.textContent = ''; + leadingLine.style = 'margin-left: 0px; height: 18px;'; + formattedRawCode.appendChild(leadingLine); + + rawCode.innerHTML = JSON.stringify(JSON.parse(code.replace(/\\u/g, "\u")), sortingFuncton); + + let leadingLine1 = document.createElement('div'); + leadingLine1.className = 'line emptyLine'; + leadingLine1.textContent = ''; + leadingLine1.style = 'margin-left: 0px; height: 18px;'; + rawCode.appendChild(leadingLine1); + + tree = createTree(JSON.parse(code + .replace(/\\/g, "\\\\") + .replace(/\\\\\"/g, "\\\\\\\""), + sortingFuncton)); + var thme = isDark ? "dark" : "light"; + var renderedCode = render(tree, parsedCode, { theme: thme, string: true }); + expandChildren(tree); + return [renderedCode, JSON.stringify(JSON.parse(code), undefined, 2)]; + } + + function formatJSON(str) { + // JSON Check already done in _() function + var formated = setupFormatter(str); setTimeout(function () { try { var script = document.createElement("script"); @@ -152,16 +217,196 @@ SOFTWARE. }, 100); } catch (err) { - console.log("JSON Formatter: Sorry but you can't access original JSON in console in this page.") + prettyLog("Sorry but you can't access original JSON in console in this page.") } }, 100); } + /** + * DOM manipulation Functions + */ + async function prepareBody() { + document.body.innerHTML = ` + ` + + // Disable What's New Popup + // ${options.whats_new_screen_shown ? '' : + // ``} + `
+
+ +
+ + + +
+
+ +
+
+

+  

+  
+
Collapse All[
+
Expand All]
+
Toggle Dark ModeD
+
Open Parsed ViewP
+
Open Formatted Raw Viewshift + R
+
Open Raw ViewR
+
Toggle ToolbarT
+
+ `; + currentView = options.tab; + + btn_parsed = document.getElementById("open_parsed"); + btn_formatted_raw = document.getElementById("open_formatted_raw"); + btn_raw = document.getElementById("open_raw"); + parsedCode = document.getElementById("parsed"); + formattedRawCode = document.getElementById("formatted_raw"); + rawCode = document.getElementById("raw"); + toolbar = document.getElementById("json_toolbar"); + btn_toolbar = document.getElementById("toggle_toolbar"); + theme_css = document.getElementById("JF_theme"); + contextMenu = document.getElementById("JF_context_menu"); + + // Add Event Listeners to context menu + document.getElementById("JF_context_menu_collapse_all").addEventListener("click", function () { collapseChildren(tree); }); + document.getElementById("JF_context_menu_expand_all").addEventListener("click", function () { expandChildren(tree); }); + document.getElementById("JF_context_menu_dark").addEventListener("click", async function () { await toggleDarkMode(); }); + document.getElementById("JF_context_menu_parsed").addEventListener("click", function () { openView("parsed"); }); + document.getElementById("JF_context_menu_formatted_raw").addEventListener("click", function () { openView("formatted_raw"); }); + document.getElementById("JF_context_menu_raw").addEventListener("click", function () { openView("raw"); }); + document.getElementById("JF_context_menu_toolbar").addEventListener("click", function () { toggleToolbar(); }); + + // Add event listeners to toolbar buttons + btn_parsed.addEventListener("click", function () { + openView("parsed"); + }); + btn_formatted_raw.addEventListener("click", function () { + openView("formatted_raw"); + }); + btn_raw.addEventListener("click", function () { + openView("raw"); + }); + btn_toolbar.addEventListener("click", function () { + toggleToolbar(); + }); + document.getElementById("toggle_dark").addEventListener("click", async function () { + await toggleDarkMode(); + }); + + if (options.colorScheme == "auto") { + let darkbool = window.matchMedia("(prefers-color-scheme: dark)").matches; + await toggleDarkMode(darkbool); + + window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').addEventListener("change", async function (e) { + if (options.colorScheme == "auto") { + let bool = e.matches; + await toggleDarkMode(bool); + } + }); + } + if (options.colorScheme == "dark") await toggleDarkMode(true); + if (options.colorScheme == "light") await toggleDarkMode(false); + toggleWordWrap(); + + // Disable What's New Popup + // window.addEventListener("message", async function (m) { + // if (m.data.type == "JF-close-whats-new") { + // document.getElementById("JF_whats_new").remove(); + // options.whats_new_screen_shown = true; + // await chrome.storage.local.set({ [bucket]: options }); + // } + // }); + + + window.addEventListener("keydown", async (e) => { + if (e.target.tagName === "INPUT" || e.target.isContentEditable) { + return false; + } + if ( + !e.ctrlKey && + !e.altKey && + !e.metaKey && + e.shiftKey + ) { + if (e.key === hotkeys.formatted_raw || e.code === "Key" + hotkeys.formatted_raw.toUpperCase()) { + e.preventDefault(); + openView("formatted_raw"); + } + } + + if ( + !e.ctrlKey && + !e.altKey && + !e.metaKey && + !e.shiftKey + ) { + if (e.key === hotkeys.toolbar || e.code === "Key" + hotkeys.toolbar.toUpperCase()) { + e.preventDefault(); + toggleToolbar(); + } + if (e.key === hotkeys.dark || e.code === "Key" + hotkeys.dark.toUpperCase()) { + e.preventDefault(); + await toggleDarkMode(); + } + if (e.key === hotkeys.parsed || e.code === "Key" + hotkeys.parsed.toUpperCase()) { + e.preventDefault(); + openView("parsed"); + } + if (e.key === hotkeys.raw || e.code === "Key" + hotkeys.raw.toUpperCase()) { + e.preventDefault(); + openView("raw"); + } + if (e.key === hotkeys.collapse_all || e.code === "Key" + hotkeys.collapse_all.toUpperCase()) { + e.preventDefault(); + collapseChildren(tree); + } + if (e.key === hotkeys.expand_all || e.code === "Key" + hotkeys.expand_all.toUpperCase()) { + e.preventDefault(); + expandChildren(tree); + } + } + }); + } + async function _() { var preCode; if (!document || !document.body || !document.body.childNodes || + !document.body.childNodes.length || document.body === null || document.body === undefined || document.body.childNodes === null || @@ -173,7 +418,7 @@ SOFTWARE. ) { let tb = false; try { - JSON.parse(document.body.innerText); + JSON.parse(document.body.textContent); } catch (e) { tb = true; @@ -188,20 +433,20 @@ SOFTWARE. firstEl.hidden = false; }, 1000); if (firstEl.tagName === "PRE" && firstEl.nodeName === "PRE" && firstEl.nodeType === 1) { - preCode = firstEl.innerText; + preCode = firstEl.textContent; } else if (firstEl.tagName === "DIV" && firstEl.nodeName === "DIV" && firstEl.nodeType === 1) { // preventing chrome native UI - if (firstEl.innerText.length == 0) { - preCode = document.getElementsByTagName("pre")[0].innerText + if (firstEl.textContent.length == 0) { + preCode = document.getElementsByTagName("pre")[0].textContent } else { - preCode = firstEl.innerText; + preCode = firstEl.textContent; } } else if (firstEl.tagName === "CODE" && firstEl.nodeName === "CODE" && firstEl.nodeType === 1) { - preCode = firstEl.innerText; + preCode = firstEl.textContent; } else if (firstEl.tagName === undefined && firstEl.nodeName === "#text" && firstEl.nodeType === 3) { preCode = firstEl.nodeValue; @@ -216,14 +461,20 @@ SOFTWARE. jsonLen === 0 ) { firstEl.hidden = false; - console.log("JSON Formatter: JSON too large to format!") + prettyLog("JSON too large to format!") return false; } - var isJSON = false, obj; + var isJSON = false; try { - obj = JSON.parse(preCode); + let obj = JSON.parse(preCode); + let increment = 0; while (typeof (obj) === "string") { obj = JSON.parse(obj); + increment++; + } + // if it can be parsed 2nd time, then original object was wrapped in quotes, some API devs do that for no reason + if (increment > 1) { + preCode = JSON.parse(preCode); } if (typeof (obj) === "number" || typeof (obj) === "boolean" || typeof (obj) === "null" || typeof (obj) === "undefined" || typeof (obj) === "NaN") { firstEl.hidden = false; @@ -249,7 +500,8 @@ SOFTWARE. if (isJSON) { await fetchExtensionSettings(); await prepareBody(); - formatJSON(JSON.stringify(obj)); + setupContextMenu(); + formatJSON(preCode); globalThis.validJSON = true; } } @@ -555,155 +807,30 @@ SOFTWARE. str = str.replace(//gm, ">") return str; } - - /** - * DOM manipulation Functions + /* + escape the following to make then visible and not treated as special char + \" + \\ + \/ + \b + \f + \n + \r + \t + \u */ - async function prepareBody() { - document.body.innerHTML = ` -` + -// Disable What's New Popup -// ${options.whats_new_screen_shown ? '' : -// ``} -`
-
- -
- - - -
-
- -
-
-

-
`;
-
-    btn_parsed = document.getElementById("open_parsed");
-    btn_formatted_raw = document.getElementById("open_formatted_raw");
-    btn_raw = document.getElementById("open_raw");
-    parsedCode = document.getElementById("parsed");
-    formattedRawCode = document.getElementById("formatted_raw");
-    rawCode = document.getElementById("raw");
-    toolbar = document.getElementById("json_toolbar");
-    btn_toolbar = document.getElementById("toggle_toolbar");
-    theme_css = document.getElementById("JF_theme");
-
-    btn_parsed.addEventListener("click", function () {
-      openView("parsed");
-    });
-    btn_formatted_raw.addEventListener("click", function () {
-      openView("formatted_raw");
-    });
-    btn_raw.addEventListener("click", function () {
-      openView("raw");
-    });
-    btn_toolbar.addEventListener("click", function () {
-      toggleToolbar();
-    });
-    document.getElementById("toggle_dark").addEventListener("click", async function () {
-      await toggleDarkMode();
-    });
-
-    if (options.colorScheme == "auto") {
-      let darkbool = window.matchMedia("(prefers-color-scheme: dark)").matches;
-      await toggleDarkMode(darkbool);
-
-      window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').addEventListener("change", async function (e) {
-        if (options.colorScheme == "auto") {
-          let bool = e.matches;
-          await toggleDarkMode(bool);
-        }
-      });
-    }
-    if (options.colorScheme == "dark") await toggleDarkMode(true);
-    if (options.colorScheme == "light") await toggleDarkMode(false);
-    toggleWordWrap();
-
-    // Disable What's New Popup
-    // window.addEventListener("message", async function (m) {
-    //   if (m.data.type == "JF-close-whats-new") {
-    //     document.getElementById("JF_whats_new").remove();
-    //     options.whats_new_screen_shown = true;
-    //     await chrome.storage.local.set({ [bucket]: options });
-    //   }
-    // });
-
-
-    window.addEventListener("keydown", async (e) => {
-      if (e.target.tagName === "INPUT" || e.target.isContentEditable) {
-        return false;
-      }
-      if (
-        !e.ctrlKey &&
-        !e.altKey &&
-        !e.metaKey &&
-        e.shiftKey
-      ) {
-        if (e.key === hotkeys.formatted_raw || e.code === "Key" + hotkeys.formatted_raw.toUpperCase()) {
-          e.preventDefault();
-          openView("formatted_raw");
-        }
-      }
-
-      if (
-        !e.ctrlKey &&
-        !e.altKey &&
-        !e.metaKey &&
-        !e.shiftKey
-      ) {
-        if (e.key === hotkeys.toolbar || e.code === "Key" + hotkeys.toolbar.toUpperCase()) {
-          e.preventDefault();
-          toggleToolbar();
-        }
-        if (e.key === hotkeys.dark || e.code === "Key" + hotkeys.dark.toUpperCase()) {
-          e.preventDefault();
-          await toggleDarkMode();
-        }
-        if (e.key === hotkeys.parsed || e.code === "Key" + hotkeys.parsed.toUpperCase()) {
-          e.preventDefault();
-          openView("parsed");
-        }
-        if (e.key === hotkeys.raw || e.code === "Key" + hotkeys.raw.toUpperCase()) {
-          e.preventDefault();
-          openView("raw");
-        }
-      }
-    });
+  function jsonEscape(json) {
+    return json
+      .replaceAll(/\\/g, "\\\\")
+      .replaceAll(/\\n/g, "\\n")
+      .replaceAll(/\\r/g, "\\r")
+      .replaceAll(/\\t/g, "\\t")
+      .replaceAll(/\\f/g, "\\f")
+      .replaceAll(/\\b/g, "\\b")
+      .replaceAll(/\\u/g, "\\u")
+      .replaceAll(/\\\//g, "\\/");
   }
+
   function normalize(sortingFunction) {
     return function (key, value) {
       if (typeof value === 'object' && !Array.isArray(value) && value !== null) {
@@ -718,47 +845,12 @@ SOFTWARE.
       return value;
     }
   }
-  function setupFormatter(str) {
-    var code;
-    if (typeof (str) == "object") {
-      code = JSON.stringify(str);
-      code = JSON.stringify(JSON.parse(formatHTML(JSON.stringify(code))));
-    }
-    if (typeof (str) == "string") {
-      code = JSON.stringify(JSON.parse(formatHTML(str)));
-    }
-    let sortingFuncton = null
-    if (options.sortingOrder == "alphabetical") {
-      // alphabet sorting
-      sortingFuncton = normalize();
-    }
-    formattedRawCode.innerHTML = JSON.stringify(JSON.parse(code), sortingFuncton, 2);
-
-    let leadingLine = document.createElement('div');
-    leadingLine.className = 'line emptyLine';
-    leadingLine.textContent = '';
-    leadingLine.style = 'margin-left: 0px; height: 18px;';
-    formattedRawCode.appendChild(leadingLine);
-
-    rawCode.innerHTML = JSON.stringify(JSON.parse(code, sortingFuncton));
-
-    let leadingLine1 = document.createElement('div');
-    leadingLine1.className = 'line emptyLine';
-    leadingLine1.textContent = '';
-    leadingLine1.style = 'margin-left: 0px; height: 18px;';
-    rawCode.appendChild(leadingLine1);
-
-    tree = createTree(JSON.parse(code, sortingFuncton));
-    var thme = isDark ? "dark" : "light";
-    var renderedCode = render(tree, parsedCode, { theme: thme, string: true });
-    expandChildren(tree);
-    return [renderedCode, JSON.stringify(JSON.parse(code), undefined, 2)];
-  }
 
   function openView(type) {
     if (type != "parsed" && type != "raw" && type != "formatted_raw") {
       throw new TypeError(type + " is not a valid type!");
     }
+    currentView = type;
     if (type == "parsed") {
       formattedRawCode.hidden = true;
       rawCode.hidden = true;
@@ -784,6 +876,7 @@ SOFTWARE.
       btn_formatted_raw.classList.add("active");
     }
   }
+
   function toggleToolbar(bool) {
     if (bool != undefined) {
       if (bool == false) {
@@ -898,6 +991,45 @@ SOFTWARE.
     }
   }
 
+  function toggleContextMenu(bool, event) {
+    if (bool != undefined) {
+      if (bool == false) {
+        contextMenu.style.left = "-9999px";
+        contextMenu.style.top = "-9999px";
+        contextMenu.style.visibility = "hidden";
+      }
+      else {
+        if (currentView !== "parsed") {
+          document.getElementById("JF_context_menu_collapse_all").hidden = true;
+          document.getElementById("JF_context_menu_expand_all").hidden = true;
+        }
+        else {
+          document.getElementById("JF_context_menu_collapse_all").hidden = false;
+          document.getElementById("JF_context_menu_expand_all").hidden = false;
+        }
+        contextMenu.style.visibility = "visible";
+      }
+    }
+    else {
+      if (contextMenu.hidden) {
+        if (currentView !== "parsed") {
+          document.getElementById("JF_context_menu_collapse_all").hidden = true;
+          document.getElementById("JF_context_menu_expand_all").hidden = true;
+        }
+        else {
+          document.getElementById("JF_context_menu_collapse_all").hidden = false;
+          document.getElementById("JF_context_menu_expand_all").hidden = false;
+        }
+        contextMenu.style.visibility = "visible";
+      }
+      else {
+        contextMenu.style.left = "-9999px";
+        contextMenu.style.top = "-9999px";
+        contextMenu.style.visibility = "hidden";
+      }
+    }
+  }
+
   function isBrowserNativeUIShown(document) {
     let flag = false;
     let hasAHiddenDIVContainingValidJSON = false;
@@ -907,7 +1039,7 @@ SOFTWARE.
     let firstEl = document.body.childNodes[0];
     if (firstEl.tagName === "DIV" && firstEl.nodeName === "DIV" && firstEl.nodeType === 1 && firstEl.hidden) {
       try {
-        JSON.parse(firstEl.innerText);
+        JSON.parse(firstEl.textContent);
         hasAHiddenDIVContainingValidJSON = true;
       }
       catch (e) {
@@ -933,4 +1065,8 @@ SOFTWARE.
     return text;
   }
 
+  function prettyLog(str) {
+    console.log(`%c[JSON Formatter] %c${str}`, "color:purple;font-weight:bold;", "");
+  }
+
 })();
\ No newline at end of file
diff --git a/extension/manifest.json b/extension/manifest.json
index b9c2e8c..636f35a 100644
--- a/extension/manifest.json
+++ b/extension/manifest.json
@@ -55,7 +55,8 @@
         "css/content.css"
       ],
       "run_at": "document_start",
-      "all_frames": true
+      "all_frames": true,
+      "match_origin_as_fallback": true
     }
   ],
   "host_permissions": [
@@ -64,7 +65,8 @@
   ],
   "permissions": [
     "storage",
-    "unlimitedStorage"
+    "unlimitedStorage",
+    "contextMenus"
   ],
   "web_accessible_resources": [
     {