Skip to content

Commit 2e126d4

Browse files
author
blik
committed
generalize document capture island
codemirror persistence fix authorization
1 parent 3381649 commit 2e126d4

11 files changed

+324
-330
lines changed

Blik_2023_fragment.js

+138-93
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {locate,resolve,modularise,agent,virtual,window,jsdom,fetch,digest} from "./Blik_2023_interface.js";
2-
import {note,wait,observe,provide,collect,slip,infer,either,each,pass,tether,buffer,differ,compose,revert,record,combine,wether,swap,compound,something,string,basic,functor,defined,simple,exit,route,drop,crop,same,major,binary,is,has,not,numeric,array,pdflike,when,expect,generator,clock,fields} from "./Blik_2023_inference.js";
2+
import {note,wait,observe,provide,collect,slip,infer,either,each,pass,tether,buffer,differ,compose,revert,record,combine,whether,swap,compound,something,string,basic,functor,defined,simple,exit,route,drop,crop,same,major,binary,is,has,not,numeric,array,pdflike,when,expect,generator,clock,heritage} from "./Blik_2023_inference.js";
33
import {search,merge,prune,extract} from "./Blik_2023_search.js";
44
import {serialize,proceduralize,mime,data} from "./Blik_2023_meta.js";
55
import layout,{color} from "./Blik_2023_layout.js";
@@ -106,7 +106,7 @@
106106
{fields=[fields].flat().filter(Boolean);
107107
if(fields.length)
108108
return Object.fromEntries(fields.map(field=>
109-
[field,compose(infer("getAttribute",field),wether(isNaN,crop(1),Number))(node)]));
109+
[field,compose(infer("getAttribute",field),whether(isNaN,crop(1),Number))(node)]));
110110
node.normalize();
111111
if(node.documentElement)
112112
return demarkup(node.documentElement);
@@ -160,7 +160,7 @@
160160
:[entry,id];
161161
if(!defined(value))
162162
return;
163-
let type=wether([is(Date),is(Set),either(array,compound),either(binary,infer("match",/^(true|false)$/))]
163+
let type=whether([is(Date),is(Set),either(array,compound),either(binary,infer("match",/^(true|false)$/))]
164164
,...["date","radio","select","checkbox","text"].map(type=>swap(type)))(value);
165165
return JSON.parse(JSON.stringify(
166166
{for:id,title:id
@@ -230,7 +230,7 @@
230230
:parse(resource,semiotics);
231231
};
232232

233-
export function defer(form)
233+
export function dispatch(form)
234234
{return document(
235235
{"img":
236236
{onload:"!function expect(){setTimeout(tick=>(typeof dispatch=='undefined'?expect:dispatch).call(this,event),500)}.call(this)"
@@ -341,7 +341,7 @@
341341
let selectors=name?{id:"#",classList:"."}:{id:'#',class:'.',classed:'.'};
342342
let attributes=Array.from(node.attributes||[]).filter(({name})=>
343343
!["id","class","style"].includes(name.toLowerCase())&&!name.startsWith("on")).map(({name,value})=>
344-
"["+[name,value].join("=")+"]").join("");
344+
"["+[name,value].join("='")+"']").join("");
345345
return name+Object.entries(selectors).flatMap(([attribute,selector])=>
346346
[attribute==="classList"?Array.from(node[attribute]):["class","className"].includes(attribute)?node[attribute]?.split(' '):node[attribute]].flat().filter(value=>
347347
string(value)&&value?.length).map(value=>selector+value)).join('')+attributes;
@@ -526,7 +526,7 @@
526526
{let {width:limitx,height:limity}=this.parentNode.getBoundingClientRect();
527527
let {left,top,width,height}=this.getBoundingClientRect();
528528
let overflow=[-left,-top,left+width-limitx,top+height-limity];
529-
overflow.forEach(wether(major(0),(overflow,index)=>
529+
overflow.forEach(whether(major(0),(overflow,index)=>
530530
this.style[index%2?"top":"left"]=Number(this.style[index%2?"top":"left"].match(/\d+/)?.[0])+overflow*(index<2||-1)+"px",infer()));
531531
};
532532

@@ -659,7 +659,7 @@
659659
}
660660
}
661661
});
662-
buffer(capture)(author,"/feed");
662+
capture.call(author,"/feed");
663663
return author;
664664
};
665665

@@ -703,7 +703,7 @@
703703
,style
704704
,span:
705705
[{canvas:await buffer
706-
(compose(icon&&fetch,digest,wether(is(Blob),compose(image,canvas),infer()))
706+
(compose(icon&&fetch,digest,whether(is(Blob),compose(image,canvas),infer()))
707707
,swap({role:"img"})
708708
)(icon)
709709
}
@@ -758,46 +758,115 @@
758758
)(syntax);
759759
};
760760

761-
export function activate(event)
761+
export var actions=
762+
{body:
763+
{load(event)
762764
{if(this!==event.target.body)
763765
// ignore propagated load events.
764766
return;
765767
this.querySelectorAll("canvas[role=img]").forEach(canvas=>
766768
canvas.dispatchEvent(new canvas.ownerDocument.defaultView.Event("contextrestored",{bubbles:true})));
767769
this.querySelectorAll("[actions]").forEach(scope=>
768-
capture(scope,scope.getAttribute("actions"))||
770+
capture.call(scope,scope.getAttribute("actions"))||
769771
scope.dispatchEvent(new scope.ownerDocument.defaultView.Event("contextrestored")));
770-
};
772+
socket("/peer");
773+
},popstate(event)
774+
{//note(event);this.document.forms[0]?.dispatchEvent(new Event("submit"));
775+
},async message(event)
776+
{let {data:message}=event;
777+
console.info("send",message);
778+
if(this.ownerDocument.defaultView.socket?.readyState===3)
779+
await socket("/peer");
780+
this.ownerDocument.defaultView.socket?.send(JSON.stringify(message));
781+
}}
782+
,"canvas[role=img]":
783+
{contextrestored(event)
784+
{let [src,alt]=["data-source","aria-label"].map(this.getAttribute.bind(this));
785+
compose(image,canvas,infer(insert,"over",this))(src,alt);
786+
}}
787+
,"span[role=link]":
788+
{click(){this.ownerDocument.defaultView.open(this.getAttribute("source"),this.classList.contains("redirect")?undefined:"_blank")}
789+
}
790+
,".defer":
791+
{load(event)
792+
{if(!this.dataset.subject)
793+
return;
794+
let subject=JSON.parse(this.dataset.subject);
795+
compose(combine(compose("source",fetch,digest),infer()),transform,"over",this,insert)(subject);
796+
}}
797+
,".reference":{click(){expand.call(this,...arguments);}}
798+
,"#extend":
799+
{keydown(event)
800+
{event.stopPropagation();
801+
event.preventDefault();
802+
let {target,keyCode}=event;
803+
let {enter}=keyboard(keyCode);
804+
if(!enter)return;
805+
let value=target.textContent;
806+
if(Array.from(value).every(is(".")))
807+
return this.dispatchEvent(new Event("submit",{bubbles:true}));
808+
let form=this.closest("form");
809+
let method=form.getAttribute("method");
810+
fragment.form.call(form,{[method]:annotate({[value]:""},labels)});
811+
target.textContent="...";
812+
}}
813+
,".spell":{click(){spell(this);}}
814+
,".field":{click({target}){form.call(target.closest("form"),{extend:{key:"",value:""}})}}
815+
,".carousel":
816+
{scroll({target})
817+
{let timeout=target.timeout=setTimeout(tick=>
818+
{if(target.timeout!==timeout)return;
819+
delete target.timeout;
820+
let to=
821+
[target.scrollLeft,target.getBoundingClientRect().width
822+
].reduce((offset,width)=>Math.round(offset/width)*width);
823+
let duration=500;
824+
!function animateScroll(start,change,time,increment)
825+
{time+=increment;
826+
time/=span/2;
827+
if(time<1)
828+
target.scrollLeft=delta/2*time*time+start;
829+
else
830+
target.scrollLeft=-delta/2*(--time*(time-2)-1)+start;
831+
if(currentTime<duration)
832+
setTimeout(tick=>animateScroll(start,change,currentTime,increment),increment);
833+
}(target.scrollLeft,to-target.scrollLeft,0,20);
834+
},100);
835+
}}
836+
};
771837

772-
// export function activate(fragment,actions,path=[])
773-
// {// dispose actions on node/fragment to event listeners.
774-
// if(!actions)
775-
// return fragment;
776-
// if(string(actions))
777-
// return import(actions).then(({default:actions})=>activate(fragment,actions));
778-
// let node=fragment instanceof window.DocumentFragment
779-
// ?Array.from(fragment.childNodes)
780-
// :fragment.nodeName
781-
// ?[fragment]
782-
// :fragment;
783-
// let dispatch=proceduralize(function(){dispatch.call(this,event)});
784-
// let nodes=Object.entries(node).flatMap(([nodename,node])=>
785-
// [node].flat().filter(compound).map(node=>compose
786-
// (tether(select)
787-
// ,Object.keys
788-
// ,infer("map",event=>["on"+event,dispatch])
789-
// ,Object.fromEntries
790-
// ,node
791-
// ,(events,node)=>document.call(node,events)
792-
// ,node=>node.childNodes?Array.from(node.childNodes):node
793-
// )(node,actions)
794-
// ));
795-
// nodes.forEach(infer(activate,actions,path.concat(node?.nodeName)));
796-
// return fragment;
797-
// };
838+
export async function socket(actions)
839+
{var {default:peer}=await import(actions);
840+
var {protocol,host}=window.location;
841+
return revert((resolve,reject,socket)=>
842+
Object.assign(window
843+
,{socket:Object.assign(new WebSocket(socket)
844+
,{async onopen({target})
845+
{console.warn("Websocket open: ",target);
846+
let {author:name}=cookies(window.document.cookie);
847+
if(name)
848+
this.send(JSON.stringify({action:"sign",name}));
849+
resolve(this);
850+
},onmessage(event)
851+
{let message=JSON.parse(event.data);
852+
if(message.action!=="check")
853+
console.log("receive",message);
854+
peer[message.action]?.call(this,message,window);
855+
},onerror({target}){console.warn("Websocket not available at "+target.url);reject(target);}
856+
,onclose({target}){console.warn("Websocket closed: ",target);}
857+
})
858+
}))(["ws",/s/.test(protocol)?"s":"","://",host].join(""));
859+
};
798860

799861
export function keyboard(code)
800-
{let [key]=Object.entries({enter:13,escape:27,space:32,leftright:[37,39],updown:[38,40]}).find(({1:codes})=>
862+
{let [key]=Object.entries(
863+
{enter:13,escape:27,space:32,leftright:[37,39],updown:[38,40],backspace:8,tab:9
864+
,shift:16,ctrl:17,alt:18,caps:20,end:35,home:36,insert:45,delete:46
865+
,...Object.fromEntries(
866+
[Array.from({length:10},(number,index)=>index)
867+
,Array.from("abcdefghijklmnopqrstuvwxyz")
868+
].flat().map((key,index)=>[key,(index>9?55:48)+index]))
869+
}).find(({1:codes})=>
801870
[codes].flat().includes(code))||[];
802871
return key?{[key]:true}:{};
803872
};
@@ -854,39 +923,21 @@
854923
Promise.all(["/Blik_2023_interface.js","/Blik_2023_inference.js","/Blik_2023_search.js","/Blik_2023_fragment.js"].map(module=>
855924
import(module))).then((
856925
[{resolve,cookies}
857-
,{note,provide,collect,record,infer,tether,compose,combine,either,wether,drop,crop,swap,slip,match,is,not,has,simple}
926+
,{note,provide,collect,record,infer,tether,compose,combine,either,whether,drop,crop,swap,slip,match,is,not,has,simple}
858927
,{merge,prune,search}
859928
,{css}
860929
])=>Object.assign(window
861930
,{resolve,cookies
862-
,note,provide,collect,record,infer,tether,compose,combine,either,wether,drop,crop,swap,slip,match,is,not,has,simple
931+
,note,provide,collect,record,infer,tether,compose,combine,either,whether,drop,crop,swap,slip,match,is,not,has,simple
863932
,merge,prune,search
864933
,css
865934
}));
866-
var {peer}=import("/peer").then(({default:module})=>peer=module);
867-
var {protocol,host}=window.location;
868-
let socket=["ws",/s/.test(protocol)?"s":"","://",host].join("");
869-
Object.assign(window
870-
,{socket:Object.assign(new WebSocket(socket)
871-
,{async onopen({target})
872-
{console.warn("Websocket open: ",target);
873-
await new Promise(function defer(resolve){window.cookies?resolve():setTimeout(time=>defer(resolve),500);});
874-
let {author:name}=cookies(window.document.cookie);
875-
if(name)
876-
this.send(JSON.stringify({action:"sign",name}));
877-
},onmessage(event)
878-
{let message=JSON.parse(event.data);
879-
if(message.action!=="check")
880-
console.log("receive",message);
881-
peer[message.action]?.call(this,message,window);
882-
},onerror({target}){console.warn("Websocket not available at "+target.url);}
883-
,onclose({target}){console.warn("Websocket closed: ",target);}
884-
})
885-
,worker:import("/Blik_2023_interface.js").then(({delegate})=>delegate("/worker")).then(worker=>
935+
Object. assign(window
936+
,{worker:import("/Blik_2023_interface.js").then(({delegate})=>delegate("/worker")).then(worker=>
886937
Object.assign(window,{worker})).catch(fail=>
887938
Object.assign(window,{worker:console.warn("Worker not available at /worker.")}))
888939
,dispatch(event,buffering)
889-
{// deprecated in favor of capture(fragment,actions).
940+
{// deprecated in favor of capture.call(fragment,actions).
890941
// rarely needed for unpropagated server-rendered events like focus/blur.
891942
// explicit stopPropagation should be reproduced with event target conditions on scopes
892943
// (eg. {form:{input({target}){if(target)return;}}}).
@@ -902,55 +953,49 @@
902953
}});
903954
};
904955

905-
export function capture(fragment,actions)
956+
export function capture(module)
906957
{// synchronously register events to be routed to actions scoped by selector (eg. {#form:{change(){}}}).
907-
// common procedure in deferred scripts (with {fields} composed), hence no return statements.
908958
if(!globalThis.window)
909-
fragment.setAttribute("actions",actions)
910-
,exit(fragment.nodeName+" marked interactive: "+actions+". Re-invoke "+capture.name+" on client load to route events.");
959+
// Re-invoke on client to capture events.
960+
return this.setAttribute("actions",module),this;
911961
let exclusion=
912962
[["motion","orientation","orientationabsolute"].map(sensor=>"device"+sensor)
913963
,["start","run","end","cancel"].map(state=>"transition"+state)
914964
].flat().map(event=>"on"+event);
915965
let inclusion=["focusout","focusin","message"].map(event=>"on"+event);
916-
let buffered=new Set([fields(fragment).filter(event=>
917-
event.startsWith?.("on")&&!exclusion.includes(event)),inclusion].flat().map(event=>
918-
event.replace(/^on/,"")));
919-
buffered.forEach(event=>fragment.addEventListener(event,buffer,{passive:false}));
920-
let lead=["/","./"].find(lead=>actions.startsWith(lead));
921-
let [module,...route]=actions.replace(lead,"").split("/").filter(Boolean);
922-
import(lead+module).then(module=>
923-
actions=Object.assign(["default"],route).reduce((module,field)=>
924-
module[field],module)).then(function(actions)
925-
{let events=Object.values(actions).flatMap(Object.keys);
926-
new Set(events).forEach(event=>fragment.addEventListener(event,dispatch,{passive:false}));
927-
buffered.forEach(event=>fragment.removeEventListener(event,buffer));
928-
console.groupCollapsed("routing all propagated events to actions from scope: ",{fragment});
929-
console.log(actions);
930-
console.log(events.sort().join(" "));
931-
console.log(globalThis.window.location.origin+lead+module);
966+
let lead=["/","./"].find(lead=>module.startsWith(lead));
967+
let route=module.replace(lead,"").split("/").filter(Boolean);
968+
let actions=import(lead+route.shift()).then(module=>
969+
Object.assign(["default"],route).reduce((module,field)=>module[field],module));
970+
let refer=defer.bind(actions),deferred=new Set([heritage(this).filter(event=>
971+
event.startsWith?.("on")&&!exclusion.includes(event)),inclusion].flat());
972+
deferred.forEach(event=>this.addEventListener(event.slice(2),refer,{passive:false}));
973+
actions.then(actions=>
974+
new Set(Object.values(actions).flatMap(Object.keys)).forEach(event=>
975+
this.addEventListener(event,delegate.bind(actions),{passive:false}))).then(ready=>
976+
deferred.forEach(event=>this.removeEventListener(event.slice(2),refer)));
977+
console.groupCollapsed("routing all propagated events to actions from scope: ",{fragment:this});
978+
console.log({[globalThis.window.location.origin+lead+module]:actions});
932979
console.groupEnd();
933-
});
934-
function dispatch(event)
980+
return this;
981+
};
982+
983+
export function delegate(event)
935984
{let target=event.target.document?.body||event.target.body||event.target;
936985
if(target.nodeName==="#text")target=target.parentNode;
937-
let scopes=Object.entries(actions).filter(([selector,actions])=>
986+
let scopes=Object.entries(this).filter(([selector,actions])=>
938987
actions[event.type]&&target.closest(selector));
939988
scopes.map(([selector,actions])=>
940989
[target.closest(selector),actions[event.type]]).forEach(([scope,action])=>
941990
console.log({[event.type]:target,scope})||
942991
action.call(scope,event));
943-
//if(!scopes.length)
944-
//console.info("consumed:",{[event.type]:event});
945992
};
946-
function buffer(event,buffering=0)
993+
994+
export function defer(event)
947995
{// asynchronizing event dispatch unblocks its synchronous default unless prevented.
948-
if(typeof actions==="string")
949-
return setTimeout(buffer.bind(this,event,buffering+1),500)&&
950-
buffering||event.defaultPrevented||event.preventDefault()||console.warn(
951-
{["waiting for actions to dispatch "+event.type+" event from"]:event.target});
952-
dispatch(event);
953-
};
996+
this.then(actions=>delegate.call(actions,event));
997+
event.preventDefault();
998+
console.warn({["captured "+event.type+" event from"]:event.target});
954999
};
9551000

9561001
export function dispose(){if(globalThis.window)Object.assign(window.actions,actions);}

0 commit comments

Comments
 (0)