1import {renderStudioReader} from './studio-frame.js?v=social-account-v2-routing-1'; 2import {validCursor} from './progress.js?v=social-account-v2'; 3import {validateSocialChapter} from './model.js?v=social-account-v2'; 4import {initialCursor,transition,deriveVisibleState,resolveVisibleIdentity} from './playback.js?v=social-story-20260926'; 5import {feedView} from './feed-view.js?v=priority-fixes-20260927';import {threadView} from './thread-view.js?v=social-story-20260926';import {profileView} from './profile-view.js?v=social-story-20260926';import {node,button,overlay} from './ui.js?v=social-story-20260926'; 6export function renderSocialReader(root,doc,{startCursor,onPosition=()=>{},resolveMedia=async()=>null,chapterTitle,title}={}){ 7 if(!validateSocialChapter(doc).ok){root.textContent='This Social Story could not be opened. Update your reader and try again.';return ()=>{};} 8 if(doc.version===2)return renderStudioReader(root,doc,{startCursor,onPosition,resolveMedia,chapterTitle,title}); 9 let cursor=startCursor&&validCursor(doc,startCursor)?startCursor:initialCursor(doc),disposed=false,limit=20,closers=new Set(); 10 const shell=node('section','ss-reader');shell.tabIndex=0;shell.setAttribute('aria-label','Social Story reader');root.replaceChildren(shell); 11 function open(title,children){const close=overlay(document.body,title,children);closers.add(close);return close;} 12 function photo(asset,interactive=true){const holder=interactive?button('',()=>open('Story photo',[photo(asset,false),node('p','',asset.alt)]),'ss-photo'):node('span','ss-photo');const img=node('img');img.alt=asset.alt||'Story photo';holder.append(img);const path=asset.variants.standard.storagePath; 13 Promise.resolve().then(()=>disposed?null:resolveMedia(path)).then(src=>{if(disposed||!holder.isConnected)return;let valid=false;try{const u=new URL(src);valid=['http:','https:'].includes(u.protocol)&&!u.username&&!u.password;}catch{}if(!valid){holder.textContent='Photo unavailable';return;}img.src=src;img.onerror=()=>{holder.textContent='Photo unavailable';};}).catch(()=>{if(!disposed)holder.textContent='Photo unavailable';});return holder;} 14 function dispatch(action){if(disposed)return;const next=transition(doc,cursor,action);if(JSON.stringify(next)===JSON.stringify(cursor))return;cursor=next;draw(true);shell.querySelector('[data-primary]')?.focus({preventScroll:true});} 15 function draw(scroll=false){ 16 const state=deriveVisibleState(doc,cursor),head=node('header','ss-reader-header');head.append(node('span','ss-eyebrow','SOCIAL STORY'),node('strong','',state.viewpoint.label+'âs world')); 17 const body=node('div','ss-reader-body');const identity=id=>resolveVisibleIdentity(doc,id,cursor); 18 if(cursor.mode==='dm')body.append(threadView(state,doc,{photo,dispatch}));else{ 19 body.append(feedView(state,{identity,photo,limit,showEarlier:()=>{limit+=20;draw();},openProfile:p=>{if(p.canOpenProfile)open(p.label,[profileView(p,{photo})]);}})); 20 if(state.event?.kind==='incoming_message'||(state.event?.kind==='conversation'&&cursor.messageId)){ 21 const notice=node('section','ss-notification');notice.append(node('small','ss-eyebrow','NEW MESSAGE'),node('strong','',state.peer.label),node('p','',state.thread.messages[0].kind==='photo'?'Sent a photo':state.thread.messages[0].text));body.append(notice); 22 } 23 } 24 if(!state.event)body.append(node('p','ss-empty','Your story begins with a post.')); 25 const footer=node('footer','ss-reader-controls'),previous=button('Previous',()=>dispatch('previous'));previous.disabled=state.index<=0; 26 let label='Continue',action='next';if(cursor.mode==='dm'){const last=state.messageIndex===state.thread.messages.length-1;label=last?'Return to feed':'Next message';action=last?'returnToFeed':'nextMessage';}else if(state.event?.kind==='incoming_message'){label='Open conversation';action='openConversation';}else if(state.event?.kind==='conversation'&&cursor.messageId){label='Reopen conversation';action='openConversation';} 27 const next=button(label,()=>dispatch(action),'ss-button is-primary');next.dataset.primary='true';next.disabled=state.complete; 28 footer.append(previous,node('span','ss-muted',state.total?`${state.index+1} / ${state.total}`:'No events yet'),next);if(state.complete&&state.total)body.append(node('p','ss-end','End of chapter')); 29 shell.replaceChildren(head,body,footer); 30 const current=body.querySelector('[aria-current="step"]'); 31 if(current)body.scrollTop=Math.max(0,current.getBoundingClientRect().top-body.getBoundingClientRect().top+body.scrollTop-16); 32 else if(scroll)body.scrollTop=body.scrollHeight; 33 onPosition(cursor); 34 } 35 shell.onkeydown=e=>
35{if(![shell,...shell.querySelectorAll('.ss-reader-controls button')].includes(e.target)||e.ctrlKey||e.metaKey||e.altKey)return;if(e.key==='ArrowLeft'){e.preventDefault();dispatch('previous');}if(e.key==='ArrowRight'){e.preventDefault();shell.querySelector('[data-primary]')?.click();}}; 36 draw();return ()=>{disposed=true;shell.onkeydown=null;for(const close of closers)close();}; 37}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.