2023-09-12

Parse Wiki-Style Links and Render Them as Anchors ⇒ ContextMiniLatex: a Parser-Renderer for … ⇐ Render Wiki LinkRender Story

An internal link's Context provides a path to be searched when the link is clicked. The origin, the remote site, the reference cite and forks in the journal all contribute to the context.

See Origin for where we intend writing to be found.

See Neighborhood for how browsing influences context.

See Future for how unresolved links interact with neighbors.

A Future item describes how a missing page can be found or created. Unresolved Internal Links add a ghost page with a future to the lineup.

One can choose to create a new empty page.

One can choose to create from an available Template.

One can choose to view Twins from the Neighborhood.

~

Wikilinks (internal links) wikipedia

> Links are enclosed in doubled square brackets:

is seen as 1234 and "1234" in text and links to (the top of) page "1234" wikipedia .

~

MiniLatex: a Parser-Renderer for a Subset of LaTeX pdf

> James Carlson. 2666. MiniLatex: a Parser-Renderer for a Subset of LaTeX. ACM Trans. Web 0, 0, Article 0 (January 2666), 10 pages.

~

http://css-spikes.dbbs.co/hello.fed.wiki/

<html> <head> <link href="style.css" rel="stylesheet"> <meta charset=utf-8> <meta name=viewport content="width=device-width, initial-scale=1"> <link rel="manifest" href="manifest.json" /> </head> <body> <section class=main id=lineup></section> <footer id=footer> Try old <a href="http://hello.fed.wiki/view/welcome-visitors">Welcome Visitors</a> site. Completed checks show <span class=pass>pass</span> or <span class=fail>fail</span> </footer> <script type=module> const asSlug = text => text.replace(/\s/g, '-').replace(/[^A-Za-z0-9-]/g, '').toLowerCase() const delay = time => new Promise(res => setTimeout(res,time)) const lineup = [] // {page,id}... let pages = [] // {site,slug,page}... const params = new URLSearchParams(location.search) const origin = params.get('site') || 'hello.fed.wiki' { // launch the app with one page, typically welcome-visitors const slug = params.get('slug') || 'welcome-visitors' const view = r => `${r.site==origin?'view':r.site}/${asSlug(r.page.title)}` const legacy = () => window.open(`http://${origin}/${lineup.map(view).join('/')}`,'_blank') pages = await fetch('./pages.json').then(res => res.json()) await render(await resolve([origin],slug)) window.lineup.addEventListener('click',async event => { const target = event.target if (target.tagName == 'A' && target.getAttribute('href') == '#') { event.preventDefault() const title = target.innerText const here = target.closest('.page').id link(title,here,event.shiftKey) } }) window.lineup.addEventListener('focusout', async event => { const target = event.target edit(target.id, target.innerHTML) }) window.addEventListener("dragstart", event => event.preventDefault()) window.addEventListener("dragover", event => event.preventDefault()) window.addEventListener("drop", event => { event.preventDefault() drop(event.dataTransfer.getData("url"))}) window.debug = {origin,lineup,pages,legacy} } async function resolve(context,slug) { // retrieve page json following all the collaborative rules const site = context.shift() const index = pages.findIndex(cache => cache.site==site && cache.slug==slug) if (index != -1) return {site,page:pages[index].page} return await fetch(`//${site}/${slug}.json`) .then(async res => res.ok && res.status==200 ? {site, page:(await res.json())} : resolve(context,slug)) } async function post(site,slug,page) { const index = pages.findIndex(cache => cache.site==site && cache.slug==slug) if(index == -1) pages.push({site,slug,page}) else pages[index].page = page } async function render(panel) { // add a new content to the dom const escape = text => text.replace(/&/g,'&amp;').replace(/</g,'&lt;') const linked = text => text .replace(/\[\[(.*?)\]\]/g, (_,title) => `<a href="#">${title}</a>`) .replace(/\[(https?:.*?) (.*?)\]/g, (_,url,word) => `<a href="${url}">${word}</a>`) panel.id = (Math.floor(Math.random()*2**32)).toString(16) lineup.push(panel) const body = panel.page.story .filter(item => item.type == 'paragraph') .map(item => `<p id="${panel.id}.${item.id}" contenteditable="true">${linked(escape(item.text))}</p>`) .join("\n") window.lineup.innerHTML += ` <div class=page id=${panel.id}> <div class=paper> <div class=twins></div> <div class=header> <h1 title=${panel.site}> <span> <img src=//${panel.site}/favicon.png height=32px> ${panel.page.title} </span> </h1> </div> ${body} </div> </div>` await test(panel) } async function link(title,here,shiftKey) { // handle internal link const last = array => array[array.length-1] const uniq = (value, index, self) => self.indexOf(value) === index if(!shiftKey) { // make space in the lineup for new content while(last(lineup).id != here) { document.getElementById(last(lineup).id).remove() lineup.pop() } } const panel = lineup.find(panel => panel.id == here) const more = (panel.page.journal||[]).slice().reverse() .map(action => action.site) .filter(site => site) const context = ([origin, panel.site, ...more]).filter(uniq) const slug = asSlug(title) await render(await resolve(context,slug)) window.lineup .querySelector('.page:last-of-type') .scrollIntoView({behavior:'smooth', block:'center'}) } async function edit(id,html) { const tuple = id.split('.') const text = html .replace(/<\/?(b|i|u)>/g,'') .replace(/<a href="(https?:.+?)">(.+?)<\/a>/g,(_,url,word) => `[${url} ${word}]`) .replace(/<a href="#">(.+?)<\/a>/g,(_,title) => `${title}`) .replace(/<\/?\w.*?>/g,'') .replace(/&amp;/g,'&').replace(/&lt;/g,'<') .replace(/&nbsp;/g,' ') const panel = lineup.find(panel => panel.id == id.split('.')[0]) const item = panel.page.story.find(item => item.id == id.split('.')[1]) const changed = item.text != text if(changed) { const type = 'edit' const id = item.id const date = Date.now() item.text = text const action = {type,id,date,item} panel.page.journal.push(action) post(panel.site, asSlug(panel.page.title), panel.page) } } async function drop(url) { // handle drag and drop from remote page flag const m = url.match(/^(https?:)?\/\/(.+?)\/.*\/([a-z-]+)$/) if (!m) return await render(await resolve([m[2]],m[3])) window.lineup .querySelector('.page:last-of-type') .scrollIntoView({behavior:'smooth', block:'center'}) } async function recall(panel,date,shiftKey) { const last = array => array[array.length-1] const here = panel.id if(!shiftKey) { // make space in the lineup for new content while(last(lineup).id != here) { document.getElementById(last(lineup).id).remove() lineup.pop() } } const page = { title:panel.page.title, story:[], journal:[] } const order = () => (panel.page.story||[]).map(item => item.id) const add = (after, item) => { const index = order().indexOf(after) + 1 page.story.splice(index, 0, item) } for(const action of panel.page.journal) { switch (action.type) { case 'create': break case 'add': add(action.after, action.item) break case 'edit': const index = order().indexOf(action.id) if (index != -1) page.story.splice(index,1,action.item) else page.story.push(action.item) break } page.journal.push(action) if(action.date == date) { await render({site:panel.site,page}) return } } } async function test(panel) { await delay(200) panel.ok = true const child = () => lineup[lineup.findIndex(each => each.id == panel.id)+1] const pragmas = panel.page.story .filter(item => item.type == 'paragraph' && item.text.startsWith('► ')) for (const item of pragmas) { const ok = bool => { console.log(bool?'%cpass':'%cfail',bool?'color:green':'color:red') document.getElementById(`${panel.id}.${item.id}`).classList.add(bool?'pass':'fail') if(!bool) panel.ok = false } let m console.log(item.text) if(lineup.length > 10) {ok(false); break} if(m = item.text.match(/click \[\[(.+?)\]\]/)) {await link(m[1],panel.id,false); ok(child()?.ok); await delay(400)} if(m = item.text.match(/click action (\d+)/)) {await recall(panel,+m[1],false)} if(m = item.text.match(/drop ([^ ]+)/)) {await drop(m[1]); await delay(400)} if(m = item.text.match(/check site ([\w\.-]+)/)) {ok(lineup[lineup.length-1].site.includes(m[1]))} if(m = item.text.match(/check slug ([a-z-]+)/)) {ok(asSlug(lineup[lineup.length-1].page.title).includes(m[1]))} if(m = item.text.match(/check text ([^ ]+)/)) {ok(lineup[lineup.length-1].page.story.find(item => item.text.includes(m[1])))} } } </script> </body> </html>

DOT FROM lambda-browsing

When poor choices are made by engineers that seem right in the moment because they can be owned and the owner benefits from growth fueled by short-term utility.

A Planetary Crisis Planning Game site