char-slop/berry
multipurpose self-modifiable agentic harness
git clone https://git.t4t.associates/char-slop/berry
7692781
main
1import { Marked , type Token , type TokensList } from "marked" ; 2import { code } from "./code.tsx" ; 3 4const escape = ( text :string ) => 5text . replace ( 6/ [&<>"'] / g , 7( char ) => `& ${{ "&" : "amp" , "<" : "lt" , ">" : "gt" , '"' : "quot" , "'" : "#39" }[ char ]} ;` , 8); 9 10// model output is untrusted: raw html shows as text, and links only go where links should 11const marked = new Marked ({ 12gfm :true , 13tokenizer :{ 14del :() => undefined , 15}, 16renderer :{ 17html :({ text}) => escape ( text ), 18link ({ href, tokens}) { 19const text = this . parser . parseInline ( tokens ); 20if ( ! /^(https?:|mailto:|#|\/) / i . test ( href )) return text ; 21return `<a href=" ${ escape ( href )} " target="_blank" rel="noopener noreferrer"> ${ text } </a>` ; 22}, 23}, 24}); 25 26/** One top-level block's nodes; its code is highlighted only once `settled`, since a fence being written still grows. */ 27function draw ( token :Token , links :TokensList [ "links" ], settled :boolean ) :ChildNode [] { 28const template = document . createElement ( "template" ); 29template . innerHTML = marked . parser ( Object . assign ([ token ], { links})); 30for ( const block of template . content . querySelectorAll ( "pre > code" )) { 31const source = block . textContent ?? "" ; 32if ( ! settled ) { 33// a growing fence redraws whole on every revision, so it stays one text node until it settles 34const pre = document . createElement ( "pre" ); 35pre . className = "code bare" ; 36const text = document . createElement ( "span" ); 37text . className = "code-text" ; 38text . textContent = source . replace ( / \n$ / , "" ); 39pre . append ( text ); 40block . parentElement ! . replaceWith ( pre ); 41continue ; 42} 43const language = block . className . match ( / language-(\S+) / )?.[ 1 ]; 44block . parentElement ! . replaceWith ( 45code ( source , { gutter :false , ...( language ?{ language} :{}) }), 46); 47} 48return [ ...template . content . childNodes ]; 49} 50 51type Drawn = { raw :string ; settled :boolean ; nodes :ChildNode [] }; 52 53/** 54* Markdown that may still be being written: each `update` draws again only the top-level blocks that changed, which 55* while it streams is the last. `final` says no more is coming. 56*/ 57export function markdownView () { 58const element = document . createElement ( "div" ); 59element . className = "markdown" ; 60let drawn :Drawn [] = []; 61let shown :{ text :string ; final :boolean } | null = null ; 62let links = "" ; 63return { 64 element, 65update ( text :string , final :boolean ) { 66if ( shown ?. text === text && shown . final === final ) return ; 67shown = { text, final}; 68const tokens = marked . lexer ( text ); 69// a reference link resolves against definitions anywhere in the text, even those written later 70const known = JSON . stringify ( tokens . links ); 71if ( known !== links ) { 72links = known ; 73drawn = []; 74element . replaceChildren (); 75} 76const next = tokens . map (( token , index ) :Drawn => { 77const settled = final || index < tokens . length - 1 ; 78const before = drawn [ index ]; 79if ( before ?. raw === token . raw && before . settled === settled ) return before ; 80return { raw :token . raw , settled, nodes :draw ( token , tokens . links , settled ) }; 81}); 82let first = 0 ; 83while ( first < next . length && next [ first ] === drawn [ first ]) first ++ ; 84for ( const { nodes} of drawn . slice ( first )) for ( const node of nodes ) node . remove (); 85element . append ( ...next . slice ( first ). flatMap (({ nodes}) => nodes )); 86drawn = next ; 87}, 88}; 89} 90 91export function markdown ( text :string ) :HTMLElement { 92const view = markdownView (); 93view . update ( text , true ); 94return view . element ; 95}