diff --git a/.gitignore b/.gitignore index 85ce8a5..9690fd9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,58 +1,6 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release # Dependency directories node_modules/ -jspm_packages/ - -# Typescript v1 declaration files -typings/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# dotenv environment variables file -.env # ide .idea @@ -60,8 +8,9 @@ typings/ # os .DS_Store -*.zip -*.js.map - # typescript output .ts-built + +# application files not for git +*.js.map +*.zip diff --git a/.prettierignore b/.prettierignore index 29a9314..6cba1e3 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,3 @@ -/src/js/bundle +/bundle/js .ts-built *.min.js diff --git a/.prettierrc.json b/.prettierrc.json index 544138b..0a72520 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,3 +1,6 @@ { + "trailingComma": "es5", + "tabWidth": 2, + "semi": true, "singleQuote": true } diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..c83e263 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["esbenp.prettier-vscode"] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b46c099 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "json.schemaDownload.enable": true +} \ No newline at end of file diff --git a/README.md b/README.md index 9e30ca9..08f4c7b 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,7 @@ [![console.diff()](https://storage.googleapis.com/web-dev-uploads/image/WlD8wC6g8khYWPJUsQceQkhXSlv1/tbyBjqi7Zu733AAKA5n4.png)](https://chrome.google.com/webstore/detail/jsdiff-devtool/iefeamoljhdcpigpnpggeiiabpnpgonb) -Chrome devtools extension intended to display result of deep in-memory object -comparisons with the help of dedicated console commands. +Chrome extension to compare objects in memory with console.diff(old, new) devtools function.
Screenshots @@ -23,23 +22,54 @@ comparisons with the help of dedicated console commands. ### Features -- compare objects between multiple [sub]domains, chrome tabs, or single page reloads -- function code included in comparison result in form of a string, may help to see if it was altered -- document, dom-elements and other non-serializable objects are filtered-out from the results -- self recurring references displayed only once, the rest of occurrences are filtered-out -- basic integration with search functionality within devtools - - if search query contains upper-case letter - the search will be case-sensitive +- Symple user interface: -### Limitations and workarounds + - Button to hide/show unchanged properties. + - Button to copy changed properties in format of `jsondiffpatch` diff object. + - Button to clear current result. + - Indicator of the last update time. + - Indicator of a fatal error (out of storage memory). -- some instances of objects may cause exception during preparations for comparison - - try to narrow compared contexts - - if it's some Browser API that causes an exception and not a framework, consider opening an issue, - so it will be possible to solve it on a permanent basis -- while paused in debug mode, JSDiff panel won't reflect the result until runtime is resumed ([#10][i10]) +- Compare objects between multiple [sub]domains, chrome tabs, or single page reloads. + + - JSDiff devtools panel reflects current state of comparison, regardless the tab[s] it was opened from. + +- Basic integration with search functionality within devtools: + + - If search query contains at least one upper-case letter - the search will be case-sensitive. + +- Using `console.diff` functions from within online code editors like: [codesandbox.io](https://codesandbox.io), [coderpad.io](https://coderpad.io), [flems.io](https://flems.io), [codepen.io](https://codepen.io), [jsfiddle.net](https://jsfiddle.net). + +- Functions are included in comparison result in order to detect possible alterations, in form of a string combined from a function name (if present) and a SHA-256 hash of a `function.toString()` body. Native functions are shown as silmply as `ƒ⟪native⟫`. + +- Some DOM objects like Document or Element are not worth to be shown entirely, since that is not the purpose of this extension. So if they are present anywhere, they are serialized as `0x####: ⟪unserializable⟫`. + +- Object, Array, Map, Set - serialized only once and the rest of their ocurrances are mentioned with unique reference like: `0x####: {♻️}`, `0x####: [♻️]`, `0x####: Map{♻️}`, `0x####: Set[♻️]` respectivly. + + - Map keys, unless they are primitive types, serialized by their pseudo ids. + +- Symbols serialized with his pseudo id like: `0x####: Symbol(name)`. + +- Serialization of numerics like `+/-Infinity`, `NaN`, `BigInt`, or `undefined` serialized like: `Number⟪Infinity⟫`, `Number⟪NaN⟫`, `BigInt⟪#⟫`, `⟪undefined⟫` respectivly. + +- Failsafe serialization of objects having security issues while accessing their properties. + +- Failsefe serialization of objects having `toJSON()` function (when instead of serialization of all object properties, - only toJSON() return value is serialized, similar to the way native `JSON.strigify()` works). + +### Legend + +- Pseudo id, assigned to non-primitive data types, used in order to detect reference recurrences and, in case of Symbols - symbol uniqueness. Id for an object shown in the output only if it seen more than once. It being assigned in the scope of serialization of a high level argument instance, while comparing left or right side; that means if some object, having id 0x0001 on the left side, is not guarantied to have same id on the right side. + +### Limitations + +- While paused in debug mode, JSDiff panel won't reflect the result until runtime is resumed (see [#10][i10]). [i10]: https://github.com/zendive/jsdiff/issues/10 +- Compared objects, after being serialized, and stored in `chrome.storage.local` wich has 10MB limit. + +- Will not work on `file:///` prorocol and https://chrome.google.com/webstore site. + ### API - **console.diff(left, right)** - compare left and right arguments @@ -72,25 +102,47 @@ console.diffLeft(Date.now()); console.diffRight(Date.now()); ``` +- **console.diff\_(\*)** - deprecated, left for backward compatibility, uses `nativeClone` based of JSON.parse(JSON.stringify(...)) serialization method + ### Usage basics Historically, left side represents the old state and right side the new state. - Things that are present on the left side but missing on the right side are colour-coded as red (old). + - Things that are missing on the left side but present on the right side are colour-coded as green (new). -To track changes of the same variable in timed manner you can push it with `diffPush` or `diff` -with a single argument, that will shift objects from right to left, showing differences with previous push state. +- To track changes of the same variable in timed manner you can push it with `diffPush` or `diff` with a single argument, - that will shift objects from right to left, showing differences with previous push state. ### How it works -- `jsdiff-devtools.js` registers devtools panel - - injects `console.diff` commands into inspected window's console interface - - each function clones arguments and sends them via `postMessage` to `jsdiff-proxy.js` in `jsdiff-console-to-proxy` message - - injects `jsdiff-proxy.js` that listens on window `jsdiff-console-to-proxy` message and sends it further to chrome runtime in `jsdiff-proxy-to-devtools` message - - listens on `jsdiff-proxy-to-devtools` and prepares payload for `view/panel.vue` and sends it with `jsdiff-devtools-to-panel-compare` message - - when user invokes devtools search command - informs `view/panel.vue` with `jsdiff-devtools-to-panel-search` message -- when `view/panel.vue` is visible in devtools - - reflects result of last compare request - - listens on `jsdiff-devtools-to-panel-compare` requests - - listens on `jsdiff-devtools-to-panel-search` and tries to find query in DOM +- `manifest.json` injects content scripts to each visited site (except for chrome web store site and google-protected alike): + - `jsdiff-console.ts` as [MAIN](https://developer.chrome.com/docs/extensions/reference/scripting/#type-ExecutionWorld) world (has access to the target site memory) + - sends messages to `jsdiff-proxy.ts`. + - `jsdiff-proxy.ts` as `ISOLATED` world (has access to the chrome runtime) + - stores data from `jsdiff-console.ts` in `chrome.storage.local` and sends runtime messages to `panel.vue`. +- `jsdiff-devtools.ts` registers `panel.vue` as a JSDiff devtools panel that reads current state of `chorme.storage.local` and listens to incomming `chrome.runtime` mesages from `jsdiff-proxy.ts`. + +### How to build + +- requires npm/nodejs +- requires pnpm `npm i -g pnpm` + +```sh +pnpm i +pnpm dev # local development +pnpm zip # make extension.zip +``` + +### Protection + +- How to protect your site from this extension: + - Well, tests show that even `Content-Security-Policy: default-src 'none';` header won't prevent injection of extension content-scripts... + - In general, you can incapacitate console functions: + ```js + for (const prop in console) { + if (typeof console[prop] === 'function' && prop !== 'error') { + console[prop] = function noop() {}; + } + } + ``` diff --git a/src/img/panel-icon128.png b/bundle/img/panel-icon128.png similarity index 100% rename from src/img/panel-icon128.png rename to bundle/img/panel-icon128.png diff --git a/src/img/panel-icon28.png b/bundle/img/panel-icon28.png similarity index 100% rename from src/img/panel-icon28.png rename to bundle/img/panel-icon28.png diff --git a/src/img/panel-icon64.png b/bundle/img/panel-icon64.png similarity index 100% rename from src/img/panel-icon64.png rename to bundle/img/panel-icon64.png diff --git a/bundle/js/jsdiff-console.js b/bundle/js/jsdiff-console.js new file mode 100644 index 0000000..434dc59 --- /dev/null +++ b/bundle/js/jsdiff-console.js @@ -0,0 +1 @@ +(()=>{"use strict";const t={EMPTY:"⟪empty⟫",UNDEFINED:"⟪undefined⟫",NULL:"⟪null⟫",NATIVE_FUNCTION:"ƒ⟪native⟫",EXCEPTION_FALLBACK:"⁉️ ⟪exception⟫",EXCEPTION:t=>`⁉️ ⟪${t}⟫`,RECURRING_ARRAY:t=>`0x${t}: [♻️]`,RECURRING_OBJECT:t=>`0x${t}: {♻️}`,RECURRING_SET:t=>`0x${t}: Set[♻️]`,RECURRING_MAP:t=>`0x${t}: Map{♻️}`,UNSERIALIZABLE:t=>`0x${t}: ⟪unserializable⟫`,SYMBOL:(t,n)=>`0x${n}: ${t}`,FUNCTION:(t,n)=>`ƒ${t?` ${t}`:""}⟪${n}⟫`,NUMERIC:t=>"bigint"==typeof t?`BigInt⟪${t}⟫`:`Number⟪${t}⟫`};var n,e,o,i,r=function(t,n,e,o,i){if("m"===o)throw new TypeError("Private method is not writable");if("a"===o&&!i)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof n?t!==n||!i:!n.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===o?i.call(t,e):i?i.value=e:n.set(t,e),e},a=function(t,n,e,o){if("a"===e&&!o)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof n?t!==n||!o:!n.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===e?o:"a"===e?o.call(t):o?o.value:n.get(t)};class s{constructor(){n.add(this),e.set(this,void 0),o.set(this,0),r(this,e,new Map,"f")}clear(){a(this,e,"f").clear()}lookup(t,s){var c;let f=a(this,e,"f").get(t);if(!f){r(this,o,(c=a(this,o,"f"),++c),"f");const u=a(this,n,"m",i).call(this,a(this,o,"f"));f={name:A(t)?s(t.toString(),u):s(u),seen:!1},a(this,e,"f").set(t,f)}return f}}async function c(n,e){try{window.postMessage({source:"jsdiff-console-to-proxy-inprogress",on:!0},window.location.origin);for(const o of["push","left","right"])if(Reflect.has(e,o)){const i=e[o];e[o]=void 0===i?t.UNDEFINED:null===i?t.NULL:await n(i)}window.postMessage({source:"jsdiff-console-to-proxy-compare",payload:e},window.location.origin)}catch(t){console.error("console.diff()",t),window.postMessage({source:"jsdiff-console-to-proxy-inprogress",on:!1},window.location.origin)}}async function f(t){let n=new Set;const e=JSON.parse(JSON.stringify(t,R.bind(null,n)));return n.clear(),n=null,e}async function u(t){let n=new s;const e=await l(n,t);return n.clear(),n=null,e}async function l(n,e){let o=e;if(g(e)){const{name:i}=n.lookup(e,t.UNSERIALIZABLE);o=i}else if(m(e))o=await E(e);else if(A(e)){const{name:i}=n.lookup(e,t.SYMBOL);o=i}else d(e)?o=await w(n,e,t.RECURRING_ARRAY):h(e)?o=await w(n,e,t.RECURRING_SET):I(e)?o=await async function(n,e){const o=n.lookup(e,t.RECURRING_MAP);let i;if(o.seen)i=o.name;else{o.seen=!0;const t={};for(const[o,i]of e){const e=await y(n,o),r=await l(n,i);t[e]=r}i=t}return i}(n,e):U(e)?o=await async function(n,e){const o=n.lookup(e,t.RECURRING_OBJECT);let i;if(o.seen)i=o.name;else if(o.seen=!0,function(t){let n;try{n=null!==t&&"object"==typeof t&&"toJSON"in t&&"function"==typeof t.toJSON}catch(t){n=!1}return n}(e)){const t=function(t){let n;try{n=t.toJSON()}catch(t){n=N(t)}return n}(e);i=await l(n,t)}else{const o={},r=Reflect.ownKeys(e);for(const i of r){let r,a;if(A(i)){const{name:e}=n.lookup(i,t.SYMBOL);r=e}else r=i;try{a=await l(n,e[i])}catch(t){a=N(t)}o[r]=a}i=o}return i}(n,e):p(e)?o=t.NUMERIC(e):void 0===e&&(o=t.UNDEFINED);return o}function p(t){return"bigint"==typeof t||Number.isNaN(t)||t===-1/0||t===1/0}async function w(t,n,e){const o=t.lookup(n,e);let i;if(o.seen)i=o.name;else{o.seen=!0;const e=[];for(const o of n)e.push(await l(t,o));i=e}return i}async function y(n,e){let o;if(g(e)){const{name:i}=n.lookup(e,t.UNSERIALIZABLE);o=i}else if(m(e))o=await E(e);else if(A(e)){const{name:i}=n.lookup(e,t.SYMBOL);o=i}else if(d(e)){const{name:i}=n.lookup(e,t.RECURRING_ARRAY);o=i}else if(h(e)){const{name:i}=n.lookup(e,t.RECURRING_SET);o=i}else if(I(e)){const{name:i}=n.lookup(e,t.RECURRING_MAP);o=i}else if(U(e)){const{name:i}=n.lookup(e,t.RECURRING_OBJECT);o=i}else o=p(e)?t.NUMERIC(e):void 0===e?t.UNDEFINED:String(e);return o}async function E(n){const e=n.toString();if(e.endsWith("{ [native code] }"))return t.NATIVE_FUNCTION;{const o=await async function(t){const n=(new TextEncoder).encode(t),e=await window.crypto.subtle.digest("SHA-256",n);return Array.from(new Uint8Array(e)).map((t=>t.toString(16).padStart(2,"0").toUpperCase())).join("")}(e);return t.FUNCTION(n.name,o)}}function N(n){return"function"==typeof n?.toString?t.EXCEPTION(n.toString()):t.EXCEPTION_FALLBACK}function R(t,n,e){try{if(g(e))return;if(m(e))return e.toString();if(U(e)){if(t.has(e))return;t.add(e)}return e}catch(t){return N(t)}}function d(t){return t instanceof Array||t instanceof Uint8Array||t instanceof Uint8ClampedArray||t instanceof Uint16Array||t instanceof Uint32Array||t instanceof BigUint64Array||t instanceof Int8Array||t instanceof Int16Array||t instanceof Int32Array||t instanceof BigInt64Array||t instanceof Float32Array||t instanceof Float64Array}function m(t){return"function"==typeof t&&"toString"in t&&"function"==typeof t.toString}function h(t){return t instanceof Set}function I(t){return t instanceof Map}function g(t){return t instanceof Element||t instanceof Document}function A(t){return"symbol"==typeof t}function U(t){return null!==t&&"object"==typeof t||t instanceof Object}e=new WeakMap,o=new WeakMap,n=new WeakSet,i=function(t){return t.toString(16).padStart(4,"0")},Object.assign(console,{diff:(...t)=>{c(u,1===t.length?{push:t[0],timestamp:Date.now()}:{left:t[0],right:t[1],timestamp:Date.now()})},diffLeft:t=>{c(u,{left:t,timestamp:Date.now()})},diffRight:t=>{c(u,{right:t,timestamp:Date.now()})},diffPush:t=>{c(u,{push:t,timestamp:Date.now()})},diff_:(...t)=>{c(f,1===t.length?{push:t[0],timestamp:Date.now()}:{left:t[0],right:t[1],timestamp:Date.now()})}}),console.debug("✚ console.diff()")})(); \ No newline at end of file diff --git a/bundle/js/jsdiff-devtools.js b/bundle/js/jsdiff-devtools.js new file mode 100644 index 0000000..89a6126 --- /dev/null +++ b/bundle/js/jsdiff-devtools.js @@ -0,0 +1 @@ +(()=>{"use strict";null!==chrome.devtools.inspectedWindow.tabId&&chrome.devtools.panels.create("JSDiff","/bundle/img/panel-icon28.png","/bundle/jsdiff-panel.html",(e=>{e.onSearch.addListener((async(e,s)=>{await chrome.runtime.sendMessage({source:"jsdiff-devtools-to-panel-search",params:{cmd:e,query:s}})}))}))})(); \ No newline at end of file diff --git a/bundle/js/jsdiff-panel.js b/bundle/js/jsdiff-panel.js new file mode 100644 index 0000000..9dbc32d --- /dev/null +++ b/bundle/js/jsdiff-panel.js @@ -0,0 +1 @@ +(()=>{var e={825:(e,t,n)=>{"use strict";n.d(t,{Z:()=>a});var r=n(916),i=n.n(r),o=n(282),s=n.n(o)()(i());s.push([e.id,'.jsondiffpatch-delta{font-family:"Bitstream Vera Sans Mono","DejaVu Sans Mono",Monaco,Courier,monospace;font-size:12px;margin:0;padding:0 0 0 12px;display:inline-block}.jsondiffpatch-delta pre{font-family:"Bitstream Vera Sans Mono","DejaVu Sans Mono",Monaco,Courier,monospace;font-size:12px;margin:0;padding:0;display:inline-block}ul.jsondiffpatch-delta{list-style-type:none;padding:0 0 0 20px;margin:0}.jsondiffpatch-delta ul{list-style-type:none;padding:0 0 0 20px;margin:0}.jsondiffpatch-added .jsondiffpatch-property-name,.jsondiffpatch-added .jsondiffpatch-value pre,.jsondiffpatch-modified .jsondiffpatch-right-value pre,.jsondiffpatch-textdiff-added{background:#bfb}.jsondiffpatch-deleted .jsondiffpatch-property-name,.jsondiffpatch-deleted pre,.jsondiffpatch-modified .jsondiffpatch-left-value pre,.jsondiffpatch-textdiff-deleted{background:#fbb;text-decoration:line-through}.jsondiffpatch-unchanged,.jsondiffpatch-movedestination{color:gray}.jsondiffpatch-unchanged,.jsondiffpatch-movedestination>.jsondiffpatch-value{transition:all .5s;-webkit-transition:all .5s;overflow-y:hidden}.jsondiffpatch-unchanged-showing .jsondiffpatch-unchanged,.jsondiffpatch-unchanged-showing .jsondiffpatch-movedestination>.jsondiffpatch-value{max-height:100px}.jsondiffpatch-unchanged-hidden .jsondiffpatch-unchanged,.jsondiffpatch-unchanged-hidden .jsondiffpatch-movedestination>.jsondiffpatch-value{max-height:0}.jsondiffpatch-unchanged-hiding .jsondiffpatch-movedestination>.jsondiffpatch-value,.jsondiffpatch-unchanged-hidden .jsondiffpatch-movedestination>.jsondiffpatch-value{display:block}.jsondiffpatch-unchanged-visible .jsondiffpatch-unchanged,.jsondiffpatch-unchanged-visible .jsondiffpatch-movedestination>.jsondiffpatch-value{max-height:100px}.jsondiffpatch-unchanged-hiding .jsondiffpatch-unchanged,.jsondiffpatch-unchanged-hiding .jsondiffpatch-movedestination>.jsondiffpatch-value{max-height:0}.jsondiffpatch-unchanged-showing .jsondiffpatch-arrow,.jsondiffpatch-unchanged-hiding .jsondiffpatch-arrow{display:none}.jsondiffpatch-value{display:inline-block}.jsondiffpatch-property-name{display:inline-block;padding-right:5px;vertical-align:top}.jsondiffpatch-property-name:after{content:": "}.jsondiffpatch-child-node-type-array>.jsondiffpatch-property-name:after{content:": ["}.jsondiffpatch-child-node-type-array:after{content:"],"}div.jsondiffpatch-child-node-type-array:before{content:"["}div.jsondiffpatch-child-node-type-array:after{content:"]"}.jsondiffpatch-child-node-type-object>.jsondiffpatch-property-name:after{content:": {"}.jsondiffpatch-child-node-type-object:after{content:"},"}div.jsondiffpatch-child-node-type-object:before{content:"{"}div.jsondiffpatch-child-node-type-object:after{content:"}"}.jsondiffpatch-value pre:after{content:","}li:last-child>.jsondiffpatch-value pre:after,.jsondiffpatch-modified>.jsondiffpatch-left-value pre:after{content:""}.jsondiffpatch-modified .jsondiffpatch-value{display:inline-block}.jsondiffpatch-modified .jsondiffpatch-right-value{margin-left:5px}.jsondiffpatch-moved .jsondiffpatch-value{display:none}.jsondiffpatch-moved .jsondiffpatch-moved-destination{display:inline-block;background:#ffb;color:#888}.jsondiffpatch-moved .jsondiffpatch-moved-destination:before{content:" => "}ul.jsondiffpatch-textdiff{padding:0}.jsondiffpatch-textdiff-location{color:#bbb;display:inline-block;min-width:60px}.jsondiffpatch-textdiff-line{display:inline-block}.jsondiffpatch-textdiff-line-number:after{content:","}.jsondiffpatch-error{background:red;color:#fff;font-weight:bold}',""]);const a=s},348:(e,t,n)=>{"use strict";n.d(t,{Z:()=>a});var r=n(916),i=n.n(r),o=n(282),s=n.n(o)()(i());s.push([e.id,":root{--colour-background: #fff;--colour-text: #000;--colour-found: 0, 191, 255;--height-header: 1.625rem}body{margin:0;padding:0}.jsdiff-panel{height:100vh;background-color:var(--colour-background);color:var(--colour-text)}.jsdiff-panel .btn{height:var(--height-header);cursor:pointer;border:none;border-radius:0;outline:none;background-color:rgba(0,0,0,.03)}.jsdiff-panel .btn:hover{background-color:rgba(0,0,0,.3)}.jsdiff-panel .-header{border-bottom:1px solid #bbb;box-shadow:1px 2px 5px #bbb;display:flex;align-items:center;height:var(--height-header);margin-bottom:12px;min-width:512px;user-select:none}.jsdiff-panel .-header .-toolbox{display:flex;justify-content:center;align-items:center;padding-left:10px}.jsdiff-panel .-header .-toolbox .btn{margin:0 2px}.jsdiff-panel .-header .-toolbox .-last-updated{cursor:default;margin-left:10px;color:#bbb}.jsdiff-panel .-header .-toolbox .-last-updated .-value{font-weight:bold}.jsdiff-panel .-header .-last-error{display:flex;justify-content:center;align-items:center;padding-left:10px;color:#b62121}.jsdiff-panel .-header .-badge{position:fixed;top:0;right:0;display:flex;flex-direction:column;align-items:center;padding:4px 4px}.jsdiff-panel .-header .-badge .-version{font-family:monospace}.jsdiff-panel .-header .-badge .-icon img{width:32px}.jsdiff-panel .-center{margin:0 auto;text-align:center}.jsdiff-panel .-match{display:flex;align-items:center;height:100%}.jsdiff-panel .-match .-center{font-size:26px;color:#bbb}.jsdiff-panel .-empty{display:flex;height:calc(100vh - var(--height-header));justify-content:center;align-items:center}.jsdiff-panel .-empty .-links{margin-top:16px;font-size:11px}.jsdiff-panel .-empty .-center{font-size:26px;color:#bbb}.jsdiff-panel .-delta{padding-top:10px}.jsdiff-panel .-delta .jsdiff-found{outline:1px solid rgba(var(--colour-found), 0.6);outline-offset:-1px}.jsdiff-panel .-delta .jsdiff-found.jsdiff-found-this{outline:2px solid rgb(var(--colour-found));outline-offset:-2px;animation:found_this 1s infinite}@keyframes found_this{0%{background-color:rgba(0,0,0,0)}50%{background-color:rgba(var(--colour-found), 0.2)}}",""]);const a=s},697:(e,t,n)=>{"use strict";n.d(t,{Z:()=>a});var r=n(916),i=n.n(r),o=n(282),s=n.n(o)()(i());s.push([e.id,".progress-indicator[data-v-28e0d635]{background-color:rgba(0,0,0,0);height:2px;width:100%;overflow:hidden;position:fixed;top:0;display:block}.progress-indicator>.-eye[data-v-28e0d635]{background-image:linear-gradient(to right, rgba(179, 7, 247, 0) 15%, rgb(241, 14, 14) 50%, rgba(179, 7, 247, 0) 85%);color:rgba(0,0,0,0);height:100%;width:20%;animation:wt-scanning-eye-animation-28e0d635 1s linear infinite alternate,wt-scanning-eye-animation-blink-28e0d635 1.25s linear infinite alternate}@keyframes wt-scanning-eye-animation-28e0d635{from{transform:translateX(-100%)}to{transform:translateX(500%)}}@keyframes wt-scanning-eye-animation-blink-28e0d635{0%{filter:hue-rotate(0deg)}80%{filter:hue-rotate(0deg)}100%{filter:hue-rotate(360deg)}}",""]);const a=s},282:e=>{"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n="",r=void 0!==t[5];return t[4]&&(n+="@supports (".concat(t[4],") {")),t[2]&&(n+="@media ".concat(t[2]," {")),r&&(n+="@layer".concat(t[5].length>0?" ".concat(t[5]):""," {")),n+=e(t),r&&(n+="}"),t[2]&&(n+="}"),t[4]&&(n+="}"),n})).join("")},t.i=function(e,n,r,i,o){"string"==typeof e&&(e=[[null,e,void 0]]);var s={};if(r)for(var a=0;a0?" ".concat(f[5]):""," {").concat(f[1],"}")),f[5]=o),n&&(f[2]?(f[1]="@media ".concat(f[2]," {").concat(f[1],"}"),f[2]=n):f[2]=n),i&&(f[4]?(f[1]="@supports (".concat(f[4],") {").concat(f[1],"}"),f[4]=i):f[4]="".concat(i)),t.push(f))}},t}},916:e=>{"use strict";e.exports=function(e){return e[1]}},931:()=>{},908:function(e,t,n){!function(e,t){"use strict";t=t&&t.hasOwnProperty("default")?t.default:t;var n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},r=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},i=function(){function e(e,t){for(var n=0;ne[i-1][o]?--o:--i;return s},R={get:function(e,t,n,r){var i=r||{},o=M(e,t,n||O,i),s=T(o,e,t,i);return"string"==typeof e&&"string"==typeof t&&(s.sequence=s.sequence.join("")),s}},P=3,N="function"==typeof Array.isArray?Array.isArray:function(e){return e instanceof Array},F="function"==typeof Array.prototype.indexOf?function(e,t){return e.indexOf(t)}:function(e,t){for(var n=e.length,r=0;r0&&f>0&&!t.objectHash&&"boolean"!=typeof t.matchByPosition&&(t.matchByPosition=!I(a,l,c,f));n0)for(var j=0;j<_;j++)if(D(h,p,(o=m[j])-n,i-n,t)){d["_"+o].splice(1,2,i,P),b||(d["_"+o][0]=""),s=i,u=new g(e.left[o],e.right[s]),e.push(u,s),m.splice(j,1),w=!0;break}w||(d[i]=[l[i]])}else o=v.indices1[x]+n,s=v.indices2[x]+n,u=new g(e.left[o],e.right[s]),e.push(u,s)}e.setResult(d).exit()}else{for(d=d||{_t:"a"},i=n;i=0;t--){var l=r["_"+(n=o[t])],c=i.splice(n,1)[0];l[2]===P&&s.push({index:l[1],value:c})}var f=(s=s.sort(V.numericallyBy("index"))).length;for(t=0;t0)for(t=0;tr?r++:s>=r&&at.length?e:t,c=e.length>t.length?t:e,f=l.indexOf(c);if(-1!=f)return a=[[r,l.substring(0,f)],[i,c],[r,l.substring(f+c.length)]],e.length>t.length&&(a[0][0]=a[2][0]=n),a;if(1==c.length)return[[n,e],[r,t]];var u=this.diff_halfMatch_(e,t);if(u){var d=u[0],h=u[1],p=u[2],v=u[3],g=u[4],m=this.diff_main(d,p,o,s),y=this.diff_main(h,v,o,s);return m.concat([[i,g]],y)}return o&&e.length>100&&t.length>100?this.diff_lineMode_(e,t,s):this.diff_bisect_(e,t,s)},t.prototype.diff_lineMode_=function(e,t,o){e=(h=this.diff_linesToChars_(e,t)).chars1,t=h.chars2;var s=h.lineArray,a=this.diff_main(e,t,!1,o);this.diff_charsToLines_(a,s),this.diff_cleanupSemantic(a),a.push([i,""]);for(var l=0,c=0,f=0,u="",d="";l=1&&f>=1){a.splice(l-c-f,c+f),l=l-c-f;for(var h,p=(h=this.diff_main(u,d,!1,o)).length-1;p>=0;p--)a.splice(l,0,h[p]);l+=h.length}f=0,c=0,u="",d=""}l++}return a.pop(),a},t.prototype.diff_bisect_=function(e,t,i){for(var o=e.length,s=t.length,a=Math.ceil((o+s)/2),l=a,c=2*a,f=new Array(c),u=new Array(c),d=0;di);b++){for(var _=-b+v;_<=b-g;_+=2){for(var x=l+_,w=(A=_==-b||_!=b&&f[x-1]o)g+=2;else if(w>s)v+=2;else if(p&&(C=l+h-_)>=0&&C=(k=o-u[C]))return this.diff_bisectSplit_(e,t,A,w,i)}for(var j=-b+m;j<=b-y;j+=2){for(var k,C=l+j,E=(k=j==-b||j!=b&&u[C-1]o)y+=2;else if(E>s)m+=2;else if(!p){var A;if((x=l+h-j)>=0&&x=(k=o-k))return this.diff_bisectSplit_(e,t,A,w,i)}}}return[[n,e],[r,t]]},t.prototype.diff_bisectSplit_=function(e,t,n,r,i){var o=e.substring(0,n),s=t.substring(0,r),a=e.substring(n),l=t.substring(r),c=this.diff_main(o,s,!1,i),f=this.diff_main(a,l,!1,i);return c.concat(f)},t.prototype.diff_linesToChars_=function(e,t){var n=[],r={};function i(e){for(var t="",i=0,o=-1,s=n.length;or?e=e.substring(n-r):nt.length?e:t,r=e.length>t.length?t:e;if(n.length<4||2*r.length=e.length?[r,o,s,a,f]:null}var s,a,l,c,f,u=o(n,r,Math.ceil(n.length/4)),d=o(n,r,Math.ceil(n.length/2));return u||d?(s=d?u&&u[4].length>d[4].length?u:d:u,e.length>t.length?(a=s[0],l=s[1],c=s[2],f=s[3]):(c=s[0],f=s[1],a=s[2],l=s[3]),[a,l,c,f,s[4]]):null},t.prototype.diff_cleanupSemantic=function(e){for(var t=!1,o=[],s=0,a=null,l=0,c=0,f=0,u=0,d=0;l0?o[s-1]:-1,c=0,f=0,u=0,d=0,a=null,t=!0)),l++;for(t&&this.diff_cleanupMerge(e),this.diff_cleanupSemanticLossless(e),l=1;l=g?(v>=h.length/2||v>=p.length/2)&&(e.splice(l,0,[i,p.substring(0,v)]),e[l-1][1]=h.substring(0,h.length-v),e[l+1][1]=p.substring(v),l++):(g>=h.length/2||g>=p.length/2)&&(e.splice(l,0,[i,h.substring(0,g)]),e[l-1][0]=r,e[l-1][1]=p.substring(0,p.length-g),e[l+1][0]=n,e[l+1][1]=h.substring(g),l++),l++}l++}},t.prototype.diff_cleanupSemanticLossless=function(e){function n(e,n){if(!e||!n)return 6;var r=e.charAt(e.length-1),i=n.charAt(0),o=r.match(t.nonAlphaNumericRegex_),s=i.match(t.nonAlphaNumericRegex_),a=o&&r.match(t.whitespaceRegex_),l=s&&i.match(t.whitespaceRegex_),c=a&&r.match(t.linebreakRegex_),f=l&&i.match(t.linebreakRegex_),u=c&&e.match(t.blanklineEndRegex_),d=f&&n.match(t.blanklineStartRegex_);return u||d?5:c||f?4:o&&!a&&l?3:a||l?2:o||s?1:0}for(var r=1;r=h&&(h=p,f=o,u=s,d=a)}e[r-1][1]!=f&&(f?e[r-1][1]=f:(e.splice(r-1,1),r--),e[r][1]=u,d?e[r+1][1]=d:(e.splice(r+1,1),r--))}r++}},t.nonAlphaNumericRegex_=/[^a-zA-Z0-9]/,t.whitespaceRegex_=/\s/,t.linebreakRegex_=/[\r\n]/,t.blanklineEndRegex_=/\n\r?\n$/,t.blanklineStartRegex_=/^\r?\n\r?\n/,t.prototype.diff_cleanupEfficiency=function(e){for(var t=!1,o=[],s=0,a=null,l=0,c=!1,f=!1,u=!1,d=!1;l0?o[s-1]:-1,u=d=!1),t=!0)),l++;t&&this.diff_cleanupMerge(e)},t.prototype.diff_cleanupMerge=function(e){e.push([i,""]);for(var t,o=0,s=0,a=0,l="",c="";o1?(0!==s&&0!==a&&(0!==(t=this.diff_commonPrefix(c,l))&&(o-s-a>0&&e[o-s-a-1][0]==i?e[o-s-a-1][1]+=c.substring(0,t):(e.splice(0,0,[i,c.substring(0,t)]),o++),c=c.substring(t),l=l.substring(t)),0!==(t=this.diff_commonSuffix(c,l))&&(e[o][1]=c.substring(c.length-t)+e[o][1],c=c.substring(0,c.length-t),l=l.substring(0,l.length-t))),0===s?e.splice(o-a,s+a,[r,c]):0===a?e.splice(o-s,s+a,[n,l]):e.splice(o-s-a,s+a,[n,l],[r,c]),o=o-s-a+(s?1:0)+(a?1:0)+1):0!==o&&e[o-1][0]==i?(e[o-1][1]+=e[o][1],e.splice(o,1)):o++,a=0,s=0,l="",c=""}""===e[e.length-1][1]&&e.pop();var f=!1;for(o=1;ot));i++)a=o,l=s;return e.length!=i&&e[i][0]===n?l:l+(t-a)},t.prototype.diff_prettyHtml=function(e){for(var t=[],o=/&/g,s=//g,l=/\n/g,c=0;c");switch(f){case r:t[c]=''+u+"";break;case n:t[c]=''+u+"";break;case i:t[c]=""+u+""}}return t.join("")},t.prototype.diff_text1=function(e){for(var t=[],n=0;nthis.Match_MaxBits)throw new Error("Pattern too long for this browser.");var r=this.match_alphabet_(t),i=this;function o(e,r){var o=e/t.length,s=Math.abs(n-r);return i.Match_Distance?o+s/i.Match_Distance:s?1:o}var s=this.Match_Threshold,a=e.indexOf(t,n);-1!=a&&(s=Math.min(o(0,a),s),-1!=(a=e.lastIndexOf(t,n+t.length))&&(s=Math.min(o(0,a),s)));var l,c,f=1<=p;m--){var y=r[e.charAt(m-1)];if(g[m]=0===h?(g[m+1]<<1|1)&y:(g[m+1]<<1|1)&y|(u[m+1]|u[m])<<1|1|u[m+1],g[m]&f){var b=o(h,m-1);if(b<=s){if(s=b,!((a=m-1)>n))break;p=Math.max(1,2*n-a)}}}if(o(h+1,n)>s)break;u=g}return a},t.prototype.match_alphabet_=function(e){for(var t={},n=0;n2&&(this.diff_cleanupSemantic(l),this.diff_cleanupEfficiency(l));else if(e&&"object"==typeof e&&void 0===o&&void 0===s)l=e,a=this.diff_text1(l);else if("string"==typeof e&&o&&"object"==typeof o&&void 0===s)a=e,l=o;else{if("string"!=typeof e||"string"!=typeof o||!s||"object"!=typeof s)throw new Error("Unknown call format to patch_make.");a=e,l=s}if(0===l.length)return[];for(var c=[],f=new t.patch_obj,u=0,d=0,h=0,p=a,v=a,g=0;g=2*this.Patch_Margin&&u&&(this.patch_addContext_(f,p),c.push(f),f=new t.patch_obj,u=0,p=v,d=h)}m!==r&&(d+=y.length),m!==n&&(h+=y.length)}return u&&(this.patch_addContext_(f,p),c.push(f)),c},t.prototype.patch_deepCopy=function(e){for(var n=[],r=0;rthis.Match_MaxBits?-1!=(c=this.match_main(t,d.substring(0,this.Match_MaxBits),u))&&(-1==(h=this.match_main(t,d.substring(d.length-this.Match_MaxBits),u+d.length-this.Match_MaxBits))||c>=h)&&(c=-1):c=this.match_main(t,d,u),-1==c)a[l]=!1,s-=e[l].length2-e[l].length1;else if(a[l]=!0,s=c-u,d==(f=-1==h?t.substring(c,c+d.length):t.substring(c,h+this.Match_MaxBits)))t=t.substring(0,c)+this.diff_text2(e[l].diffs)+t.substring(c+d.length);else{var p=this.diff_main(d,f,!1);if(d.length>this.Match_MaxBits&&this.diff_levenshtein(p)/d.length>this.Patch_DeleteThreshold)a[l]=!1;else{this.diff_cleanupSemanticLossless(p);for(var v,g=0,m=0;ms[0][1].length){var a=t-s[0][1].length;s[0][1]=n.substring(s[0][1].length)+s[0][1],o.start1-=a,o.start2-=a,o.length1+=a,o.length2+=a}return 0==(s=(o=e[e.length-1]).diffs).length||s[s.length-1][0]!=i?(s.push([i,n]),o.length1+=t,o.length2+=t):t>s[s.length-1][1].length&&(a=t-s[s.length-1][1].length,s[s.length-1][1]+=n.substring(0,a),o.length1+=a,o.length2+=a),n},t.prototype.patch_splitMax=function(e){for(var o=this.Match_MaxBits,s=0;s2*o?(u.length1+=p.length,l+=p.length,d=!1,u.diffs.push([h,p]),a.diffs.shift()):(p=p.substring(0,o-u.length1-this.Patch_Margin),u.length1+=p.length,l+=p.length,h===i?(u.length2+=p.length,c+=p.length):d=!1,u.diffs.push([h,p]),p==a.diffs[0][1]?a.diffs.shift():a.diffs[0][1]=a.diffs[0][1].substring(p.length))}f=(f=this.diff_text2(u.diffs)).substring(f.length-this.Patch_Margin);var v=this.diff_text1(a.diffs).substring(0,this.Patch_Margin);""!==v&&(u.length1+=v.length,u.length2+=v.length,0!==u.diffs.length&&u.diffs[u.diffs.length-1][0]===i?u.diffs[u.diffs.length-1][1]+=v:u.diffs.push([i,v])),d||e.splice(++s,0,u)}}},t.prototype.patch_toText=function(e){for(var t=[],n=0;n'+t+"")}},{key:"formatValue",value:function(e,t){e.out("
"+ue(JSON.stringify(t,null,2))+"
")}},{key:"formatTextDiffString",value:function(e,t){var n=this.parseTextDiff(t);e.out('
    ');for(var r=0,i=n.length;r
    '+o.location.line+''+o.location.chr+'
    ');for(var s=o.pieces,a=0,l=s.length;a'+ue(decodeURI(c.text))+"")}e.out("
    ")}e.out("
")}},{key:"rootBegin",value:function(e,t,n){var r="jsondiffpatch-"+t+(n?" jsondiffpatch-child-node-type-"+n:"");e.out('
')}},{key:"rootEnd",value:function(e){e.out("
"+(e.hasArrows?' + diff --git a/src/jsdiff-panel.html b/bundle/jsdiff-panel.html similarity index 60% rename from src/jsdiff-panel.html rename to bundle/jsdiff-panel.html index cf3e4da..5d4bfe4 100644 --- a/src/jsdiff-panel.html +++ b/bundle/jsdiff-panel.html @@ -1,10 +1,10 @@ - +
- + diff --git a/manifest.json b/manifest.json index 819dc20..ecf2589 100644 --- a/manifest.json +++ b/manifest.json @@ -1,15 +1,33 @@ { "name": "console.diff(...)", - "description": "Compare in-memory objects and see the result inside devtools panel with a set of console.diff functions.", - "version": "3.0.0", + "description": "Compare objects in memory with console.diff(old, new) devtools function", + "version": "3.0.1", "manifest_version": 3, - "minimum_chrome_version": "88.0", - "devtools_page": "src/jsdiff-devtools.html", + "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlCx2Bl0li+3idvfrH9cQL/MzphafGFqMUA2P+0vbyhwxsxWl0llOaGQbkirX5qCoAVHoUCPqu3hCjpVCv35igPbfqDs5bdLZZmXt2F0HjEQnWI/eZKd9IKcKYMplEeL2BodmpU02VrP1UnUzQHZeeMWk9ybgWOqCimkwliILVubRj5dxNB9AidLwO4Z5iGq/OvW9AJMYdxKxrLP2lF6/GGNcCBg+iCJZwlQOhFB9LbUjytT4ws3bIEX4b5zmWLqGKR1NiZfGug2eCWXt9oEKg2WkbXmBBzFKqxnM/bBUrVR29N9qNgx0f42qnyhsW3Bo4kPzE3d0asXCV5nofLTLEwIDAQAB", + "minimum_chrome_version": "100.0", + "devtools_page": "bundle/jsdiff-devtools.html", + "content_scripts": [ + { + "world": "MAIN", + "js": ["/bundle/js/jsdiff-console.js"], + "matches": [""], + "match_origin_as_fallback": true, + "all_frames": true, + "run_at": "document_start" + }, + { + "js": ["/bundle/js/jsdiff-proxy.js"], + "matches": [""], + "match_origin_as_fallback": true, + "all_frames": true, + "run_at": "document_start" + } + ], "icons": { - "28": "src/img/panel-icon28.png", - "64": "src/img/panel-icon64.png", - "128": "src/img/panel-icon128.png" + "28": "bundle/img/panel-icon28.png", + "64": "bundle/img/panel-icon64.png", + "128": "bundle/img/panel-icon128.png" }, - "permissions": ["storage", "scripting", "clipboardWrite"], + "permissions": ["storage"], "host_permissions": ["*://*/*"] } diff --git a/package.json b/package.json index 8b15a58..54fc2a1 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,14 @@ { "name": "jsdiff", - "version": "3.0.0", - "description": "![jsdiff](./src/img/panel-icon64.png) --- Chrome devtools extension intended to display result of in-memory object comparisons with the help of dedicated commands invoked via console.", + "version": "3.0.1", + "description": "Chrome extension to compare objects in memory with console.diff(old, new) devtools function", "private": true, "scripts": { "test": "echo \"no test\" && exit 1", "dev": "webpack --progress --watch --mode=development", "prod": "NODE_ENV=production webpack --mode=production", - "format": "prettier . --write" + "format": "prettier . --write", + "zip": "./scripts/package.sh" }, "repository": { "type": "git", @@ -26,21 +27,22 @@ "homepage": "https://github.com/zendive/jsdiff#readme", "type": "module", "devDependencies": { - "@types/chrome": "^0.0.237", - "@vue/compiler-sfc": "^3.3.4", - "clean-webpack-plugin": "~4.0.0", - "css-loader": "~6.8.1", - "jsondiffpatch": "~0.4.1", - "prettier": "^2.8.8", - "sass": "^1.63.3", - "sass-loader": "~13.3.2", - "style-loader": "~3.3.3", - "ts-loader": "^9.4.3", - "typescript": "^5.1.3", - "vue": "~3.3.4", - "vue-loader": "~17.2.2", - "webpack": "~5.86.0", - "webpack-bundle-analyzer": "^4.9.0", - "webpack-cli": "~5.1.4" + "@types/chrome": "0.0.242", + "@vue/compiler-sfc": "3.3.4", + "clean-webpack-plugin": "4.0.0", + "css-loader": "6.8.1", + "jsondiffpatch": "0.4.1", + "prettier": "3.0.0", + "sass": "1.64.1", + "sass-loader": "13.3.2", + "style-loader": "3.3.3", + "terser-webpack-plugin": "5.3.9", + "ts-loader": "9.4.4", + "typescript": "5.1.6", + "vue": "3.3.4", + "vue-loader": "17.2.2", + "webpack": "5.88.2", + "webpack-bundle-analyzer": "4.9.0", + "webpack-cli": "5.1.4" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad248b5..3c851e8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,53 +6,56 @@ settings: devDependencies: '@types/chrome': - specifier: ^0.0.237 - version: 0.0.237 + specifier: 0.0.242 + version: 0.0.242 '@vue/compiler-sfc': - specifier: ^3.3.4 + specifier: 3.3.4 version: 3.3.4 clean-webpack-plugin: - specifier: ~4.0.0 - version: 4.0.0(webpack@5.86.0) + specifier: 4.0.0 + version: 4.0.0(webpack@5.88.2) css-loader: - specifier: ~6.8.1 - version: 6.8.1(webpack@5.86.0) + specifier: 6.8.1 + version: 6.8.1(webpack@5.88.2) jsondiffpatch: - specifier: ~0.4.1 + specifier: 0.4.1 version: 0.4.1 prettier: - specifier: ^2.8.8 - version: 2.8.8 + specifier: 3.0.0 + version: 3.0.0 sass: - specifier: ^1.63.3 - version: 1.63.3 + specifier: 1.64.1 + version: 1.64.1 sass-loader: - specifier: ~13.3.2 - version: 13.3.2(sass@1.63.3)(webpack@5.86.0) + specifier: 13.3.2 + version: 13.3.2(sass@1.64.1)(webpack@5.88.2) style-loader: - specifier: ~3.3.3 - version: 3.3.3(webpack@5.86.0) + specifier: 3.3.3 + version: 3.3.3(webpack@5.88.2) + terser-webpack-plugin: + specifier: 5.3.9 + version: 5.3.9(webpack@5.88.2) ts-loader: - specifier: ^9.4.3 - version: 9.4.3(typescript@5.1.3)(webpack@5.86.0) + specifier: 9.4.4 + version: 9.4.4(typescript@5.1.6)(webpack@5.88.2) typescript: - specifier: ^5.1.3 - version: 5.1.3 + specifier: 5.1.6 + version: 5.1.6 vue: - specifier: ~3.3.4 + specifier: 3.3.4 version: 3.3.4 vue-loader: - specifier: ~17.2.2 - version: 17.2.2(@vue/compiler-sfc@3.3.4)(vue@3.3.4)(webpack@5.86.0) + specifier: 17.2.2 + version: 17.2.2(@vue/compiler-sfc@3.3.4)(vue@3.3.4)(webpack@5.88.2) webpack: - specifier: ~5.86.0 - version: 5.86.0(webpack-cli@5.1.4) + specifier: 5.88.2 + version: 5.88.2(webpack-cli@5.1.4) webpack-bundle-analyzer: - specifier: ^4.9.0 + specifier: 4.9.0 version: 4.9.0 webpack-cli: - specifier: ~5.1.4 - version: 5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.86.0) + specifier: 5.1.4 + version: 5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.88.2) packages: /@babel/helper-string-parser@7.22.5: @@ -171,10 +174,10 @@ packages: } dev: true - /@types/chrome@0.0.237: + /@types/chrome@0.0.242: resolution: { - integrity: sha512-krsRmyfMlck5r+H1EapsrrucDRq6iRm0NAi5fapr93CgnpVMDdK+h2+z4x79GegdW7BNH9Vb//gkptORwwwVIQ==, + integrity: sha512-SeMXBSfcAGX9ezTz7Pro7n/AiNdIH3cetkdbM+Kfg3zD24jmbnm0IAEIxzx8ccqrnJenLCfD7fR+4WIYAbeQHw==, } dependencies: '@types/filesystem': 0.0.32 @@ -526,7 +529,7 @@ packages: '@xtuc/long': 4.2.2 dev: true - /@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4)(webpack@5.86.0): + /@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4)(webpack@5.88.2): resolution: { integrity: sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==, @@ -536,11 +539,11 @@ packages: webpack: 5.x.x webpack-cli: 5.x.x dependencies: - webpack: 5.86.0(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.86.0) + webpack: 5.88.2(webpack-cli@5.1.4) + webpack-cli: 5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.88.2) dev: true - /@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.86.0): + /@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.88.2): resolution: { integrity: sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==, @@ -550,11 +553,11 @@ packages: webpack: 5.x.x webpack-cli: 5.x.x dependencies: - webpack: 5.86.0(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.86.0) + webpack: 5.88.2(webpack-cli@5.1.4) + webpack-cli: 5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.88.2) dev: true - /@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack@5.86.0): + /@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack@5.88.2): resolution: { integrity: sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==, @@ -568,8 +571,8 @@ packages: webpack-dev-server: optional: true dependencies: - webpack: 5.86.0(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.86.0) + webpack: 5.88.2(webpack-cli@5.1.4) + webpack-cli: 5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.88.2) dev: true /@xtuc/ieee754@1.2.0: @@ -798,7 +801,7 @@ packages: engines: { node: '>=6.0' } dev: true - /clean-webpack-plugin@4.0.0(webpack@5.86.0): + /clean-webpack-plugin@4.0.0(webpack@5.88.2): resolution: { integrity: sha512-WuWE1nyTNAyW5T7oNyys2EN0cfP2fdRxhxnIQWiAp0bMabPdHhoGxM8A6YL2GhqwgrPnnaemVE7nv5XJ2Fhh2w==, @@ -808,7 +811,7 @@ packages: webpack: '>=4.0.0 <6.0.0' dependencies: del: 4.1.1 - webpack: 5.86.0(webpack-cli@5.1.4) + webpack: 5.88.2(webpack-cli@5.1.4) dev: true /clone-deep@4.0.1: @@ -887,7 +890,10 @@ packages: dev: true /concat-map@0.0.1: - resolution: { integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= } + resolution: + { + integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, + } dev: true /cross-spawn@7.0.3: @@ -902,7 +908,7 @@ packages: which: 2.0.2 dev: true - /css-loader@6.8.1(webpack@5.86.0): + /css-loader@6.8.1(webpack@5.88.2): resolution: { integrity: sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==, @@ -919,7 +925,7 @@ packages: postcss-modules-values: 4.0.0(postcss@8.4.24) postcss-value-parser: 4.2.0 semver: 7.5.1 - webpack: 5.86.0(webpack-cli@5.1.4) + webpack: 5.88.2(webpack-cli@5.1.4) dev: true /cssesc@3.0.0: @@ -975,10 +981,10 @@ packages: } dev: true - /enhanced-resolve@5.14.1: + /enhanced-resolve@5.15.0: resolution: { - integrity: sha512-Vklwq2vDKtl0y/vtwjSesgJ5MYS7Etuk5txS8VdKL4AOS1aUlD96zqIfsOSLQsdv3xgMRbtkWM8eG9XDfKUPow==, + integrity: sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==, } engines: { node: '>=10.13.0' } dependencies: @@ -1424,7 +1430,6 @@ packages: chalk: 2.4.2 diff-match-patch: 1.0.5 dev: true - bundledDependencies: [] /kind-of@6.0.3: resolution: @@ -1804,12 +1809,12 @@ packages: source-map-js: 1.0.2 dev: true - /prettier@2.8.8: + /prettier@3.0.0: resolution: { - integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==, + integrity: sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==, } - engines: { node: '>=10.13.0' } + engines: { node: '>=14' } hasBin: true dev: true @@ -1897,7 +1902,7 @@ packages: } dev: true - /sass-loader@13.3.2(sass@1.63.3)(webpack@5.86.0): + /sass-loader@13.3.2(sass@1.64.1)(webpack@5.88.2): resolution: { integrity: sha512-CQbKl57kdEv+KDLquhC+gE3pXt74LEAzm+tzywcA0/aHZuub8wTErbjAoNI57rPUWRYRNC5WUnNl8eGJNbDdwg==, @@ -1920,14 +1925,14 @@ packages: optional: true dependencies: neo-async: 2.6.2 - sass: 1.63.3 - webpack: 5.86.0(webpack-cli@5.1.4) + sass: 1.64.1 + webpack: 5.88.2(webpack-cli@5.1.4) dev: true - /sass@1.63.3: + /sass@1.64.1: resolution: { - integrity: sha512-ySdXN+DVpfwq49jG1+hmtDslYqpS7SkOR5GpF6o2bmb1RL/xS+wvPmegMvMywyfsmAV6p7TgwXYGrCZIFFbAHg==, + integrity: sha512-16rRACSOFEE8VN7SCgBu1MpYCyN7urj9At898tyzdXFhC+a+yOX5dXwAR7L8/IdPJ1NB8OYoXmD55DM30B2kEQ==, } engines: { node: '>=14.0.0' } hasBin: true @@ -2035,7 +2040,7 @@ packages: engines: { node: '>=0.10.0' } dev: true - /style-loader@3.3.3(webpack@5.86.0): + /style-loader@3.3.3(webpack@5.88.2): resolution: { integrity: sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==, @@ -2044,7 +2049,7 @@ packages: peerDependencies: webpack: ^5.0.0 dependencies: - webpack: 5.86.0(webpack-cli@5.1.4) + webpack: 5.88.2(webpack-cli@5.1.4) dev: true /supports-color@5.5.0: @@ -2093,7 +2098,7 @@ packages: engines: { node: '>=6' } dev: true - /terser-webpack-plugin@5.3.9(webpack@5.86.0): + /terser-webpack-plugin@5.3.9(webpack@5.88.2): resolution: { integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==, @@ -2116,14 +2121,14 @@ packages: jest-worker: 27.5.1 schema-utils: 3.2.0 serialize-javascript: 6.0.1 - terser: 5.18.0 - webpack: 5.86.0(webpack-cli@5.1.4) + terser: 5.19.2 + webpack: 5.88.2(webpack-cli@5.1.4) dev: true - /terser@5.18.0: + /terser@5.19.2: resolution: { - integrity: sha512-pdL757Ig5a0I+owA42l6tIuEycRuM7FPY4n62h44mRLRfnOxJkkOHd6i89dOpwZlpF6JXBwaAHF6yWzFrt+QyA==, + integrity: sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==, } engines: { node: '>=10' } hasBin: true @@ -2160,10 +2165,10 @@ packages: engines: { node: '>=6' } dev: true - /ts-loader@9.4.3(typescript@5.1.3)(webpack@5.86.0): + /ts-loader@9.4.4(typescript@5.1.6)(webpack@5.88.2): resolution: { - integrity: sha512-n3hBnm6ozJYzwiwt5YRiJZkzktftRpMiBApHaJPoWLA+qetQBAXkHqCLM6nwSdRDimqVtA5ocIkcTRLMTt7yzA==, + integrity: sha512-MLukxDHBl8OJ5Dk3y69IsKVFRA/6MwzEqBgh+OXMPB/OD01KQuWPFd1WAQP8a5PeSCAxfnkhiuWqfmFJzJQt9w==, } engines: { node: '>=12.0.0' } peerDependencies: @@ -2171,17 +2176,17 @@ packages: webpack: ^5.0.0 dependencies: chalk: 4.1.2 - enhanced-resolve: 5.14.1 + enhanced-resolve: 5.15.0 micromatch: 4.0.5 semver: 7.5.1 - typescript: 5.1.3 - webpack: 5.86.0(webpack-cli@5.1.4) + typescript: 5.1.6 + webpack: 5.88.2(webpack-cli@5.1.4) dev: true - /typescript@5.1.3: + /typescript@5.1.6: resolution: { - integrity: sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==, + integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==, } engines: { node: '>=14.17' } hasBin: true @@ -2217,7 +2222,7 @@ packages: } dev: true - /vue-loader@17.2.2(@vue/compiler-sfc@3.3.4)(vue@3.3.4)(webpack@5.86.0): + /vue-loader@17.2.2(@vue/compiler-sfc@3.3.4)(vue@3.3.4)(webpack@5.88.2): resolution: { integrity: sha512-aqNvKJvnz2A/6VWeJZodAo8XLoAlVwBv+2Z6dama+LHsAF+P/xijQ+OfWrxIs0wcGSJduvdzvTuATzXbNKkpiw==, @@ -2237,7 +2242,7 @@ packages: hash-sum: 2.0.0 vue: 3.3.4 watchpack: 2.4.0 - webpack: 5.86.0(webpack-cli@5.1.4) + webpack: 5.88.2(webpack-cli@5.1.4) dev: true /vue@3.3.4: @@ -2287,7 +2292,7 @@ packages: - utf-8-validate dev: true - /webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.86.0): + /webpack-cli@5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.88.2): resolution: { integrity: sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==, @@ -2308,9 +2313,9 @@ packages: optional: true dependencies: '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4)(webpack@5.86.0) - '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4)(webpack@5.86.0) - '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4)(webpack@5.86.0) + '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4)(webpack@5.88.2) + '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4)(webpack@5.88.2) + '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4)(webpack@5.88.2) colorette: 2.0.20 commander: 10.0.1 cross-spawn: 7.0.3 @@ -2319,7 +2324,7 @@ packages: import-local: 3.1.0 interpret: 3.1.1 rechoir: 0.8.0 - webpack: 5.86.0(webpack-cli@5.1.4) + webpack: 5.88.2(webpack-cli@5.1.4) webpack-bundle-analyzer: 4.9.0 webpack-merge: 5.9.0 dev: true @@ -2343,10 +2348,10 @@ packages: engines: { node: '>=10.13.0' } dev: true - /webpack@5.86.0(webpack-cli@5.1.4): + /webpack@5.88.2(webpack-cli@5.1.4): resolution: { - integrity: sha512-3BOvworZ8SO/D4GVP+GoRC3fVeg5MO4vzmq8TJJEkdmopxyazGDxN8ClqN12uzrZW9Tv8EED8v5VSb6Sqyi0pg==, + integrity: sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==, } engines: { node: '>=10.13.0' } hasBin: true @@ -2365,7 +2370,7 @@ packages: acorn-import-assertions: 1.9.0(acorn@8.8.2) browserslist: 4.21.8 chrome-trace-event: 1.0.3 - enhanced-resolve: 5.14.1 + enhanced-resolve: 5.15.0 es-module-lexer: 1.3.0 eslint-scope: 5.1.1 events: 3.3.0 @@ -2377,9 +2382,9 @@ packages: neo-async: 2.6.2 schema-utils: 3.2.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.9(webpack@5.86.0) + terser-webpack-plugin: 5.3.9(webpack@5.88.2) watchpack: 2.4.0 - webpack-cli: 5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.86.0) + webpack-cli: 5.1.4(webpack-bundle-analyzer@4.9.0)(webpack@5.88.2) webpack-sources: 3.2.3 transitivePeerDependencies: - '@swc/core' diff --git a/scripts/package.sh b/scripts/package.sh new file mode 100755 index 0000000..641957c --- /dev/null +++ b/scripts/package.sh @@ -0,0 +1,13 @@ +#!/bin/sh -e + +ZIP_FILE="extension.zip" +HASH_ALG="sha384" + +pnpm run prod > /dev/null + +rm -rf $ZIP_FILE +zip -r $ZIP_FILE ./bundle ./manifest.json > /dev/null + +FILE_HASH=$(openssl dgst -$HASH_ALG -binary $ZIP_FILE | openssl base64 -A) + +echo "$ZIP_FILE (hash-$HASH_ALG): $FILE_HASH" diff --git a/src/@types/index.d.ts b/src/@types/index.d.ts new file mode 100644 index 0000000..f19b1f6 --- /dev/null +++ b/src/@types/index.d.ts @@ -0,0 +1,49 @@ +export {}; + +declare global { + interface ICompareState { + timestamp: number; + left?: unknown; + right?: unknown; + } + + interface ICompareMessagePayload { + push?: unknown; + left?: unknown; + right?: unknown; + timestamp: number; + [key: string]: unknown; + } + + interface ICompareMessage { + source: 'jsdiff-proxy-to-panel-compare'; + payload: ICompareMessagePayload; + } + + interface IErrorMessage { + source: 'jsdiff-proxy-to-panel-error'; + } + + interface IProgressMessage { + source: 'jsdiff-proxy-to-panel-inprogress'; + on: boolean; + } + + interface ISearchMessage { + source: 'jsdiff-devtools-to-panel-search'; + params: ISearchOptions; + } + + type TSearchCommands = 'performSearch' | 'nextSearchResult' | 'cancelSearch'; + + interface ISearchOptions { + cmd: TSearchCommands; + query: string | null; + } + + type TRuntimeMessageOptions = + | ICompareMessage + | IProgressMessage + | IErrorMessage + | ISearchMessage; +} diff --git a/src/api/clone.ts b/src/api/clone.ts new file mode 100644 index 0000000..6496905 --- /dev/null +++ b/src/api/clone.ts @@ -0,0 +1,406 @@ +import { TAG } from '@/api/const'; +import { SHA256 } from './toolkit'; + +type TInstanceBadgeTag = (id: string) => string; +type TSymbolBadgeTag = (symbolName: string, symbolId: string) => string; +interface ICatalogRecord { + name: string; + seen: boolean; +} +interface ISerializeToObject { + [key: string]: any; +} +interface IFunction { + name: string; + toString: () => string; +} +interface IHasToJSON { + toJSON: () => unknown; +} + +class ObjectsCatalog { + #records: Map; + #instanceCounter = 0; + + constructor() { + this.#records = new Map(); + } + + clear() { + this.#records.clear(); + } + + #counterToString(counter: number): string { + return counter.toString(16).padStart(4, '0'); + } + + lookup( + value: unknown, + badge: TInstanceBadgeTag | TSymbolBadgeTag + ): ICatalogRecord { + let record = this.#records.get(value); + + if (!record) { + ++this.#instanceCounter; + const id = this.#counterToString(this.#instanceCounter); + record = { + name: isSymbol(value) + ? (badge as TSymbolBadgeTag)(value.toString(), id) + : (badge as TInstanceBadgeTag)(id), + seen: false, + }; + this.#records.set(value, record); + } + + return record; + } +} + +export async function post( + cloneFn: (value: unknown) => Promise, + payload: ICompareMessagePayload +): Promise { + try { + window.postMessage( + { source: 'jsdiff-console-to-proxy-inprogress', on: true }, + window.location.origin + ); + + for (const key of ['push', 'left', 'right']) { + if (Reflect.has(payload, key)) { + const value = payload[key]; + + if (value === undefined) { + payload[key] = TAG.UNDEFINED; + } else if (value === null) { + payload[key] = TAG.NULL; + } else { + payload[key] = await cloneFn(value); + } + } + } + + window.postMessage( + { source: 'jsdiff-console-to-proxy-compare', payload }, + window.location.origin + ); + } catch (error) { + console.error('console.diff()', error); + + window.postMessage( + { source: 'jsdiff-console-to-proxy-inprogress', on: false }, + window.location.origin + ); + } +} + +export async function nativeClone(value: unknown): Promise { + let set: Set | null = new Set(); + const rv = JSON.parse( + JSON.stringify(value, nativeClonePostDataAdapter.bind(null, set)) + ); + + set.clear(); + set = null; + + return rv; +} + +export async function customClone(value: unknown): Promise { + let catalog: ObjectsCatalog | null = new ObjectsCatalog(); + const rv = await recursiveClone(catalog, value); + + catalog.clear(); + catalog = null; + + return rv; +} + +async function recursiveClone( + catalog: ObjectsCatalog, + value: unknown +): Promise { + let rv = value; + + if (isUnserializable(value)) { + const { name } = catalog.lookup(value, TAG.UNSERIALIZABLE); + rv = name; + } else if (isFunction(value)) { + rv = await serializeFunction(value); + } else if (isSymbol(value)) { + const { name } = catalog.lookup(value, TAG.SYMBOL); + rv = name; + } else if (isArray(value)) { + rv = await serializeArrayAlike(catalog, value, TAG.RECURRING_ARRAY); + } else if (isSet(value)) { + rv = await serializeArrayAlike(catalog, value, TAG.RECURRING_SET); + } else if (isMap(value)) { + rv = await serializeMap(catalog, value); + } else if (isObject(value)) { + rv = await serializeObject(catalog, value); + } else if (isNumericSpecials(value)) { + rv = TAG.NUMERIC(value); + } else if (value === undefined) { + // JsonDiffPatch has problem identifying undefined value - storing a string instead + rv = TAG.UNDEFINED; + } + + return rv; +} + +function isNumericSpecials(value: unknown): value is bigint | number { + return ( + typeof value === 'bigint' || + Number.isNaN(value) || + value === -Infinity || + value === Infinity + ); +} + +async function serializeArrayAlike( + catalog: ObjectsCatalog, + value: unknown[] | Set, + badge: TInstanceBadgeTag +): Promise { + const record = catalog.lookup(value, badge); + let rv; + + if (record.seen) { + rv = record.name; + } else { + record.seen = true; + const arr = []; + + for (const v of value) { + arr.push(await recursiveClone(catalog, v)); + } + + rv = arr; + } + + return rv; +} + +async function serializeMap( + catalog: ObjectsCatalog, + value: Map +): Promise { + const record = catalog.lookup(value, TAG.RECURRING_MAP); + let rv; + + if (record.seen) { + rv = record.name; + } else { + record.seen = true; + const obj = {} as ISerializeToObject; + + for (const [k, v] of value) { + const newKey = await serializeMapKey(catalog, k); + const newValue = await recursiveClone(catalog, v); + + obj[newKey] = newValue; + } + + rv = obj; + } + + return rv; +} + +async function serializeMapKey( + catalog: ObjectsCatalog, + key: unknown +): Promise { + let rv; + + if (isUnserializable(key)) { + const { name } = catalog.lookup(key, TAG.UNSERIALIZABLE); + rv = name; + } else if (isFunction(key)) { + rv = await serializeFunction(key); + } else if (isSymbol(key)) { + const { name } = catalog.lookup(key, TAG.SYMBOL); + rv = name; + } else if (isArray(key)) { + const { name } = catalog.lookup(key, TAG.RECURRING_ARRAY); + rv = name; + } else if (isSet(key)) { + const { name } = catalog.lookup(key, TAG.RECURRING_SET); + rv = name; + } else if (isMap(key)) { + const { name } = catalog.lookup(key, TAG.RECURRING_MAP); + rv = name; + } else if (isObject(key)) { + const { name } = catalog.lookup(key, TAG.RECURRING_OBJECT); + rv = name; + } else if (isNumericSpecials(key)) { + rv = TAG.NUMERIC(key); + } else if (key === undefined) { + rv = TAG.UNDEFINED; + } else { + rv = String(key); + } + + return rv; +} + +async function serializeObject( + catalog: ObjectsCatalog, + value: object +): Promise { + const record = catalog.lookup(value, TAG.RECURRING_OBJECT); + let rv; + + if (record.seen) { + rv = record.name; + } else { + record.seen = true; + + if (isSelfSerializableObject(value)) { + const newValue = serializeSelfSerializable(value); + rv = await recursiveClone(catalog, newValue); + } else { + const obj = {} as ISerializeToObject; + const ownKeys = Reflect.ownKeys(value); + + for (const key of ownKeys) { + let newKey, newValue; + + if (isSymbol(key)) { + const { name } = catalog.lookup(key, TAG.SYMBOL); + newKey = name; + } else { + newKey = key; + } + + try { + // accessing value by key may throw + newValue = await recursiveClone(catalog, (value as any)[key]); + } catch (error) { + newValue = stringifyError(error); + } + + obj[newKey] = newValue; + } + + rv = obj; + } + } + + return rv; +} + +async function serializeFunction(value: IFunction): Promise { + const fnBody = value.toString(); + + if (fnBody.endsWith('{ [native code] }')) { + return TAG.NATIVE_FUNCTION; + } else { + const hash = await SHA256(fnBody); + return TAG.FUNCTION(value.name, hash); + } +} + +function serializeSelfSerializable(value: IHasToJSON) { + let rv; + + try { + // rogue object may throw + rv = value.toJSON(); + } catch (error) { + rv = stringifyError(error); + } + + return rv; +} + +function stringifyError(error: unknown) { + return typeof error?.toString === 'function' + ? TAG.EXCEPTION(error.toString()) + : TAG.EXCEPTION_FALLBACK; +} + +function nativeClonePostDataAdapter( + set: Set, + key: string | Symbol, + value: unknown +): unknown { + try { + if (isUnserializable(value)) { + return undefined; + } else if (isFunction(value)) { + return value.toString(); + } else if (isObject(value)) { + if (set.has(value)) { + return undefined; + } else { + set.add(value); + } + } + + return value; + } catch (error) { + return stringifyError(error); + } +} + +function isArray(that: unknown): that is unknown[] { + return ( + that instanceof Array || + that instanceof Uint8Array || + that instanceof Uint8ClampedArray || + that instanceof Uint16Array || + that instanceof Uint32Array || + that instanceof BigUint64Array || + that instanceof Int8Array || + that instanceof Int16Array || + that instanceof Int32Array || + that instanceof BigInt64Array || + that instanceof Float32Array || + that instanceof Float64Array + ); +} + +function isFunction(that: unknown): that is IFunction { + return ( + typeof that === 'function' && + 'toString' in that && + typeof that.toString === 'function' + ); +} + +function isSet(that: unknown): that is Set { + return that instanceof Set; +} + +function isMap(that: unknown): that is Map { + return that instanceof Map; +} + +function isSelfSerializableObject(that: unknown): that is IHasToJSON { + let rv; + + try { + rv = + that !== null && + typeof that === 'object' && + 'toJSON' in that && + typeof that.toJSON === 'function'; + } catch (ignore) { + rv = false; + } + + return rv; +} + +function isUnserializable(that: unknown): boolean { + return that instanceof Element || that instanceof Document; +} + +function isSymbol(that: unknown): that is Symbol { + return typeof that === 'symbol'; +} + +function isObject(that: unknown): that is object { + return (that !== null && typeof that === 'object') || that instanceof Object; +} diff --git a/src/api/const.ts b/src/api/const.ts new file mode 100644 index 0000000..4cf3914 --- /dev/null +++ b/src/api/const.ts @@ -0,0 +1,25 @@ +export const TAG = { + EMPTY: '⟪empty⟫', + UNDEFINED: '⟪undefined⟫', + NULL: '⟪null⟫', + NATIVE_FUNCTION: 'ƒ⟪native⟫', + EXCEPTION_FALLBACK: '⁉️ ⟪exception⟫', + EXCEPTION: (str: string) => `⁉️ ⟪${str}⟫`, + RECURRING_ARRAY: (id: string) => `0x${id}: [♻️]`, + RECURRING_OBJECT: (id: string) => `0x${id}: {♻️}`, + RECURRING_SET: (id: string) => `0x${id}: Set[♻️]`, + RECURRING_MAP: (id: string) => `0x${id}: Map{♻️}`, + UNSERIALIZABLE: (id: string) => `0x${id}: ⟪unserializable⟫`, + SYMBOL: (name: string, id: string) => `0x${id}: ${name}`, + FUNCTION: (name: string, hash: string) => + `ƒ${name ? ` ${name}` : ''}⟪${hash}⟫`, + NUMERIC: (value: bigint | number) => + typeof value === 'bigint' ? `BigInt⟪${value}⟫` : `Number⟪${value}⟫`, +}; + +export const ERROR = { + NO_CONNECTION: + 'Could not establish connection. Receiving end does not exist.', + PORT_CLOSED: 'The message port closed before a response was received.', + QUOTA_EXCEEDED: 'QUOTA_BYTES quota exceeded', +}; diff --git a/src/api/formatter-dom.ts b/src/api/formatter-dom.ts new file mode 100644 index 0000000..813e35c --- /dev/null +++ b/src/api/formatter-dom.ts @@ -0,0 +1,86 @@ +function getElementText(_ref: HTMLElement | null): string { + return _ref ? _ref.textContent || _ref.innerText : ''; +} + +function eachByQuery( + el: HTMLElement, + query: string, + fn: (_ref: HTMLElement) => void +) { + const elems = el.querySelectorAll(query) as NodeListOf; + for (let i = 0, l = elems.length; i < l; i++) { + fn(elems[i]); + } +} + +function eachChildren( + _ref2: ParentNode | null, + fn: (el: HTMLElement, i: number) => void +) { + if (_ref2) { + const children = _ref2.children; + + for (let i = 0, l = children.length; i < l; i++) { + fn(children[i] as HTMLElement, i); + } + } +} + +export const postDiffRender = (nodeArg: HTMLElement | null): Promise => { + return new Promise((resolve) => { + setTimeout(() => { + jsondiffpatchHtmlFormatterAdjustArrows(nodeArg); + resolve(); + }, 10); + }); +}; + +function jsondiffpatchHtmlFormatterAdjustArrows(nodeArg: HTMLElement | null) { + const node = nodeArg || document; + + eachByQuery(node, '.jsondiffpatch-arrow', function (_ref3) { + const parentNode = _ref3.parentNode; + const children = _ref3.children; + const style = _ref3.style; + const arrowParent = parentNode; + const svg = children[0] as HTMLElement; + const path = svg.children[1]; + + svg.style.display = 'none'; + + if (arrowParent instanceof HTMLElement) { + const destination = getElementText( + arrowParent.querySelector('.jsondiffpatch-moved-destination') + ); + const container = arrowParent.parentNode; + let destinationElem: unknown; + + eachChildren(container, function (child) { + if (child.getAttribute('data-key') === destination) { + destinationElem = child; + } + }); + + if (destinationElem instanceof HTMLElement) { + try { + const distance = destinationElem.offsetTop - arrowParent.offsetTop; + svg.setAttribute('height', `${Math.abs(distance) + 6}`); + style.top = -8 + (distance > 0 ? 0 : distance) + 'px'; + const curve = + distance > 0 + ? 'M30,0 Q-10,' + + Math.round(distance / 2) + + ' 26,' + + (distance - 4) + : 'M30,' + + -distance + + ' Q-10,' + + Math.round(-distance / 2) + + ' 26,4'; + path.setAttribute('d', curve); + svg.style.display = ''; + } catch (ignore) {} + } + } + }); +} diff --git a/src/api/proxy.ts b/src/api/proxy.ts new file mode 100644 index 0000000..5fedcd1 --- /dev/null +++ b/src/api/proxy.ts @@ -0,0 +1,109 @@ +import { ERROR, TAG } from '@/api/const'; + +export function proxyMessageGate( + callbackInprogress: (e: MessageEvent) => void, + callbackCompare: (e: MessageEvent) => Promise +) { + return function (e: MessageEvent) { + if ( + e.origin === window.location.origin && + e.source === window && + typeof e.data === 'object' && + e.data !== null + ) { + if ('jsdiff-console-to-proxy-inprogress' === e.data.source) { + callbackInprogress(e); + } else if ('jsdiff-console-to-proxy-compare' === e.data.source) { + callbackCompare(e); + } + } + }; +} + +export async function proxyCompareHandler( + e: MessageEvent +): Promise { + const current = e.data.payload; + const { lastApiReq: old } = await chrome.storage.local.get(['lastApiReq']); + const next = processComparisonObject(old, current); + + try { + // may throw + await chrome.storage.local.set({ lastApiReq: next, lastError: '' }); + + chrome.runtime.sendMessage( + { + source: 'jsdiff-proxy-to-panel-compare', + } as ICompareMessage, + handleResponse + ); + } catch (error: any) { + if (error?.message === ERROR.QUOTA_EXCEEDED) { + await chrome.storage.local.set({ lastError: ERROR.QUOTA_EXCEEDED }); + + chrome.runtime.sendMessage( + { source: 'jsdiff-proxy-to-panel-error' } as IErrorMessage, + handleResponse + ); + } else if (error?.message) { + console.error('Unhnadled', error.message); + } + } +} + +export function proxyInprogressHandler( + e: MessageEvent +): void { + chrome.runtime.sendMessage( + { + source: 'jsdiff-proxy-to-panel-inprogress', + on: e.data.on, + } as IProgressMessage, + handleResponse + ); +} + +function processComparisonObject( + old: ICompareMessagePayload, + next: ICompareMessagePayload +) { + if (!old) { + old = { + left: TAG.EMPTY, + right: TAG.EMPTY, + timestamp: Date.now(), + }; + } + + const rv = old; + + if (Reflect.has(next, 'push')) { + rv.left = rv.right; + rv.right = next.push; + } else { + if (Reflect.has(next, 'left')) { + rv.left = next.left; + } + if (Reflect.has(next, 'right')) { + rv.right = next.right; + } + } + + rv.timestamp = next.timestamp; + + return rv; +} + +function handleResponse(error: chrome.runtime.LastError | undefined): void { + if (!isIgnorable(chrome.runtime.lastError)) { + console.error(chrome.runtime.lastError); + } +} + +function isIgnorable(error: chrome.runtime.LastError | undefined): boolean { + return ( + !error || + error.message === ERROR.NO_CONNECTION || + error.message === ERROR.PORT_CLOSED + ); +} diff --git a/src/js/view/api/search.ts b/src/api/search.ts similarity index 95% rename from src/js/view/api/search.ts rename to src/api/search.ts index 5cbe1ab..34d28d9 100644 --- a/src/js/view/api/search.ts +++ b/src/api/search.ts @@ -1,10 +1,3 @@ -type TSearchCommands = 'performSearch' | 'nextSearchResult' | 'cancelSearch'; - -export interface ISearchOptions { - cmd: TSearchCommands; - query: string | null; -} - interface ISearchState { foundEls: HTMLElement[]; query: string; diff --git a/src/js/view/api/time.ts b/src/api/time.ts similarity index 87% rename from src/js/view/api/time.ts rename to src/api/time.ts index 8fad814..ae7fd91 100644 --- a/src/js/view/api/time.ts +++ b/src/api/time.ts @@ -16,6 +16,10 @@ const intervals = [ { ge: 0, divisor: 1, text: 'now' }, ]; const rtf = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' }); +const dtf = new Intl.DateTimeFormat(undefined, { + dateStyle: 'full', + timeStyle: 'full', +}); /** * @param test {number} @@ -39,3 +43,7 @@ export function timeFromNow(test: number, now: number): string { return rv; } + +export function timeToString(time: number): string { + return dtf.format(time); +} diff --git a/src/api/toolkit.ts b/src/api/toolkit.ts new file mode 100644 index 0000000..979b5f5 --- /dev/null +++ b/src/api/toolkit.ts @@ -0,0 +1,14 @@ +export function hasValue(o: unknown): boolean { + return undefined !== o && null !== o; +} + +export async function SHA256(data: string): Promise { + const textAsBuffer = new TextEncoder().encode(data); + const hashBuffer = await window.crypto.subtle.digest('SHA-256', textAsBuffer); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const digest = hashArray + .map((b) => b.toString(16).padStart(2, '0').toUpperCase()) + .join(''); + + return digest; +} diff --git a/src/js/bundle/jsdiff-panel.js b/src/js/bundle/jsdiff-panel.js deleted file mode 100644 index ed38a5f..0000000 --- a/src/js/bundle/jsdiff-panel.js +++ /dev/null @@ -1 +0,0 @@ -(()=>{var e={842:(e,t,n)=>{"use strict";n.d(t,{Z:()=>a});var r=n(49),i=n.n(r),o=n(538),s=n.n(o)()(i());s.push([e.id,'.jsondiffpatch-delta{font-family:"Bitstream Vera Sans Mono","DejaVu Sans Mono",Monaco,Courier,monospace;font-size:12px;margin:0;padding:0 0 0 12px;display:inline-block}.jsondiffpatch-delta pre{font-family:"Bitstream Vera Sans Mono","DejaVu Sans Mono",Monaco,Courier,monospace;font-size:12px;margin:0;padding:0;display:inline-block}ul.jsondiffpatch-delta{list-style-type:none;padding:0 0 0 20px;margin:0}.jsondiffpatch-delta ul{list-style-type:none;padding:0 0 0 20px;margin:0}.jsondiffpatch-added .jsondiffpatch-property-name,.jsondiffpatch-added .jsondiffpatch-value pre,.jsondiffpatch-modified .jsondiffpatch-right-value pre,.jsondiffpatch-textdiff-added{background:#bfb}.jsondiffpatch-deleted .jsondiffpatch-property-name,.jsondiffpatch-deleted pre,.jsondiffpatch-modified .jsondiffpatch-left-value pre,.jsondiffpatch-textdiff-deleted{background:#fbb;text-decoration:line-through}.jsondiffpatch-unchanged,.jsondiffpatch-movedestination{color:gray}.jsondiffpatch-unchanged,.jsondiffpatch-movedestination>.jsondiffpatch-value{transition:all .5s;-webkit-transition:all .5s;overflow-y:hidden}.jsondiffpatch-unchanged-showing .jsondiffpatch-unchanged,.jsondiffpatch-unchanged-showing .jsondiffpatch-movedestination>.jsondiffpatch-value{max-height:100px}.jsondiffpatch-unchanged-hidden .jsondiffpatch-unchanged,.jsondiffpatch-unchanged-hidden .jsondiffpatch-movedestination>.jsondiffpatch-value{max-height:0}.jsondiffpatch-unchanged-hiding .jsondiffpatch-movedestination>.jsondiffpatch-value,.jsondiffpatch-unchanged-hidden .jsondiffpatch-movedestination>.jsondiffpatch-value{display:block}.jsondiffpatch-unchanged-visible .jsondiffpatch-unchanged,.jsondiffpatch-unchanged-visible .jsondiffpatch-movedestination>.jsondiffpatch-value{max-height:100px}.jsondiffpatch-unchanged-hiding .jsondiffpatch-unchanged,.jsondiffpatch-unchanged-hiding .jsondiffpatch-movedestination>.jsondiffpatch-value{max-height:0}.jsondiffpatch-unchanged-showing .jsondiffpatch-arrow,.jsondiffpatch-unchanged-hiding .jsondiffpatch-arrow{display:none}.jsondiffpatch-value{display:inline-block}.jsondiffpatch-property-name{display:inline-block;padding-right:5px;vertical-align:top}.jsondiffpatch-property-name:after{content:": "}.jsondiffpatch-child-node-type-array>.jsondiffpatch-property-name:after{content:": ["}.jsondiffpatch-child-node-type-array:after{content:"],"}div.jsondiffpatch-child-node-type-array:before{content:"["}div.jsondiffpatch-child-node-type-array:after{content:"]"}.jsondiffpatch-child-node-type-object>.jsondiffpatch-property-name:after{content:": {"}.jsondiffpatch-child-node-type-object:after{content:"},"}div.jsondiffpatch-child-node-type-object:before{content:"{"}div.jsondiffpatch-child-node-type-object:after{content:"}"}.jsondiffpatch-value pre:after{content:","}li:last-child>.jsondiffpatch-value pre:after,.jsondiffpatch-modified>.jsondiffpatch-left-value pre:after{content:""}.jsondiffpatch-modified .jsondiffpatch-value{display:inline-block}.jsondiffpatch-modified .jsondiffpatch-right-value{margin-left:5px}.jsondiffpatch-moved .jsondiffpatch-value{display:none}.jsondiffpatch-moved .jsondiffpatch-moved-destination{display:inline-block;background:#ffb;color:#888}.jsondiffpatch-moved .jsondiffpatch-moved-destination:before{content:" => "}ul.jsondiffpatch-textdiff{padding:0}.jsondiffpatch-textdiff-location{color:#bbb;display:inline-block;min-width:60px}.jsondiffpatch-textdiff-line{display:inline-block}.jsondiffpatch-textdiff-line-number:after{content:","}.jsondiffpatch-error{background:red;color:#fff;font-weight:bold}',""]);const a=s},891:(e,t,n)=>{"use strict";n.d(t,{Z:()=>a});var r=n(49),i=n.n(r),o=n(538),s=n.n(o)()(i());s.push([e.id,":root{--colour-background: #fff;--colour-text: #000;--colour-found: 0, 191, 255;--height-header: 1.625rem}body{margin:0;padding:0}.jsdiff-panel{height:100vh;background-color:var(--colour-background);color:var(--colour-text)}.jsdiff-panel .btn{height:var(--height-header);cursor:pointer;border:none;border-radius:0;outline:none;background-color:rgba(0,0,0,.03)}.jsdiff-panel .btn:hover{background-color:rgba(0,0,0,.3)}.jsdiff-panel .-header{border-bottom:1px solid #bbb;box-shadow:1px 2px 5px #bbb;display:flex;align-items:center;height:var(--height-header);margin-bottom:12px;min-width:512px}.jsdiff-panel .-header .-toolbox{display:flex;justify-content:center;align-items:center;padding-left:10px}.jsdiff-panel .-header .-toolbox .btn{margin:0 2px}.jsdiff-panel .-header .-toolbox .-last-updated{margin-left:10px;color:#bbb}.jsdiff-panel .-header .-toolbox .-last-updated .-value{font-weight:bold}.jsdiff-panel .-header .-badge{position:fixed;top:0;right:0;display:flex;flex-direction:column;align-items:center;padding:4px 4px}.jsdiff-panel .-header .-badge .-version{font-family:monospace}.jsdiff-panel .-header .-badge .-icon img{width:32px}.jsdiff-panel .-center{margin:0 auto;text-align:center}.jsdiff-panel .-match{display:flex;align-items:center;height:100%}.jsdiff-panel .-match .-center{font-size:26px;color:#bbb}.jsdiff-panel .-empty{display:flex;height:calc(100vh - var(--height-header));justify-content:center;align-items:center}.jsdiff-panel .-empty .-links{margin-top:16px;font-size:11px}.jsdiff-panel .-empty .-center{font-size:26px;color:#bbb}.jsdiff-panel .-delta{padding-top:10px}.jsdiff-panel .-delta .jsdiff-found{outline:1px solid rgba(var(--colour-found), 0.6);outline-offset:-1px}.jsdiff-panel .-delta .jsdiff-found.jsdiff-found-this{outline:2px solid rgb(var(--colour-found));outline-offset:-2px;animation:found_this 1s infinite}@keyframes found_this{0%{background-color:rgba(0,0,0,0)}50%{background-color:rgba(var(--colour-found), 0.2)}}",""]);const a=s},538:e=>{"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n="",r=void 0!==t[5];return t[4]&&(n+="@supports (".concat(t[4],") {")),t[2]&&(n+="@media ".concat(t[2]," {")),r&&(n+="@layer".concat(t[5].length>0?" ".concat(t[5]):""," {")),n+=e(t),r&&(n+="}"),t[2]&&(n+="}"),t[4]&&(n+="}"),n})).join("")},t.i=function(e,n,r,i,o){"string"==typeof e&&(e=[[null,e,void 0]]);var s={};if(r)for(var a=0;a0?" ".concat(f[5]):""," {").concat(f[1],"}")),f[5]=o),n&&(f[2]?(f[1]="@media ".concat(f[2]," {").concat(f[1],"}"),f[2]=n):f[2]=n),i&&(f[4]?(f[1]="@supports (".concat(f[4],") {").concat(f[1],"}"),f[4]=i):f[4]="".concat(i)),t.push(f))}},t}},49:e=>{"use strict";e.exports=function(e){return e[1]}},931:()=>{},908:function(e,t,n){!function(e,t){"use strict";t=t&&t.hasOwnProperty("default")?t.default:t;var n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},r=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},i=function(){function e(e,t){for(var n=0;ne[i-1][o]?--o:--i;return s}(o,e,t,i);return"string"==typeof e&&"string"==typeof t&&(s.sequence=s.sequence.join("")),s},M="function"==typeof Array.isArray?Array.isArray:function(e){return e instanceof Array},T="function"==typeof Array.prototype.indexOf?function(e,t){return e.indexOf(t)}:function(e,t){for(var n=e.length,r=0;r0&&f>0&&!t.objectHash&&"boolean"!=typeof t.matchByPosition&&(t.matchByPosition=!function(e,t,n,r){for(var i=0;i0)for(var j=0;j=0;t--){var l=r["_"+(n=o[t])],c=i.splice(n,1)[0];3===l[2]&&s.push({index:l[1],value:c})}var f=(s=s.sort(F("index"))).length;for(t=0;t0)for(t=0;tr?r++:s>=r&&at.length?e:t,a=e.length>t.length?t:e,l=s.indexOf(a);if(-1!=l)return o=[[1,s.substring(0,l)],[0,a],[1,s.substring(l+a.length)]],e.length>t.length&&(o[0][0]=o[2][0]=n),o;if(1==a.length)return[[n,e],[1,t]];var c=this.diff_halfMatch_(e,t);if(c){var f=c[0],u=c[1],d=c[2],h=c[3],p=c[4],v=this.diff_main(f,d,r,i),g=this.diff_main(u,h,r,i);return v.concat([[0,p]],g)}return r&&e.length>100&&t.length>100?this.diff_lineMode_(e,t,i):this.diff_bisect_(e,t,i)},t.prototype.diff_lineMode_=function(e,t,r){e=(u=this.diff_linesToChars_(e,t)).chars1,t=u.chars2;var i=u.lineArray,o=this.diff_main(e,t,!1,r);this.diff_charsToLines_(o,i),this.diff_cleanupSemantic(o),o.push([0,""]);for(var s=0,a=0,l=0,c="",f="";s=1&&l>=1){o.splice(s-a-l,a+l),s=s-a-l;for(var u,d=(u=this.diff_main(c,f,!1,r)).length-1;d>=0;d--)o.splice(s,0,u[d]);s+=u.length}l=0,a=0,c="",f=""}s++}return o.pop(),o},t.prototype.diff_bisect_=function(e,t,r){for(var i=e.length,o=t.length,s=Math.ceil((i+o)/2),a=s,l=2*s,c=new Array(l),f=new Array(l),u=0;ur);y++){for(var _=-y+p;_<=y-v;_+=2){for(var b=a+_,x=(A=_==-y||_!=y&&c[b-1]i)v+=2;else if(x>o)p+=2;else if(h&&(k=a+d-_)>=0&&k=(j=i-f[k]))return this.diff_bisectSplit_(e,t,A,x,r)}for(var w=-y+g;w<=y-m;w+=2){for(var j,k=a+w,C=(j=w==-y||w!=y&&f[k-1]i)m+=2;else if(C>o)g+=2;else if(!h){var A;if((b=a+d-w)>=0&&b=(j=i-j)))return this.diff_bisectSplit_(e,t,A,x,r)}}}return[[n,e],[1,t]]},t.prototype.diff_bisectSplit_=function(e,t,n,r,i){var o=e.substring(0,n),s=t.substring(0,r),a=e.substring(n),l=t.substring(r),c=this.diff_main(o,s,!1,i),f=this.diff_main(a,l,!1,i);return c.concat(f)},t.prototype.diff_linesToChars_=function(e,t){var n=[],r={};function i(e){for(var t="",i=0,o=-1,s=n.length;or?e=e.substring(n-r):nt.length?e:t,r=e.length>t.length?t:e;if(n.length<4||2*r.length=e.length?[r,o,s,a,f]:null}var s,a,l,c,f,u=o(n,r,Math.ceil(n.length/4)),d=o(n,r,Math.ceil(n.length/2));return u||d?(s=d?u&&u[4].length>d[4].length?u:d:u,e.length>t.length?(a=s[0],l=s[1],c=s[2],f=s[3]):(c=s[0],f=s[1],a=s[2],l=s[3]),[a,l,c,f,s[4]]):null},t.prototype.diff_cleanupSemantic=function(e){for(var t=!1,r=[],i=0,o=null,s=0,a=0,l=0,c=0,f=0;s0?r[i-1]:-1,a=0,l=0,c=0,f=0,o=null,t=!0)),s++;for(t&&this.diff_cleanupMerge(e),this.diff_cleanupSemanticLossless(e),s=1;s=p?(h>=u.length/2||h>=d.length/2)&&(e.splice(s,0,[0,d.substring(0,h)]),e[s-1][1]=u.substring(0,u.length-h),e[s+1][1]=d.substring(h),s++):(p>=u.length/2||p>=d.length/2)&&(e.splice(s,0,[0,u.substring(0,p)]),e[s-1][0]=1,e[s-1][1]=d.substring(0,d.length-p),e[s+1][0]=n,e[s+1][1]=u.substring(p),s++),s++}s++}},t.prototype.diff_cleanupSemanticLossless=function(e){function n(e,n){if(!e||!n)return 6;var r=e.charAt(e.length-1),i=n.charAt(0),o=r.match(t.nonAlphaNumericRegex_),s=i.match(t.nonAlphaNumericRegex_),a=o&&r.match(t.whitespaceRegex_),l=s&&i.match(t.whitespaceRegex_),c=a&&r.match(t.linebreakRegex_),f=l&&i.match(t.linebreakRegex_),u=c&&e.match(t.blanklineEndRegex_),d=f&&n.match(t.blanklineStartRegex_);return u||d?5:c||f?4:o&&!a&&l?3:a||l?2:o||s?1:0}for(var r=1;r=d&&(d=h,c=i,f=o,u=s)}e[r-1][1]!=c&&(c?e[r-1][1]=c:(e.splice(r-1,1),r--),e[r][1]=f,u?e[r+1][1]=u:(e.splice(r+1,1),r--))}r++}},t.nonAlphaNumericRegex_=/[^a-zA-Z0-9]/,t.whitespaceRegex_=/\s/,t.linebreakRegex_=/[\r\n]/,t.blanklineEndRegex_=/\n\r?\n$/,t.blanklineStartRegex_=/^\r?\n\r?\n/,t.prototype.diff_cleanupEfficiency=function(e){for(var t=!1,r=[],i=0,o=null,s=0,a=!1,l=!1,c=!1,f=!1;s0?r[i-1]:-1,c=f=!1),t=!0)),s++;t&&this.diff_cleanupMerge(e)},t.prototype.diff_cleanupMerge=function(e){e.push([0,""]);for(var t,r=0,i=0,o=0,s="",a="";r1?(0!==i&&0!==o&&(0!==(t=this.diff_commonPrefix(a,s))&&(r-i-o>0&&0==e[r-i-o-1][0]?e[r-i-o-1][1]+=a.substring(0,t):(e.splice(0,0,[0,a.substring(0,t)]),r++),a=a.substring(t),s=s.substring(t)),0!==(t=this.diff_commonSuffix(a,s))&&(e[r][1]=a.substring(a.length-t)+e[r][1],a=a.substring(0,a.length-t),s=s.substring(0,s.length-t))),0===i?e.splice(r-o,i+o,[1,a]):0===o?e.splice(r-i,i+o,[n,s]):e.splice(r-i-o,i+o,[n,s],[1,a]),r=r-i-o+(i?1:0)+(o?1:0)+1):0!==r&&0==e[r-1][0]?(e[r-1][1]+=e[r][1],e.splice(r,1)):r++,o=0,i=0,s="",a=""}""===e[e.length-1][1]&&e.pop();var l=!1;for(r=1;rt));r++)s=i,a=o;return e.length!=r&&e[r][0]===n?a:a+(t-s)},t.prototype.diff_prettyHtml=function(e){for(var t=[],r=/&/g,i=//g,s=/\n/g,a=0;a");switch(l){case 1:t[a]=''+c+"";break;case n:t[a]=''+c+"";break;case 0:t[a]=""+c+""}}return t.join("")},t.prototype.diff_text1=function(e){for(var t=[],n=0;nthis.Match_MaxBits)throw new Error("Pattern too long for this browser.");var r=this.match_alphabet_(t),i=this;function o(e,r){var o=e/t.length,s=Math.abs(n-r);return i.Match_Distance?o+s/i.Match_Distance:s?1:o}var s=this.Match_Threshold,a=e.indexOf(t,n);-1!=a&&(s=Math.min(o(0,a),s),-1!=(a=e.lastIndexOf(t,n+t.length))&&(s=Math.min(o(0,a),s)));var l,c,f=1<=p;m--){var y=r[e.charAt(m-1)];if(g[m]=0===h?(g[m+1]<<1|1)&y:(g[m+1]<<1|1)&y|(u[m+1]|u[m])<<1|1|u[m+1],g[m]&f){var _=o(h,m-1);if(_<=s){if(s=_,!((a=m-1)>n))break;p=Math.max(1,2*n-a)}}}if(o(h+1,n)>s)break;u=g}return a},t.prototype.match_alphabet_=function(e){for(var t={},n=0;n2&&(this.diff_cleanupSemantic(s),this.diff_cleanupEfficiency(s));else if(e&&"object"==typeof e&&void 0===r&&void 0===i)s=e,o=this.diff_text1(s);else if("string"==typeof e&&r&&"object"==typeof r&&void 0===i)o=e,s=r;else{if("string"!=typeof e||"string"!=typeof r||!i||"object"!=typeof i)throw new Error("Unknown call format to patch_make.");o=e,s=i}if(0===s.length)return[];for(var a=[],l=new t.patch_obj,c=0,f=0,u=0,d=o,h=o,p=0;p=2*this.Patch_Margin&&c&&(this.patch_addContext_(l,d),a.push(l),l=new t.patch_obj,c=0,d=h,f=u)}1!==v&&(f+=g.length),v!==n&&(u+=g.length)}return c&&(this.patch_addContext_(l,d),a.push(l)),a},t.prototype.patch_deepCopy=function(e){for(var n=[],r=0;rthis.Match_MaxBits?-1!=(a=this.match_main(t,f.substring(0,this.Match_MaxBits),c))&&(-1==(u=this.match_main(t,f.substring(f.length-this.Match_MaxBits),c+f.length-this.Match_MaxBits))||a>=u)&&(a=-1):a=this.match_main(t,f,c),-1==a)o[s]=!1,i-=e[s].length2-e[s].length1;else if(o[s]=!0,i=a-c,f==(l=-1==u?t.substring(a,a+f.length):t.substring(a,u+this.Match_MaxBits)))t=t.substring(0,a)+this.diff_text2(e[s].diffs)+t.substring(a+f.length);else{var d=this.diff_main(f,l,!1);if(f.length>this.Match_MaxBits&&this.diff_levenshtein(d)/f.length>this.Patch_DeleteThreshold)o[s]=!1;else{this.diff_cleanupSemanticLossless(d);for(var h,p=0,v=0;vo[0][1].length){var s=t-o[0][1].length;o[0][1]=n.substring(o[0][1].length)+o[0][1],i.start1-=s,i.start2-=s,i.length1+=s,i.length2+=s}return 0==(o=(i=e[e.length-1]).diffs).length||0!=o[o.length-1][0]?(o.push([0,n]),i.length1+=t,i.length2+=t):t>o[o.length-1][1].length&&(s=t-o[o.length-1][1].length,o[o.length-1][1]+=n.substring(0,s),i.length1+=s,i.length2+=s),n},t.prototype.patch_splitMax=function(e){for(var r=this.Match_MaxBits,i=0;i2*r?(c.length1+=d.length,s+=d.length,f=!1,c.diffs.push([u,d]),o.diffs.shift()):(d=d.substring(0,r-c.length1-this.Patch_Margin),c.length1+=d.length,s+=d.length,0===u?(c.length2+=d.length,a+=d.length):f=!1,c.diffs.push([u,d]),d==o.diffs[0][1]?o.diffs.shift():o.diffs[0][1]=o.diffs[0][1].substring(d.length))}l=(l=this.diff_text2(c.diffs)).substring(l.length-this.Patch_Margin);var h=this.diff_text1(o.diffs).substring(0,this.Patch_Margin);""!==h&&(c.length1+=h.length,c.length2+=h.length,0!==c.diffs.length&&0===c.diffs[c.diffs.length-1][0]?c.diffs[c.diffs.length-1][1]+=h:c.diffs.push([0,h])),f||e.splice(++i,0,c)}}},t.prototype.patch_toText=function(e){for(var t=[],n=0;n'+t+"")}},{key:"formatValue",value:function(e,t){e.out("
"+oe(JSON.stringify(t,null,2))+"
")}},{key:"formatTextDiffString",value:function(e,t){var n=this.parseTextDiff(t);e.out('
    ');for(var r=0,i=n.length;r
    '+o.location.line+''+o.location.chr+'
    ');for(var s=o.pieces,a=0,l=s.length;a'+oe(decodeURI(c.text))+"")}e.out("
    ")}e.out("
")}},{key:"rootBegin",value:function(e,t,n){var r="jsondiffpatch-"+t+(n?" jsondiffpatch-child-node-type-"+n:"");e.out('
')}},{key:"rootEnd",value:function(e){e.out("
"+(e.hasArrows?' @@ -209,6 +261,7 @@ body { height: var(--height-header); margin-bottom: 12px; min-width: 512px; + user-select: none; .-toolbox { display: flex; @@ -221,6 +274,7 @@ body { } .-last-updated { + cursor: default; margin-left: 10px; color: #bbbbbb; @@ -230,6 +284,14 @@ body { } } + .-last-error { + display: flex; + justify-content: center; + align-items: center; + padding-left: 10px; + color: rgb(182, 33, 33); + } + .-badge { position: fixed; top: 0; diff --git a/src/view/progress-indicator.vue b/src/view/progress-indicator.vue new file mode 100644 index 0000000..3ccc2dc --- /dev/null +++ b/src/view/progress-indicator.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/tsconfig.json b/tsconfig.json index 27bca07..3d5ee55 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,8 @@ "allowJs": true, "checkJs": false, "strict": true, + "strictNullChecks": true, + "noImplicitAny": true, "skipLibCheck": true, "moduleResolution": "Node", "useDefineForClassFields": true, @@ -14,7 +16,12 @@ "resolveJsonModule": true, "allowSyntheticDefaultImports": true, "esModuleInterop": true, - "typeRoots": ["node_modules/@types"] + "typeRoots": ["node_modules/@types", "src/@types"], + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } }, - "exclude": [".ts-built", "src/js/bundle"] + "include": ["src"], + "exclude": [".ts-built", "bundle"] } diff --git a/webpack.config.js b/webpack.config.js index 075c487..00fe88b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -4,6 +4,7 @@ import { CleanWebpackPlugin } from 'clean-webpack-plugin'; import { VueLoaderPlugin } from 'vue-loader'; import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; import { fileURLToPath } from 'url'; +import TerserPlugin from 'terser-webpack-plugin'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -19,30 +20,35 @@ export default function (env, op) { }, entry: { - 'jsdiff-panel': './src/js/view/app.js', + 'jsdiff-devtools': './src/jsdiff-devtools.ts', + 'jsdiff-panel': './src/view/app.js', + 'jsdiff-proxy': './src/jsdiff-proxy.ts', + 'jsdiff-console': './src/jsdiff-console.ts', }, output: { filename: '[name].js', - path: path.resolve(__dirname, 'src/js/bundle'), + path: path.resolve(__dirname, 'bundle/js'), }, resolve: { extensions: ['.ts', '.js'], modules: [path.resolve(__dirname, 'src/js'), 'node_modules'], - alias: {}, + alias: { + '@': path.resolve(__dirname, 'src'), + }, }, plugins: [ new CleanWebpackPlugin(), new VueLoaderPlugin(), // http://127.0.0.1:8888 - !isProd - ? new BundleAnalyzerPlugin({ + isProd + ? () => {} + : new BundleAnalyzerPlugin({ openAnalyzer: false, logLevel: 'silent', - }) - : () => {}, + }), new webpack.DefinePlugin({ __VUE_OPTIONS_API__: 'true', __VUE_PROD_DEVTOOLS__: 'false', @@ -69,6 +75,8 @@ export default function (env, op) { optimization: { splitChunks: false, + minimize: isProd, + minimizer: [new TerserPlugin()], }, devtool: isProd ? false : 'source-map',