wiki-plugin-mech 0.1.4 → 0.1.6
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/client/mech.js +235 -6
- package/package.json +1 -1
package/client/mech.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
(function() {
|
|
3
3
|
|
|
4
|
+
const uniq = (value, index, self) => self.indexOf(value) === index
|
|
5
|
+
|
|
4
6
|
function expand(text) {
|
|
5
7
|
return text
|
|
6
8
|
.replace(/&/g, '&')
|
|
@@ -115,12 +117,125 @@
|
|
|
115
117
|
elem.innerHTML = command + `<br><font face=Arial size=32>${value}</font>`
|
|
116
118
|
}
|
|
117
119
|
|
|
120
|
+
function source_emit ({elem,command,args,body,state}) {
|
|
121
|
+
if (!(args && args.length)) return trouble(elem,`Expected Source topic, like "markers" for Map markers.`)
|
|
122
|
+
const topic = args[0]
|
|
123
|
+
const sources = requestSourceData(state.$item, topic)
|
|
124
|
+
if(!sources.length) return trouble(elem,`Expected source for "${topic}" in the lineup.`)
|
|
125
|
+
const count = type => {
|
|
126
|
+
const count = sources
|
|
127
|
+
.filter(source => [...source.div.classList].includes(type))
|
|
128
|
+
.length
|
|
129
|
+
return count ? `${count} ${type}` : null}
|
|
130
|
+
const counts = [count('map'),count('image'),count('frame')]
|
|
131
|
+
.filter(count => count)
|
|
132
|
+
.join(", ")
|
|
133
|
+
if (state.debug) console.log({topic,sources})
|
|
134
|
+
elem.innerHTML = command + ' ⇒ ' + counts
|
|
135
|
+
state[topic] = sources
|
|
136
|
+
if (body) run(body,state)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function preview_emit ({elem,command,args,state}) {
|
|
140
|
+
const round = digits => (+digits).toFixed(7)
|
|
141
|
+
const story = []
|
|
142
|
+
for (const arg of args) {
|
|
143
|
+
switch (arg) {
|
|
144
|
+
case 'map':
|
|
145
|
+
if(!('marker' in state)) return trouble(elem,`"map" preview expects "marker" state, like from "SOURCE marker".`)
|
|
146
|
+
const text = state.marker
|
|
147
|
+
.map(marker => [marker.result])
|
|
148
|
+
.flat(2)
|
|
149
|
+
.map(latlon => `${round(latlon.lat)}, ${round(latlon.lon)} ${latlon.label||''}`)
|
|
150
|
+
.filter(uniq)
|
|
151
|
+
.join("\n")
|
|
152
|
+
story.push({type:'map',text})
|
|
153
|
+
break
|
|
154
|
+
case 'graph':
|
|
155
|
+
if(!('aspect' in state)) return trouble(elem,`"graph" preview expects "aspect" state, like from "SOURCE aspect".`)
|
|
156
|
+
for (const {div,result} of state.aspect) {
|
|
157
|
+
for (const {name,graph} of result) {
|
|
158
|
+
if(state.debug) console.log({div,result,name,graph})
|
|
159
|
+
story.push({type:'paragraph',text:name})
|
|
160
|
+
story.push({type:'graphviz',text:dotify(graph)})
|
|
161
|
+
}
|
|
162
|
+
story.push({type:'pagefold',text:'.'})
|
|
163
|
+
}
|
|
164
|
+
break
|
|
165
|
+
case 'synopsis':
|
|
166
|
+
{const text = `This page has been generated by the Mech plugin. We want to tell you where. That's coming soon.`
|
|
167
|
+
story.push({type:'paragraph',text})}
|
|
168
|
+
break
|
|
169
|
+
default:
|
|
170
|
+
return trouble(elem,`"${arg}" doesn't name an item we can preview`)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const title = "Mech Preview" + (state.tick ? ` ${state.tick}` : '')
|
|
174
|
+
const page = {title,story}
|
|
175
|
+
const options = {$page:$(elem.closest('.page'))}
|
|
176
|
+
wiki.showResult(wiki.newPage(page), options)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function neighbors_emit ({elem,command,args,state}) {
|
|
180
|
+
const want = args[0]
|
|
181
|
+
if(state.debug) console.log({neighborhoodObject:wiki.neighborhoodObject})
|
|
182
|
+
const have = Object.entries(wiki.neighborhoodObject.sites)
|
|
183
|
+
.filter(([domain,site]) => !site.sitemapRequestInflight && (!want || domain.includes(want)))
|
|
184
|
+
.map(([domain,site]) => (site.sitemap||[])
|
|
185
|
+
.map(info => Object.assign({domain},info)))
|
|
186
|
+
state.neighborhood = have.flat()
|
|
187
|
+
.sort((a,b) => b.date - a.date)
|
|
188
|
+
elem.innerHTML = command + ` ⇒ ${state.neighborhood.length} pages, ${have.length} sites`
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function walk_emit ({elem,command,args,state}) {
|
|
192
|
+
const steps = Object.groupBy(walks(state.neighborhood),({graph})=>graph?'some':'none')
|
|
193
|
+
console.log({steps})
|
|
194
|
+
const nodes = steps.some.map(({graph}) => graph.nodes).flat()
|
|
195
|
+
elem.innerHTML = command + ` ⇒ ${steps.some.length} aspects, ${steps.none.length} empty, ${nodes.length} nodes`
|
|
196
|
+
const item = elem.closest('.item')
|
|
197
|
+
item.classList.add('aspect-source')
|
|
198
|
+
item.aspectData = () => steps.some
|
|
199
|
+
state.aspect = [{div:item,result:steps.some}]
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function tick_emit ({elem,args,body,state}) {
|
|
203
|
+
if(elem.innerHTML.match(/button/)) return
|
|
204
|
+
if (!body?.length) return trouble(elem,'TICK expects indented blocks to follow.')
|
|
205
|
+
const count = args[0] || '1'
|
|
206
|
+
if (!count.match(/^[1-9][0-9]?$/)) return trouble(elem,"TICK expects a count from 1 to 99")
|
|
207
|
+
let clock = null
|
|
208
|
+
elem.innerHTML += '<button style="border-width:0;">◉</button>'
|
|
209
|
+
elem.querySelector('button').addEventListener('click',event => {
|
|
210
|
+
state.debug = event.shiftKey
|
|
211
|
+
if(clock){
|
|
212
|
+
clock = clearInterval(clock)
|
|
213
|
+
delete state.tick
|
|
214
|
+
} else {
|
|
215
|
+
state.tick = +count
|
|
216
|
+
run(body,state)
|
|
217
|
+
clock = setInterval(()=>{
|
|
218
|
+
if(state.debug) console.log({tick:state.tick})
|
|
219
|
+
if(--state.tick > 0)
|
|
220
|
+
run(body,state)
|
|
221
|
+
else
|
|
222
|
+
clock = clearInterval(clock)
|
|
223
|
+
},1000)
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
|
|
118
228
|
const blocks = {
|
|
119
|
-
CLICK:
|
|
120
|
-
HELLO:
|
|
121
|
-
FROM:
|
|
122
|
-
SENSOR: {emit:sensor_emit
|
|
123
|
-
REPORT: {emit:report_emit,
|
|
229
|
+
CLICK: {emit:click_emit},
|
|
230
|
+
HELLO: {emit:hello_emit},
|
|
231
|
+
FROM: {emit:from_emit},
|
|
232
|
+
SENSOR: {emit:sensor_emit},
|
|
233
|
+
REPORT: {emit:report_emit},
|
|
234
|
+
SOURCE: {emit:source_emit},
|
|
235
|
+
PREVIEW: {emit:preview_emit},
|
|
236
|
+
NEIGHBORS:{emit:neighbors_emit},
|
|
237
|
+
WALK: {emit:walk_emit},
|
|
238
|
+
TICK: {emit:tick_emit}
|
|
124
239
|
}
|
|
125
240
|
|
|
126
241
|
function run (nest,state={}) {
|
|
@@ -152,8 +267,9 @@
|
|
|
152
267
|
const lines = item.text.split(/\n/)
|
|
153
268
|
const nest = tree(lines,[],0)
|
|
154
269
|
const html = format(nest)
|
|
270
|
+
const state = {$item} // deprecated. use elem.closest('.item')
|
|
155
271
|
$item.append(`<div style="background-color:#eee;padding:15px;border-top:8px;">${html}</div>`)
|
|
156
|
-
run(nest)
|
|
272
|
+
run(nest,state)
|
|
157
273
|
}
|
|
158
274
|
|
|
159
275
|
function bind($item, item) {
|
|
@@ -170,4 +286,117 @@
|
|
|
170
286
|
module.exports = {expand}
|
|
171
287
|
}
|
|
172
288
|
|
|
289
|
+
|
|
290
|
+
// library functions
|
|
291
|
+
|
|
292
|
+
// adapted from wiki-plugin-frame/client/frame.js
|
|
293
|
+
function requestSourceData($item, topic) {
|
|
294
|
+
let sources = []
|
|
295
|
+
for (let div of document.querySelectorAll(`.item`)) {
|
|
296
|
+
if (div.classList.contains(`${topic}-source`)) {
|
|
297
|
+
sources.unshift(div)
|
|
298
|
+
}
|
|
299
|
+
if (div === $item.get(0)) {
|
|
300
|
+
break
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return sources.map(div => {
|
|
305
|
+
let getData = div[`${topic}Data`]
|
|
306
|
+
let result = getData ? getData() : null
|
|
307
|
+
return {div,result}
|
|
308
|
+
})
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// adapted from super-collaborator/dotify.js
|
|
312
|
+
function dotify(graph) {
|
|
313
|
+
const tip = props => Object.entries(props).filter(e => e[1]).map(e => `${e[0]}: ${e[1]}`).join("\\n")
|
|
314
|
+
const nodes = graph.nodes.map((node,id) => {
|
|
315
|
+
const label = node.type ? `${node.type}\\n${node.props.name}` : node.props.name
|
|
316
|
+
return `${id} [label="${label}" ${(node.props.url||node.props.tick)?`URL="${node.props.url||'#'}" target="_blank"`:''} tooltip="${tip(node.props)}"]`
|
|
317
|
+
})
|
|
318
|
+
const edges = graph.rels.map(rel => {
|
|
319
|
+
return `${rel.from}->${rel.to} [label="${rel.type}" labeltooltip="${tip(rel.props)}"]`
|
|
320
|
+
})
|
|
321
|
+
return [
|
|
322
|
+
'digraph {',
|
|
323
|
+
'rankdir=LR',
|
|
324
|
+
'node [shape=box style=filled fillcolor=palegreen]',
|
|
325
|
+
...nodes,
|
|
326
|
+
...edges,
|
|
327
|
+
'}'].join("\n")
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// inspired by aspects-of-recent-changes/roster-graphs.html
|
|
331
|
+
function walks(neighborhood) {
|
|
332
|
+
const prob = n => Math.floor(n * Math.abs(Math.random()-Math.random()))
|
|
333
|
+
const rand = a => a[prob(a.length)]
|
|
334
|
+
const domains = neighborhood
|
|
335
|
+
.map(info => info.domain)
|
|
336
|
+
.filter(uniq)
|
|
337
|
+
return domains
|
|
338
|
+
.map(domain => {
|
|
339
|
+
const name = domain.split('.').slice(0,3).join('.')
|
|
340
|
+
const done = new Set()
|
|
341
|
+
const graph = new Graph()
|
|
342
|
+
let nid = 0
|
|
343
|
+
const here = neighborhood
|
|
344
|
+
.filter(info => info.domain==domain && ('links' in info))
|
|
345
|
+
if(!here.length) return {name,graph:null}
|
|
346
|
+
const find = slug => neighborhood.find(info => info.slug == slug)
|
|
347
|
+
const node = info => {
|
|
348
|
+
nid = graph.addNode('',{
|
|
349
|
+
name:info.title.replaceAll(/ /g,"\n"),
|
|
350
|
+
title:info.title,
|
|
351
|
+
site:domain,
|
|
352
|
+
links:Object.keys(info.links||{}).filter(slug => find(slug))})
|
|
353
|
+
return nid}
|
|
354
|
+
const rel = (here,there) => graph.addRel('',here,there)
|
|
355
|
+
const links = nid => graph.nodes[nid].props.links.filter(slug => !done.has(slug))
|
|
356
|
+
const start = rand(here)
|
|
357
|
+
done.add(start.slug)
|
|
358
|
+
node(start)
|
|
359
|
+
for (n=5;n>0;n--) {
|
|
360
|
+
try {
|
|
361
|
+
const slugs = links(nid)
|
|
362
|
+
const slug = rand(slugs)
|
|
363
|
+
done.add(slug)
|
|
364
|
+
const info = find(slug)
|
|
365
|
+
rel(nid,node(info))}
|
|
366
|
+
catch (e) {}
|
|
367
|
+
}
|
|
368
|
+
return {name,graph}
|
|
369
|
+
})
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// adapted from graph/src/graph.js
|
|
373
|
+
class Graph {
|
|
374
|
+
|
|
375
|
+
constructor(nodes=[], rels=[]) {
|
|
376
|
+
this.nodes = nodes;
|
|
377
|
+
this.rels = rels;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
addNode(type, props={}){
|
|
381
|
+
const obj = {type, in:[], out:[], props};
|
|
382
|
+
this.nodes.push(obj);
|
|
383
|
+
return this.nodes.length-1;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
addRel(type, from, to, props={}) {
|
|
387
|
+
const obj = {type, from, to, props};
|
|
388
|
+
this.rels.push(obj);
|
|
389
|
+
const rid = this.rels.length-1;
|
|
390
|
+
this.nodes[from].out.push(rid)
|
|
391
|
+
this.nodes[to].in.push(rid);
|
|
392
|
+
return rid;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
stringify(...args) {
|
|
396
|
+
const obj = { nodes: this.nodes, rels: this.rels }
|
|
397
|
+
return JSON.stringify(obj, ...args)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
}
|
|
401
|
+
|
|
173
402
|
}).call(this)
|