hyper-undo 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/dist/hyper-undo.min.js +1 -1
- package/package.json +1 -1
- package/src/extension-noise.js +63 -0
- package/src/filter.js +21 -8
- package/src/index.js +4 -0
- package/src/primitives.js +9 -0
- package/src/scope.js +21 -0
package/CHANGELOG.md
CHANGED
package/dist/hyper-undo.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var hyperundo=(()=>{var x=Object.defineProperty;var J=Object.getOwnPropertyDescriptor;var Q=Object.getOwnPropertyNames;var X=Object.prototype.hasOwnProperty;var Y=(e,r)=>{for(var o in r)x(e,o,{get:r[o],enumerable:!0})},$=(e,r,o,t)=>{if(r&&typeof r=="object"||typeof r=="function")for(let n of Q(r))!X.call(e,n)&&n!==o&&x(e,n,{get:()=>r[n],enumerable:!(t=J(r,n))||t.enumerable});return e};var ee=e=>$(x({},"__esModule",{value:!0}),e);var ie={};Y(ie,{default:()=>oe,undo:()=>V});function te(e,r=null){let o=[];if(e.type==="attributes"){let t=e.target,n=e.attributeName,i=e.oldValue,a=t.getAttribute(n);return i==null&&a!=null?o.push({kind:"attr-add",target:t,name:n,newValue:a}):i!=null&&a==null?o.push({kind:"attr-remove",target:t,name:n,oldValue:i}):i!==a&&o.push({kind:"attr-set",target:t,name:n,oldValue:i,newValue:a}),o}if(e.type==="characterData")return o.push({kind:"text",target:e.target,oldValue:e.oldValue,newValue:e.target.data}),o;if(e.type==="childList"){let t=e.target,n=e.nextSibling,i=r?Array.from(e.addedNodes).filter(u=>!r(u)):Array.from(e.addedNodes),a=r?Array.from(e.removedNodes).filter(u=>!r(u)):Array.from(e.removedNodes);return i.length>0&&o.push({kind:"add",parent:t,nodes:i,before:n}),a.length>0&&o.push({kind:"remove",parent:t,nodes:a,before:n}),o}return o}function S(e,r=null){let o=[],t=new Map;for(let n of e){if(n.type==="attributes"){let i=n.target,a=n.attributeName,u=t.get(i);u||(u=new Map,t.set(i,u)),u.has(a)||(u.set(a,{idx:o.length,oldValue:n.oldValue}),o.push(null));continue}for(let i of te(n,r))o.push(i)}for(let[n,i]of t)for(let[a,u]of i){let l=u.oldValue,p=n.getAttribute(a),f=null;l===p?f=null:l==null?f={kind:"attr-add",target:n,name:a,newValue:p}:p==null?f={kind:"attr-remove",target:n,name:a,oldValue:l}:f={kind:"attr-set",target:n,name:a,oldValue:l,newValue:p},o[u.idx]=f}return o.filter(n=>n!=null)}function D(e){switch(e.kind){case"attr-set":case"attr-add":e.target.setAttribute(e.name,e.newValue);return;case"attr-remove":e.target.removeAttribute(e.name);return;case"text":e.target.data=e.newValue;return;case"add":for(let r of e.nodes)e.before&&e.before.parentNode===e.parent?e.parent.insertBefore(r,e.before):e.parent.appendChild(r);return;case"remove":for(let r of e.nodes)r.parentNode===e.parent&&e.parent.removeChild(r);return}}function N(e){switch(e.kind){case"attr-set":e.target.setAttribute(e.name,e.oldValue);return;case"attr-add":e.target.removeAttribute(e.name);return;case"attr-remove":e.target.setAttribute(e.name,e.oldValue);return;case"text":e.target.data=e.oldValue;return;case"add":for(let r of e.nodes)r.parentNode===e.parent&&e.parent.removeChild(r);return;case"remove":for(let r of e.nodes)e.before&&e.before.parentNode===e.parent?e.parent.insertBefore(r,e.before):e.parent.appendChild(r);return}}var re=["mutations-ignore","save-remove","save-ignore","save-freeze"];function C(e,r,o){let t=e&&e.nodeType!==1?e.parentElement:e;for(;t&&t.nodeType===1;){for(let n of re)if(t.hasAttribute&&t.hasAttribute(n))return!0;t=t.parentElement}if(r&&o&&o.type==="attributes")try{if(r(o.attributeName,o.target))return!0}catch{}return!1}function L(){let e=new Map;function r(n,i){let a=e.get(n);return a||(a=new Set,e.set(n,a)),a.add(i),()=>o(n,i)}function o(n,i){let a=e.get(n);a&&a.delete(i)}function t(n,i){let a=e.get(n);if(a)for(let u of Array.from(a))try{u(i)}catch(l){console.error("[hyper-undo] listener threw",l)}}return{on:r,off:o,emit:t}}var ne={maxHistory:100,idleWindowMs:500,idleLabel:"Edit",ignoreAttribute:null,debug:!1};function E(e){let r={...ne,...e};if(!r.scope)throw new Error("hyper-undo: scope is required");let o=L(),t=[],n=[],i=[],a=null,u=null,l=0,p=!1;function f(...d){r.debug&&console.log("[hyper-undo]",...d)}function F(){p||(p=!0,u=new MutationObserver(y),u.observe(r.scope,{childList:!0,attributes:!0,characterData:!0,subtree:!0,attributeOldValue:!0,characterDataOldValue:!0}),f("started"))}function b(){p&&(p=!1,u&&(u.disconnect(),u=null),g(),t.length=0,n.length=0,i=[],l=0,f("stopped"))}function T(){let d=r.scope;return!!(d&&d.nodeType===1&&d.isConnected===!1)}function y(d){if(l>0)return;let c=I(d);if(c.length>0){for(let m of c)i.push(m);z()}}function I(d){let c=d.filter(m=>!C(m.target,r.ignoreAttribute,m));return S(c,m=>C(m,r.ignoreAttribute))}function z(){g(),a=setTimeout(P,r.idleWindowMs)}function g(){a!=null&&(clearTimeout(a),a=null)}function P(){if(a=null,i.length===0)return;let d=i;i=[],v(r.idleLabel,d)}function v(d,c){for(t.push({label:d,timestamp:Date.now(),primitives:c}),n.length=0;t.length>r.maxHistory;)t.shift();f("commit",d,"primitives:",c.length),o.emit("commit")}function B(d,c){if(!p)throw new Error("hyper-undo: scope not started");y(u.takeRecords()),w();let m=c();if(m&&typeof m.then=="function")throw new Error("hyper-undo: commit() fn must be synchronous; returned a Promise");if(y(u.takeRecords()),i.length===0){o.emit("commit");return}g();let j=i;i=[],v(d,j)}function H(d){if(!p)throw new Error("hyper-undo: scope not started");if(!u)return;if(l>1){u.takeRecords();return}w();let c=I(u.takeRecords());c.length!==0&&(g(),v(d,c))}function W(){u&&u.takeRecords()}function w(){l===0&&u&&y(u.takeRecords()),i.length!==0&&(g(),P())}function k(){l===0&&u&&y(u.takeRecords()),l++,f("paused",l)}function A(){l!==0&&(l--,l===0&&u&&u.takeRecords(),f("resumed",l))}function q(){if(T()){b();return}w();let d=t.pop();if(d){k();try{for(let c=d.primitives.length-1;c>=0;c--)try{N(d.primitives[c])}catch(m){f("undo primitive failed (continuing)",m)}}finally{A()}n.push(d),f("undo",d.label),o.emit("undo")}}function G(){if(T()){b();return}w();let d=n.pop();if(d){k();try{for(let c of d.primitives)try{D(c)}catch(m){f("redo primitive failed (continuing)",m)}}finally{A()}t.push(d),f("redo",d.label),o.emit("redo")}}function Z(){u&&u.takeRecords(),t.length=0,n.length=0,i=[],g(),f("cleared"),o.emit("clear")}return{_config:r,start:F,stop:b,commit:B,commitCaptured:H,discardCaptured:W,flush:w,undo:q,redo:G,clear:Z,pause:k,resume:A,on:o.on,off:o.off,get canUndo(){return t.length>0},get canRedo(){return n.length>0},get isPaused(){return l>0},get history(){return t.map(d=>({label:d.label,timestamp:d.timestamp}))},get scope(){return r.scope}}}function _(e,r){let o=t=>{let n=e.scope;if(n&&n.nodeType===1&&n.isConnected===!1){r&&r();return}let i=t.target,a=e._config.shadowKeydownIn;if(i&&i.closest&&Array.isArray(a))for(let p of a)try{if(i.closest(p))return}catch{}if(!(t.metaKey||t.ctrlKey))return;let l=(t.key||"").toLowerCase();if(l==="z"&&!t.shiftKey){t.preventDefault(),t.stopPropagation(),e.undo();return}if(l==="z"&&t.shiftKey){t.preventDefault(),t.stopPropagation(),e.redo();return}if(l==="y"&&!t.shiftKey){t.preventDefault(),t.stopPropagation(),e.redo();return}};return window.addEventListener("keydown",o,!0),()=>window.removeEventListener("keydown",o,!0)}var K=[".CodeMirror",".cm-editor",".monaco-editor",".ace_editor",".ql-editor",".tiptap",".ProseMirror"],s=null,R=null,M=null,h=null;function U(e,r){if(h){if(h.inst===e)return h.cleanup;throw new Error("hyper-undo: another scope already owns the global Cmd+Z binding (bindKeys: true). Stop it first, or use bindKeys: false for additional scopes.")}let o=_(e,r);return h={inst:e,cleanup:o},o}function O(e){h&&h.inst===e&&(h.cleanup(),h=null)}var V={defaults:{shadowKeydownIn:K},start(e={}){let r=e.scope||(typeof document<"u"?document.body:null);if(!r)throw new Error("hyper-undo: no scope (need document.body or explicit { scope })");if(s){if(R===r)return console.warn("[hyper-undo] start() called again on existing singleton; ignoring new options"),s;throw new Error("hyper-undo: start() called with a different scope while the singleton is already started. Use undo.create({ scope }) for additional scopes, or call undo.stop() first.")}let o={...e,scope:r,shadowKeydownIn:e.shadowKeydownIn||K};return s=E(o),s.start(),R=r,e.bindKeys!==!1&&(M=U(s,()=>V.stop())),s},stop(){s&&(O(s),M=null,s.stop(),s=null,R=null)},create(e={}){let r=e.scope||(typeof document<"u"?document.body:null);if(!r)throw new Error("hyper-undo: no scope (need document.body or explicit { scope })");let o={...e,scope:r,shadowKeydownIn:e.shadowKeydownIn||K},t=E(o),n=null,i={_config:t._config,commit:t.commit,commitCaptured:t.commitCaptured,discardCaptured:t.discardCaptured,flush:t.flush,undo:t.undo,redo:t.redo,clear:t.clear,pause:t.pause,resume:t.resume,on:t.on,off:t.off,get canUndo(){return t.canUndo},get canRedo(){return t.canRedo},get isPaused(){return t.isPaused},get history(){return t.history},get scope(){return t.scope},start(){e.bindKeys&&(n=U(t,()=>i.stop())),t.start()},stop(){O(t),n=null,t.stop()}};return i},commit(e,r){return s?s.commit(e,r):void 0},commitCaptured(e){return s?s.commitCaptured(e):void 0},discardCaptured(){return s?s.discardCaptured():void 0},undo(){return s?s.undo():void 0},redo(){return s?s.redo():void 0},clear(){return s?s.clear():void 0},pause(){return s?s.pause():void 0},resume(){return s?s.resume():void 0},flush(){return s?s.flush():void 0},on(e,r){return s?s.on(e,r):void 0},off(e,r){return s?s.off(e,r):void 0},get canUndo(){return s?s.canUndo:!1},get canRedo(){return s?s.canRedo:!1},get history(){return s?s.history:[]},get isPaused(){return s?s.isPaused:!1}};var oe=V;return ee(ie);})();
|
|
1
|
+
var hyperundo=(()=>{var T=Object.defineProperty;var Y=Object.getOwnPropertyDescriptor;var ee=Object.getOwnPropertyNames;var te=Object.prototype.hasOwnProperty;var re=(e,r)=>{for(var o in r)T(e,o,{get:r[o],enumerable:!0})},ne=(e,r,o,t)=>{if(r&&typeof r=="object"||typeof r=="function")for(let n of ee(r))!te.call(e,n)&&n!==o&&T(e,n,{get:()=>r[n],enumerable:!(t=Y(r,n))||t.enumerable});return e};var oe=e=>ne(T({},"__esModule",{value:!0}),e);var ce={};re(ce,{default:()=>le,undo:()=>S});function ae(e,r=null){let o=[];if(e.type==="attributes"){let t=e.target,n=e.attributeName,i=e.oldValue,u=t.getAttribute(n);return i==null&&u!=null?o.push({kind:"attr-add",target:t,name:n,newValue:u}):i!=null&&u==null?o.push({kind:"attr-remove",target:t,name:n,oldValue:i}):i!==u&&o.push({kind:"attr-set",target:t,name:n,oldValue:i,newValue:u}),o}if(e.type==="characterData")return o.push({kind:"text",target:e.target,oldValue:e.oldValue,newValue:e.target.data}),o;if(e.type==="childList"){let t=e.target,n=e.nextSibling,i=r?Array.from(e.addedNodes).filter(d=>!r(d)):Array.from(e.addedNodes),u=r?Array.from(e.removedNodes).filter(d=>!r(d)):Array.from(e.removedNodes);return i.length>0&&o.push({kind:"add",parent:t,nodes:i,before:n}),u.length>0&&o.push({kind:"remove",parent:t,nodes:u,before:n}),o}return o}function P(e,r=null){let o=[],t=new Map;for(let n of e){if(n.type==="attributes"){let i=n.target,u=n.attributeName,d=t.get(i);d||(d=new Map,t.set(i,d)),d.has(u)||(d.set(u,{idx:o.length,oldValue:n.oldValue}),o.push(null));continue}for(let i of ae(n,r))o.push(i)}for(let[n,i]of t)for(let[u,d]of i){let l=d.oldValue,m=n.getAttribute(u),f=null;l===m?f=null:l==null?f={kind:"attr-add",target:n,name:u,newValue:m}:m==null?f={kind:"attr-remove",target:n,name:u,oldValue:l}:f={kind:"attr-set",target:n,name:u,oldValue:l,newValue:m},o[d.idx]=f}return o.filter(n=>n!=null)}function L(e){switch(e.kind){case"attr-set":case"attr-add":e.target.setAttribute(e.name,e.newValue);return;case"attr-remove":e.target.removeAttribute(e.name);return;case"text":e.target.data=e.newValue;return;case"value":e.target[e.prop]=e.newValue;return;case"add":for(let r of e.nodes)e.before&&e.before.parentNode===e.parent?e.parent.insertBefore(r,e.before):e.parent.appendChild(r);return;case"remove":for(let r of e.nodes)r.parentNode===e.parent&&e.parent.removeChild(r);return}}function D(e){switch(e.kind){case"attr-set":e.target.setAttribute(e.name,e.oldValue);return;case"attr-add":e.target.removeAttribute(e.name);return;case"attr-remove":e.target.setAttribute(e.name,e.oldValue);return;case"text":e.target.data=e.oldValue;return;case"value":e.target[e.prop]=e.oldValue;return;case"add":for(let r of e.nodes)r.parentNode===e.parent&&e.parent.removeChild(r);return;case"remove":for(let r of e.nodes)e.before&&e.before.parentNode===e.parent?e.parent.insertBefore(r,e.before):e.parent.appendChild(r);return}}var ie=['[src^="chrome-extension://"]','[href^="chrome-extension://"]','[src^="moz-extension://"]','[href^="moz-extension://"]','[src^="safari-web-extension://"]','[href^="safari-web-extension://"]','[id="1p-menu-live-region"]',"com-1password-button","com-1password-menu","com-1password-notification","grammarly-extension","grammarly-desktop-integration","lt-div","lt-card","lt-toolbar","lt-highlighter","[data-lastpass-root]","[data-lastpass-icon-root]","[data-dashlane-shadowhost]","[data-klarna-extension]",'[class*="klarna-extension"]',".skiptranslate","#goog-gt-tt",".goog-te-banner-frame",".goog-te-balloon-frame","style.darkreader","style[data-darkreader-mode]","style[data-darkreader-scheme]","#loom-companion-mv3","loom-container","[ext-id]"],se=["data-grammarly","data-lt","data-1p","data-com-onepassword","data-lastpass","data-lp","data-kwimpala","data-bitwarden","data-bw","data-dashlane","data-np","data-protonpass","data-proton-pass","data-keeper","data-rf","data-roboform","data-honey","data-rakuten","data-capital-one","data-wikibuy","data-klarna","data-darkreader","data-evernote","data-pocket","data-pin"],M=ie.join(","),U=new RegExp("^(?:"+se.join("|")+")(?:-|$)");var ue=["mutations-ignore","save-remove","save-ignore","save-freeze"];function N(e,r,o){let t=e&&e.nodeType!==1?e.parentElement:e;if(t&&t.closest&&t.closest(M))return!0;for(;t&&t.nodeType===1;){for(let n of ue)if(t.hasAttribute&&t.hasAttribute(n))return!0;t=t.parentElement}if(o&&o.type==="attributes"){if(o.attributeName&&U.test(o.attributeName.toLowerCase()))return!0;if(r)try{if(r(o.attributeName,o.target))return!0}catch{}}return!1}function X(){let e=new Map;function r(n,i){let u=e.get(n);return u||(u=new Set,e.set(n,u)),u.add(i),()=>o(n,i)}function o(n,i){let u=e.get(n);u&&u.delete(i)}function t(n,i){let u=e.get(n);if(u)for(let d of Array.from(u))try{d(i)}catch(l){console.error("[hyper-undo] listener threw",l)}}return{on:r,off:o,emit:t}}var de={maxHistory:100,idleWindowMs:500,idleLabel:"Edit",ignoreAttribute:null,debug:!1};function A(e){let r={...de,...e};if(!r.scope)throw new Error("hyper-undo: scope is required");let o=X(),t=[],n=[],i=[],u=null,d=null,l=0,m=!1;function f(...s){r.debug&&console.log("[hyper-undo]",...s)}function H(){m||(m=!0,d=new MutationObserver(y),d.observe(r.scope,{childList:!0,attributes:!0,characterData:!0,subtree:!0,attributeOldValue:!0,characterDataOldValue:!0}),f("started"))}function E(){m&&(m=!1,d&&(d.disconnect(),d=null),g(),t.length=0,n.length=0,i=[],l=0,f("stopped"))}function V(){let s=r.scope;return!!(s&&s.nodeType===1&&s.isConnected===!1)}function y(s){if(l>0)return;let c=_(s);if(c.length>0){for(let p of c)i.push(p);I()}}function _(s){let c=s.filter(p=>!N(p.target,r.ignoreAttribute,p));return P(c,p=>N(p,r.ignoreAttribute))}function I(){g(),u=setTimeout(K,r.idleWindowMs)}function g(){u!=null&&(clearTimeout(u),u=null)}function K(){if(u=null,i.length===0)return;let s=i;i=[],k(r.idleLabel,s)}function k(s,c){for(t.push({label:s,timestamp:Date.now(),primitives:c}),n.length=0;t.length>r.maxHistory;)t.shift();f("commit",s,"primitives:",c.length),o.emit("commit")}function W(s,c){if(!m)throw new Error("hyper-undo: scope not started");y(d.takeRecords()),w();let p=c();if(p&&typeof p.then=="function")throw new Error("hyper-undo: commit() fn must be synchronous; returned a Promise");if(y(d.takeRecords()),i.length===0){o.emit("commit");return}g();let b=i;i=[],k(s,b)}function j(s){if(!m)throw new Error("hyper-undo: scope not started");if(!d)return;if(l>1){d.takeRecords();return}w();let c=_(d.takeRecords());c.length!==0&&(g(),k(s,c))}function G(){d&&d.takeRecords()}function O(s){!m||l>0||!s||(i.push(s),I())}function Z(s,{prop:c="value",oldValue:p,newValue:b}={}){!s||p===b||O({kind:"value",target:s,prop:c,oldValue:p,newValue:b})}function w(){l===0&&d&&y(d.takeRecords()),i.length!==0&&(g(),K())}function v(){l===0&&d&&y(d.takeRecords()),l++,f("paused",l)}function x(){l!==0&&(l--,l===0&&d&&d.takeRecords(),f("resumed",l))}function $(){if(V()){E();return}w();let s=t.pop();if(s){v();try{for(let c=s.primitives.length-1;c>=0;c--)try{D(s.primitives[c])}catch(p){f("undo primitive failed (continuing)",p)}}finally{x()}n.push(s),f("undo",s.label),o.emit("undo")}}function J(){if(V()){E();return}w();let s=n.pop();if(s){v();try{for(let c of s.primitives)try{L(c)}catch(p){f("redo primitive failed (continuing)",p)}}finally{x()}t.push(s),f("redo",s.label),o.emit("redo")}}function Q(){d&&d.takeRecords(),t.length=0,n.length=0,i=[],g(),f("cleared"),o.emit("clear")}return{_config:r,start:H,stop:E,commit:W,commitCaptured:j,discardCaptured:G,flush:w,record:O,recordValue:Z,undo:$,redo:J,clear:Q,pause:v,resume:x,on:o.on,off:o.off,get canUndo(){return t.length>0},get canRedo(){return n.length>0},get isPaused(){return l>0},get history(){return t.map(s=>({label:s.label,timestamp:s.timestamp}))},get scope(){return r.scope}}}function q(e,r){let o=t=>{let n=e.scope;if(n&&n.nodeType===1&&n.isConnected===!1){r&&r();return}let i=t.target,u=e._config.shadowKeydownIn;if(i&&i.closest&&Array.isArray(u))for(let m of u)try{if(i.closest(m))return}catch{}if(!(t.metaKey||t.ctrlKey))return;let l=(t.key||"").toLowerCase();if(l==="z"&&!t.shiftKey){t.preventDefault(),t.stopPropagation(),e.undo();return}if(l==="z"&&t.shiftKey){t.preventDefault(),t.stopPropagation(),e.redo();return}if(l==="y"&&!t.shiftKey){t.preventDefault(),t.stopPropagation(),e.redo();return}};return window.addEventListener("keydown",o,!0),()=>window.removeEventListener("keydown",o,!0)}var R=[".CodeMirror",".cm-editor",".monaco-editor",".ace_editor",".ql-editor",".tiptap",".ProseMirror"],a=null,C=null,z=null,h=null;function F(e,r){if(h){if(h.inst===e)return h.cleanup;throw new Error("hyper-undo: another scope already owns the global Cmd+Z binding (bindKeys: true). Stop it first, or use bindKeys: false for additional scopes.")}let o=q(e,r);return h={inst:e,cleanup:o},o}function B(e){h&&h.inst===e&&(h.cleanup(),h=null)}var S={defaults:{shadowKeydownIn:R},start(e={}){let r=e.scope||(typeof document<"u"?document.body:null);if(!r)throw new Error("hyper-undo: no scope (need document.body or explicit { scope })");if(a){if(C===r)return console.warn("[hyper-undo] start() called again on existing singleton; ignoring new options"),a;throw new Error("hyper-undo: start() called with a different scope while the singleton is already started. Use undo.create({ scope }) for additional scopes, or call undo.stop() first.")}let o={...e,scope:r,shadowKeydownIn:e.shadowKeydownIn||R};return a=A(o),a.start(),C=r,e.bindKeys!==!1&&(z=F(a,()=>S.stop())),a},stop(){a&&(B(a),z=null,a.stop(),a=null,C=null)},create(e={}){let r=e.scope||(typeof document<"u"?document.body:null);if(!r)throw new Error("hyper-undo: no scope (need document.body or explicit { scope })");let o={...e,scope:r,shadowKeydownIn:e.shadowKeydownIn||R},t=A(o),n=null,i={_config:t._config,commit:t.commit,commitCaptured:t.commitCaptured,discardCaptured:t.discardCaptured,flush:t.flush,record:t.record,recordValue:t.recordValue,undo:t.undo,redo:t.redo,clear:t.clear,pause:t.pause,resume:t.resume,on:t.on,off:t.off,get canUndo(){return t.canUndo},get canRedo(){return t.canRedo},get isPaused(){return t.isPaused},get history(){return t.history},get scope(){return t.scope},start(){e.bindKeys&&(n=F(t,()=>i.stop())),t.start()},stop(){B(t),n=null,t.stop()}};return i},commit(e,r){return a?a.commit(e,r):void 0},commitCaptured(e){return a?a.commitCaptured(e):void 0},discardCaptured(){return a?a.discardCaptured():void 0},record(e){return a?a.record(e):void 0},recordValue(e,r){return a?a.recordValue(e,r):void 0},undo(){return a?a.undo():void 0},redo(){return a?a.redo():void 0},clear(){return a?a.clear():void 0},pause(){return a?a.pause():void 0},resume(){return a?a.resume():void 0},flush(){return a?a.flush():void 0},on(e,r){return a?a.on(e,r):void 0},off(e,r){return a?a.off(e,r):void 0},get canUndo(){return a?a.canUndo:!1},get canRedo(){return a?a.canRedo:!1},get history(){return a?a.history:[]},get isPaused(){return a?a.isPaused:!1}};var le=S;return oe(ce);})();
|
package/package.json
CHANGED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* extension-noise.js — browser-extension DOM noise the page should never own.
|
|
3
|
+
*
|
|
4
|
+
* Extensions touch the live page in two distinct ways, handled differently so a
|
|
5
|
+
* save never loses real content:
|
|
6
|
+
* 1. Standalone elements they own (password-manager menus, Grammarly overlays,
|
|
7
|
+
* translate banners, extension iframes/scripts) — remove them entirely.
|
|
8
|
+
* 2. Marker attributes they stamp onto YOUR real inputs/forms — strip just the
|
|
9
|
+
* attribute and keep the element.
|
|
10
|
+
*
|
|
11
|
+
* The selectors are not stable public APIs; extend both lists from real captures.
|
|
12
|
+
* Keep this file byte-identical to the copy in the other package (hyperclayjs and
|
|
13
|
+
* hyper-undo each carry one) — their save/undo observers must agree, or one will
|
|
14
|
+
* record/save what the other ignores.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// Standalone elements browser extensions inject into the page — remove them entirely.
|
|
18
|
+
export const EXTENSION_NODE_SELECTORS = [
|
|
19
|
+
'[src^="chrome-extension://"]', '[href^="chrome-extension://"]',
|
|
20
|
+
'[src^="moz-extension://"]', '[href^="moz-extension://"]',
|
|
21
|
+
'[src^="safari-web-extension://"]', '[href^="safari-web-extension://"]',
|
|
22
|
+
'[id="1p-menu-live-region"]',
|
|
23
|
+
'com-1password-button', 'com-1password-menu', 'com-1password-notification',
|
|
24
|
+
'grammarly-extension', 'grammarly-desktop-integration',
|
|
25
|
+
'lt-div', 'lt-card', 'lt-toolbar', 'lt-highlighter',
|
|
26
|
+
'[data-lastpass-root]', '[data-lastpass-icon-root]', '[data-dashlane-shadowhost]',
|
|
27
|
+
'[data-klarna-extension]', '[class*="klarna-extension"]',
|
|
28
|
+
'.skiptranslate', '#goog-gt-tt', '.goog-te-banner-frame', '.goog-te-balloon-frame',
|
|
29
|
+
'style.darkreader', 'style[data-darkreader-mode]', 'style[data-darkreader-scheme]',
|
|
30
|
+
'#loom-companion-mv3', 'loom-container',
|
|
31
|
+
'[ext-id]',
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
// Attribute-name prefixes extensions stamp onto YOUR real elements — strip the attribute, keep the element.
|
|
35
|
+
export const EXTENSION_ATTR_PREFIXES = [
|
|
36
|
+
'data-grammarly', 'data-lt', 'data-1p', 'data-com-onepassword', 'data-lastpass', 'data-lp', 'data-kwimpala',
|
|
37
|
+
'data-bitwarden', 'data-bw', 'data-dashlane', 'data-np', 'data-protonpass', 'data-proton-pass',
|
|
38
|
+
'data-keeper', 'data-rf', 'data-roboform', 'data-honey', 'data-rakuten', 'data-capital-one',
|
|
39
|
+
'data-wikibuy', 'data-klarna', 'data-darkreader', 'data-evernote', 'data-pocket', 'data-pin',
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
// Pre-joined once so hot paths call matches()/closest()/querySelectorAll() without re-joining.
|
|
43
|
+
export const EXTENSION_NODE_SELECTOR = EXTENSION_NODE_SELECTORS.join(',')
|
|
44
|
+
|
|
45
|
+
// One compiled test for "is this an extension marker attribute". The (?:-|$) boundary means a
|
|
46
|
+
// prefix like `data-lt` matches `data-lt` / `data-lt-foo` but never an app attribute like `data-ltr`.
|
|
47
|
+
export const EXTENSION_ATTR_PATTERN = new RegExp('^(?:' + EXTENSION_ATTR_PREFIXES.join('|') + ')(?:-|$)')
|
|
48
|
+
|
|
49
|
+
// Remove extension-injected elements and strip extension marker attributes from a (cloned) subtree, in place.
|
|
50
|
+
// Guarded so a malformed denylist entry degrades to "noise not stripped" rather than breaking the save.
|
|
51
|
+
export function stripExtensionNoise(root) {
|
|
52
|
+
if (!root || !root.querySelectorAll) return
|
|
53
|
+
try {
|
|
54
|
+
for (const el of root.querySelectorAll(EXTENSION_NODE_SELECTOR)) el.remove()
|
|
55
|
+
for (const el of root.querySelectorAll('*')) {
|
|
56
|
+
for (const attr of [...el.attributes]) {
|
|
57
|
+
if (EXTENSION_ATTR_PATTERN.test(attr.name.toLowerCase())) el.removeAttribute(attr.name)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
} catch (e) {
|
|
61
|
+
if (typeof console !== 'undefined') console.warn('[hyperclay] stripExtensionNoise skipped:', e)
|
|
62
|
+
}
|
|
63
|
+
}
|
package/src/filter.js
CHANGED
|
@@ -1,23 +1,36 @@
|
|
|
1
|
-
// Mirrors hyperclayjs/src/utilities/mutation.js _shouldIgnore
|
|
2
|
-
//
|
|
3
|
-
// one of the four ignore attributes
|
|
4
|
-
//
|
|
1
|
+
// Mirrors hyperclayjs/src/utilities/mutation.js _shouldIgnore. Returns true when
|
|
2
|
+
// a mutation should be ignored: the node is (or sits inside) a browser-extension
|
|
3
|
+
// injected element, an ancestor carries one of the four ignore attributes, or the
|
|
4
|
+
// record is an extension marker attribute (e.g. a password-manager field tag).
|
|
5
|
+
// `ignoreAttributePredicate` is an optional caller-supplied predicate for
|
|
6
|
+
// additional filtering on attribute records.
|
|
7
|
+
|
|
8
|
+
import { EXTENSION_NODE_SELECTOR, EXTENSION_ATTR_PATTERN } from './extension-noise.js'
|
|
5
9
|
|
|
6
10
|
const IGNORE_ATTRS = ['mutations-ignore', 'save-remove', 'save-ignore', 'save-freeze']
|
|
7
11
|
|
|
8
12
|
export function shouldIgnore(node, ignoreAttributePredicate, record) {
|
|
9
13
|
// For non-element nodes (like text nodes), start from parent.
|
|
10
14
|
let el = (node && node.nodeType !== 1) ? node.parentElement : node
|
|
15
|
+
|
|
16
|
+
// Browser-extension injected elements (and their descendants) are not page content.
|
|
17
|
+
if (el && el.closest && el.closest(EXTENSION_NODE_SELECTOR)) return true
|
|
18
|
+
|
|
11
19
|
while (el && el.nodeType === 1) {
|
|
12
20
|
for (const attr of IGNORE_ATTRS) {
|
|
13
21
|
if (el.hasAttribute && el.hasAttribute(attr)) return true
|
|
14
22
|
}
|
|
15
23
|
el = el.parentElement
|
|
16
24
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
|
|
26
|
+
if (record && record.type === 'attributes') {
|
|
27
|
+
// Extension marker attributes on a real element (password-manager field tags) are noise.
|
|
28
|
+
if (record.attributeName && EXTENSION_ATTR_PATTERN.test(record.attributeName.toLowerCase())) return true
|
|
29
|
+
if (ignoreAttributePredicate) {
|
|
30
|
+
try {
|
|
31
|
+
if (ignoreAttributePredicate(record.attributeName, record.target)) return true
|
|
32
|
+
} catch (_) {}
|
|
33
|
+
}
|
|
21
34
|
}
|
|
22
35
|
return false
|
|
23
36
|
}
|
package/src/index.js
CHANGED
|
@@ -97,6 +97,8 @@ const undo = {
|
|
|
97
97
|
commitCaptured: inst.commitCaptured,
|
|
98
98
|
discardCaptured: inst.discardCaptured,
|
|
99
99
|
flush: inst.flush,
|
|
100
|
+
record: inst.record,
|
|
101
|
+
recordValue: inst.recordValue,
|
|
100
102
|
undo: inst.undo,
|
|
101
103
|
redo: inst.redo,
|
|
102
104
|
clear: inst.clear,
|
|
@@ -129,6 +131,8 @@ const undo = {
|
|
|
129
131
|
commit(label, fn) { return singleton ? singleton.commit(label, fn) : undefined },
|
|
130
132
|
commitCaptured(label) { return singleton ? singleton.commitCaptured(label) : undefined },
|
|
131
133
|
discardCaptured() { return singleton ? singleton.discardCaptured() : undefined },
|
|
134
|
+
record(p) { return singleton ? singleton.record(p) : undefined },
|
|
135
|
+
recordValue(t, o) { return singleton ? singleton.recordValue(t, o) : undefined },
|
|
132
136
|
undo() { return singleton ? singleton.undo() : undefined },
|
|
133
137
|
redo() { return singleton ? singleton.redo() : undefined },
|
|
134
138
|
clear() { return singleton ? singleton.clear() : undefined },
|
package/src/primitives.js
CHANGED
|
@@ -128,6 +128,12 @@ export function replayForward(p) {
|
|
|
128
128
|
case 'text':
|
|
129
129
|
p.target.data = p.newValue
|
|
130
130
|
return
|
|
131
|
+
case 'value':
|
|
132
|
+
// An element PROPERTY write (e.g. input.value / checkbox.checked). These
|
|
133
|
+
// fire no MutationRecord, so they never arrive via the observer — a caller
|
|
134
|
+
// records them explicitly through scope.recordValue().
|
|
135
|
+
p.target[p.prop] = p.newValue
|
|
136
|
+
return
|
|
131
137
|
case 'add':
|
|
132
138
|
for (const node of p.nodes) {
|
|
133
139
|
if (p.before && p.before.parentNode === p.parent) {
|
|
@@ -162,6 +168,9 @@ export function replayReverse(p) {
|
|
|
162
168
|
case 'text':
|
|
163
169
|
p.target.data = p.oldValue
|
|
164
170
|
return
|
|
171
|
+
case 'value':
|
|
172
|
+
p.target[p.prop] = p.oldValue
|
|
173
|
+
return
|
|
165
174
|
case 'add':
|
|
166
175
|
// forward was add → reverse is remove
|
|
167
176
|
for (const node of p.nodes) {
|
package/src/scope.js
CHANGED
|
@@ -155,6 +155,26 @@ export function createScope(opts) {
|
|
|
155
155
|
if (observer) observer.takeRecords()
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
// --- Manual recording (for changes the observer can't see) ---
|
|
159
|
+
|
|
160
|
+
// Record a primitive the MutationObserver never delivers — an element PROPERTY
|
|
161
|
+
// write (input.value, checkbox.checked) fires no MutationRecord. The primitive
|
|
162
|
+
// joins the same idle batch as observed edits, so rapid same-field property
|
|
163
|
+
// writes coalesce into one commit exactly like text edits do. No-op while
|
|
164
|
+
// stopped or paused (a paused caller is inside an undo/redo replay or an
|
|
165
|
+
// exclusion frame, where injected primitives would be wrong).
|
|
166
|
+
function record(primitive) {
|
|
167
|
+
if (!started || pauseDepth > 0 || !primitive) return
|
|
168
|
+
pendingPrimitives.push(primitive)
|
|
169
|
+
restartIdleTimer()
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Convenience for the common case: a value/checked property write on an element.
|
|
173
|
+
function recordValue(target, { prop = 'value', oldValue, newValue } = {}) {
|
|
174
|
+
if (!target || oldValue === newValue) return
|
|
175
|
+
record({ kind: 'value', target, prop, oldValue, newValue })
|
|
176
|
+
}
|
|
177
|
+
|
|
158
178
|
// Force-close current idle batch as its own commit. Used by undo()/redo() and
|
|
159
179
|
// by the save seam (snapshot.js calls undo.flush() before cloning).
|
|
160
180
|
function flush() {
|
|
@@ -245,6 +265,7 @@ export function createScope(opts) {
|
|
|
245
265
|
_config: config, // private: used by keys.js to read shadowKeydownIn
|
|
246
266
|
start, stop,
|
|
247
267
|
commit, commitCaptured, discardCaptured, flush,
|
|
268
|
+
record, recordValue,
|
|
248
269
|
undo, redo, clear,
|
|
249
270
|
pause, resume,
|
|
250
271
|
on: emitter.on, off: emitter.off,
|