granite-mem 0.1.8 → 0.1.9
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/README.md +124 -227
- package/dist/index.js +4113 -652
- package/dist/public/app.js +17 -540
- package/dist/public/graph-engine.js +102 -0
- package/dist/public/index.html +113 -135
- package/dist/public/markdown-renderer.js +1 -187
- package/dist/public/style.css +1 -960
- package/dist/templates/founder-os.yml +204 -0
- package/package.json +8 -3
- package/templates/founder-os.yml +204 -0
- package/dist/public/canvas-renderer.js +0 -524
- package/dist/public/graph.js +0 -377
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";(()=>{(function(){const nt={};nt.detectCommunities=function(c,n){const R={};for(const a of c)R[a.slug]=[];for(const a of n)R[a.source].push(a.target),R[a.target].push(a.source);const t={};for(const a of c)t[a.slug]=a.community||a.slug;for(let a=0;a<20;a++){let A=0;const f=c.map(l=>l.slug).sort(()=>Math.random()-.5);for(const l of f){const x=R[l];if(!x.length)continue;const W={};for(const T of x)W[t[T]]=(W[t[T]]||0)+1;let P=t[l],z=-1;for(const[T,_]of Object.entries(W))(_>z||_===z&&T<P)&&(P=T,z=_);P!==t[l]&&(t[l]=P,A++)}if(A===0)break}const h=new Map,C=[],O={};for(const a of Object.keys(t)){const A=t[a];h.has(A)||(h.set(A,h.size),C.push(0));const f=h.get(A);O[a]=f,C[f]++}const U=[],M=h.size,g=137.508;for(let a=0;a<M;a++){const A=(a*g+24)%360,f=70+a*13%26,l=48+a*17%22;U.push(ft(A/360,f/100,l/100))}return{communityOf:O,sizes:C,K:M,palette:U}};function ft(k,c,n){let R,t,h;if(c===0)R=t=h=n;else{const C=(M,g,a)=>(a<0&&(a+=1),a>1&&(a-=1),a<.16666666666666666?M+(g-M)*6*a:a<.5?g:a<.6666666666666666?M+(g-M)*(.6666666666666666-a)*6:M),O=n<.5?n*(1+c):n+c-n*c,U=2*n-O;R=C(U,O,k+1/3),t=C(U,O,k),h=C(U,O,k-1/3)}return[R,t,h]}function J(k,c,n,R){this.x=k,this.y=c,this.w=n,this.h=R,this.mass=0,this.cx=0,this.cy=0,this.node=null,this.children=null}J.prototype.insert=function(c){if(this.mass+=1,this.cx+=c.x,this.cy+=c.y,!this.children&&this.node===null){this.node=c;return}if(!(!this.children&&this.w<1)){if(!this.children){const n=this.w/2,R=this.h/2;this.children=[new J(this.x,this.y,n,R),new J(this.x+n,this.y,n,R),new J(this.x,this.y+R,n,R),new J(this.x+n,this.y+R,n,R)];const t=this.node;this.node=null,this._insertIntoChild(t)}this._insertIntoChild(c)}},J.prototype._insertIntoChild=function(k){const c=this.w/2,n=this.h/2,R=k.x<this.x+c?0:1,t=k.y<this.y+n?0:1;this.children[t*2+R].insert(k)},nt.createSimulation=function(c,n,R=2e3,t=1400,h={}){const{communityOf:C,sizes:O,K:U,palette:M}=nt.detectCommunities(c,n),g=c.length,a=new Float32Array(g),A=new Float32Array(g),f=new Float32Array(g),l=new Float32Array(g),x=new Int32Array(g),W=new Int32Array(g),P=new Float32Array(g),z=new Float32Array(g),T=new Array(g),_={};for(let o=0;o<g;o++)T[o]=c[o].slug,_[c[o].slug]=o,x[o]=C[c[o].slug]??0;const it=o=>{let d=0;for(let p=0;p<o.length;p++)d=d*31+o.charCodeAt(p)|0;return Math.abs(d)},G=new Int32Array(n.length),V=new Int32Array(n.length),tt=new Float32Array(n.length),Z=new Uint8Array(n.length);for(let o=0;o<n.length;o++)G[o]=_[n[o].source],V[o]=_[n[o].target],tt[o]=1,Z[o]=x[G[o]]===x[V[o]]?1:0,W[G[o]]++,W[V[o]]++;const rt=[];for(let o=0;o<U;o++){const d=o/U*Math.PI*2;rt.push({x:R/2+Math.cos(d)*R*.28,y:t/2+Math.sin(d)*t*.28*.85})}for(let o=0;o<g;o++){const d=rt[x[o]],p=it(T[o]),w=40+p%200,F=p%360*Math.PI/180,m=(o*2654435761>>>0)/4294967295-.5,X=(o*40503>>>0)/4294967295-.5;a[o]=d.x+Math.cos(F)*w+m*8,A[o]=d.y+Math.sin(F)*w+X*8,P[o]=2.5+Math.min(14,Math.sqrt(W[o])*2.2),z[o]=1}const r={n:g,x:a,y:A,vx:f,vy:l,community:x,degree:W,radius:P,alpha:z,slugOf:T,idxOf:_,links:n,linkA:G,linkB:V,linkW:tt,linkIntra:Z,W:R,H:t,palette:M,K:U,communityOf:C,repel:h.repel??120,linkStrength:h.linkStrength??.015,linkDistance:h.linkDistance??55,bridgeMult:1.6,centerK:.004,damping:.86,theta:.9,cooling:1,coolTarget:.35,coolRate:.012,hoverX:null,hoverY:null,hoverR:120,hoverStrength:0,pinnedIdx:-1,pinX:0,pinY:0,active:-1};let et=null;return r.step=function(d){r.cooling=Math.max(r.coolTarget,r.cooling-r.coolRate);const p=r.cooling;if(p===0&&r.pinnedIdx<0)return;let w=1/0,F=-1/0,m=1/0,X=-1/0;for(let e=0;e<g;e++)a[e]<w&&(w=a[e]),a[e]>F&&(F=a[e]),A[e]<m&&(m=A[e]),A[e]>X&&(X=A[e]);const q=50,Q=F-w+q*2,$=X-m+q*2,at=Math.max(Q,$,100);et=new J(w-q,m-q,at,at);for(let e=0;e<g;e++)et.insert({x:a[e],y:A[e],i:e});const D=r.theta,ct=r.repel;for(let e=0;e<g;e++){let i=0,v=0;const y=[et];for(;y.length;){const u=y.pop();if(!u||u.mass===0)continue;const Y=u.cx/u.mass,N=u.cy/u.mass,E=a[e]-Y,I=A[e]-N,H=E*E+I*I+.01;if(u.node){if(u.node.i===e)continue;const B=ct/H;i+=E*B,v+=I*B}else if(u.children)if(u.w/Math.sqrt(H)<D){const B=ct*u.mass/H;i+=E*B,v+=I*B}else for(const B of u.children)y.push(B)}f[e]+=i*p,l[e]+=v*p}const s=r.linkStrength,b=r.linkDistance;for(let e=0;e<G.length;e++){const i=G[e],v=V[e],y=a[v]-a[i],u=A[v]-A[i],Y=Math.sqrt(y*y+u*u)+.01,N=Z[e]?b:b*1.8,E=Z[e]?s:s*r.bridgeMult,I=(Y-N)*E*tt[e],H=y/Y*I,B=u/Y*I;f[i]+=H*p,l[i]+=B*p,f[v]-=H*p,l[v]-=B*p}const K=R/2,S=t/2,L=r.centerK*p;for(let e=0;e<g;e++)f[e]+=(K-a[e])*L,l[e]+=(S-A[e])*L;if(r.hoverX!==null&&r.hoverStrength>0){const e=r.hoverR,i=e*e;for(let v=0;v<g;v++){const y=a[v]-r.hoverX,u=A[v]-r.hoverY,Y=y*y+u*u;if(Y<i){const N=Math.sqrt(Y)+.1,E=1-N/e,I=r.hoverStrength*E*E;f[v]+=y/N*I,l[v]+=u/N*I}}}r.pinnedIdx>=0&&(a[r.pinnedIdx]=r.pinX,A[r.pinnedIdx]=r.pinY,f[r.pinnedIdx]=0,l[r.pinnedIdx]=0);const ot=r.damping;for(let e=0;e<g;e++){f[e]*=ot,l[e]*=ot;const i=f[e]*f[e]+l[e]*l[e];if(i>400){const v=20/Math.sqrt(i);f[e]*=v,l[e]*=v}a[e]+=f[e],A[e]+=l[e]}},r.setHover=function(o,d,p=600){r.hoverX=o,r.hoverY=d,r.hoverStrength=p},r.clearHover=function(){r.hoverX=null,r.hoverStrength=0},r.setActive=function(o){r.active=o?_[o]??-1:-1},r.prebake=function(d=180){const p=r.cooling;r.cooling=1;for(let w=0;w<d;w++)r.step(1/60);r.cooling=p},r.freeze=function(){r.cooling=0,r.coolTarget=0;for(let d=0;d<g;d++)f[d]=0,l[d]=0},r.nudge=function(d=.35){r.coolTarget=0,r.cooling=Math.min(1,r.cooling+d)},r.edgeAlpha=1,r.pickNode=function(o,d,p=0){let w=-1,F=1/0;for(let m=0;m<g;m++){const X=o-a[m],q=d-A[m],Q=X*X+q*q,$=P[m]+6+p;Q<$*$&&Q<F&&(w=m,F=Q)}return w},r},nt.createRenderer=function(c,n,R={}){const t=c.getContext("webgl",{antialias:!0,alpha:!0,premultipliedAlpha:!1});if(!t)throw new Error("WebGL not supported");const h={tx:0,ty:0,k:1};let C=window.devicePixelRatio||1;function O(){C=window.devicePixelRatio||1;const s=c.getBoundingClientRect();c.width=Math.floor(s.width*C),c.height=Math.floor(s.height*C),t.viewport(0,0,c.width,c.height)}O();const U=`
|
|
2
|
+
attribute vec2 aCorner;
|
|
3
|
+
attribute vec2 aPos;
|
|
4
|
+
attribute float aRadius;
|
|
5
|
+
attribute vec3 aColor;
|
|
6
|
+
attribute float aAlpha;
|
|
7
|
+
attribute float aState; // 0=normal, 1=active, 2=hop1, 3=hop2, 4=dim
|
|
8
|
+
uniform vec2 uResolution;
|
|
9
|
+
uniform vec2 uTranslate;
|
|
10
|
+
uniform float uScale;
|
|
11
|
+
varying vec2 vCorner;
|
|
12
|
+
varying vec3 vColor;
|
|
13
|
+
varying float vAlpha;
|
|
14
|
+
varying float vState;
|
|
15
|
+
varying float vRadius;
|
|
16
|
+
void main() {
|
|
17
|
+
// Scale factor \u2014 active/hop1 get a halo extension
|
|
18
|
+
float extend = 6.0;
|
|
19
|
+
if (aState < 0.5) extend = 4.0;
|
|
20
|
+
else if (aState < 1.5) extend = 10.0; // active
|
|
21
|
+
else if (aState < 2.5) extend = 7.0; // hop1
|
|
22
|
+
float r = (aRadius + extend) * uScale;
|
|
23
|
+
vec2 world = aPos + uTranslate;
|
|
24
|
+
vec2 px = world * uScale;
|
|
25
|
+
vec2 corner = aCorner * r;
|
|
26
|
+
vec2 screen = px + corner;
|
|
27
|
+
vec2 clip = screen / uResolution * 2.0 - 1.0;
|
|
28
|
+
clip.y = -clip.y;
|
|
29
|
+
gl_Position = vec4(clip, 0.0, 1.0);
|
|
30
|
+
vCorner = aCorner;
|
|
31
|
+
vColor = aColor;
|
|
32
|
+
vAlpha = aAlpha;
|
|
33
|
+
vState = aState;
|
|
34
|
+
vRadius = aRadius * uScale;
|
|
35
|
+
}
|
|
36
|
+
`,M=`
|
|
37
|
+
precision mediump float;
|
|
38
|
+
varying vec2 vCorner;
|
|
39
|
+
varying vec3 vColor;
|
|
40
|
+
varying float vAlpha;
|
|
41
|
+
varying float vState;
|
|
42
|
+
varying float vRadius;
|
|
43
|
+
void main() {
|
|
44
|
+
float d = length(vCorner); // 0..~1 at corner
|
|
45
|
+
// inner disk
|
|
46
|
+
float rNorm = 1.0;
|
|
47
|
+
// Use vRadius vs. the pixel position: we want the disk at d < (r / r_ext)
|
|
48
|
+
// Since we built the quad at (r+extend), the disk sits at d < r/(r+extend)
|
|
49
|
+
float extend = 4.0;
|
|
50
|
+
if (vState >= 0.5 && vState < 1.5) extend = 10.0;
|
|
51
|
+
else if (vState >= 1.5 && vState < 2.5) extend = 7.0;
|
|
52
|
+
float diskEdge = vRadius / (vRadius + extend);
|
|
53
|
+
|
|
54
|
+
// Disk
|
|
55
|
+
float disk = smoothstep(diskEdge + 0.02, diskEdge - 0.02, d);
|
|
56
|
+
|
|
57
|
+
// Highlight inside (top-left)
|
|
58
|
+
vec2 hl = vCorner - vec2(-0.28, -0.28);
|
|
59
|
+
float highlight = (1.0 - smoothstep(0.0, 0.55, length(hl))) * disk * 0.32;
|
|
60
|
+
|
|
61
|
+
// Outer glow \u2014 strong, so each node broadcasts its community hue.
|
|
62
|
+
float glow = (1.0 - smoothstep(diskEdge, 1.0, d)) * 0.95;
|
|
63
|
+
|
|
64
|
+
// Active pulse ring
|
|
65
|
+
float pulse = 0.0;
|
|
66
|
+
if (vState >= 0.5 && vState < 1.5) {
|
|
67
|
+
float ring = smoothstep(diskEdge + 0.03, diskEdge + 0.06, d) * (1.0 - smoothstep(diskEdge + 0.06, diskEdge + 0.12, d));
|
|
68
|
+
pulse = ring * 0.8;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
vec3 base = vColor;
|
|
72
|
+
vec3 col = base * (disk * 1.0) + vec3(highlight) + base * glow * 0.5 + vec3(pulse);
|
|
73
|
+
float alpha = (disk + glow * 0.55 + pulse) * vAlpha;
|
|
74
|
+
if (alpha < 0.003) discard;
|
|
75
|
+
gl_FragColor = vec4(col, alpha);
|
|
76
|
+
}
|
|
77
|
+
`,g=`
|
|
78
|
+
attribute vec2 aPos; // endpoint position (0 or 1 along the edge)
|
|
79
|
+
attribute vec2 aA;
|
|
80
|
+
attribute vec2 aB;
|
|
81
|
+
attribute vec3 aColor;
|
|
82
|
+
attribute float aAlpha;
|
|
83
|
+
uniform vec2 uResolution;
|
|
84
|
+
uniform vec2 uTranslate;
|
|
85
|
+
uniform float uScale;
|
|
86
|
+
varying vec3 vColor;
|
|
87
|
+
varying float vAlpha;
|
|
88
|
+
void main() {
|
|
89
|
+
vec2 world = mix(aA, aB, aPos.x) + uTranslate;
|
|
90
|
+
vec2 px = world * uScale;
|
|
91
|
+
vec2 clip = px / uResolution * 2.0 - 1.0;
|
|
92
|
+
clip.y = -clip.y;
|
|
93
|
+
gl_Position = vec4(clip, 0.0, 1.0);
|
|
94
|
+
vColor = aColor;
|
|
95
|
+
vAlpha = aAlpha;
|
|
96
|
+
}
|
|
97
|
+
`,a=`
|
|
98
|
+
precision mediump float;
|
|
99
|
+
varying vec3 vColor;
|
|
100
|
+
varying float vAlpha;
|
|
101
|
+
void main() { gl_FragColor = vec4(vColor, vAlpha); }
|
|
102
|
+
`;function A(s,b){const K=(L,ot)=>{const e=t.createShader(L);if(t.shaderSource(e,ot),t.compileShader(e),!t.getShaderParameter(e,t.COMPILE_STATUS))throw new Error(t.getShaderInfoLog(e));return e},S=t.createProgram();if(t.attachShader(S,K(t.VERTEX_SHADER,s)),t.attachShader(S,K(t.FRAGMENT_SHADER,b)),t.linkProgram(S),!t.getProgramParameter(S,t.LINK_STATUS))throw new Error(t.getProgramInfoLog(S));return S}const f=A(U,M),l=A(g,a),x=n.n,W=new Float32Array([-1,-1,1,-1,1,1,-1,-1,1,1,-1,1]),P=new Float32Array(x*6*2);for(let s=0;s<x;s++)for(let b=0;b<6;b++)P[(s*6+b)*2]=W[b*2],P[(s*6+b)*2+1]=W[b*2+1];const z=new Float32Array(x*6*2),T=new Float32Array(x*6),_=new Float32Array(x*6*3),it=new Float32Array(x*6),G=new Float32Array(x*6),V=t.createBuffer();t.bindBuffer(t.ARRAY_BUFFER,V),t.bufferData(t.ARRAY_BUFFER,P,t.STATIC_DRAW);const tt=t.createBuffer(),Z=t.createBuffer(),rt=t.createBuffer(),r=t.createBuffer(),et=t.createBuffer(),o=n.links.length,d=new Float32Array(o*2*2);for(let s=0;s<o;s++)d[s*4+0]=0,d[s*4+1]=0,d[s*4+2]=1,d[s*4+3]=0;const p=new Float32Array(o*2*2),w=new Float32Array(o*2*2),F=new Float32Array(o*2*3),m=new Float32Array(o*2),X=t.createBuffer();t.bindBuffer(t.ARRAY_BUFFER,X),t.bufferData(t.ARRAY_BUFFER,d,t.STATIC_DRAW);const q=t.createBuffer(),Q=t.createBuffer(),$=t.createBuffer(),at=t.createBuffer();function D(s,b,K,S){const L=t.getAttribLocation(s,b);L<0||(t.bindBuffer(t.ARRAY_BUFFER,K),t.enableVertexAttribArray(L),t.vertexAttribPointer(L,S,t.FLOAT,!1,0,0))}return t.enable(t.BLEND),t.blendFunc(t.SRC_ALPHA,t.ONE_MINUS_SRC_ALPHA),{view:h,setView(s){Object.assign(h,s)},resize:O,hops:{hop1:new Set,hop2:new Set},setHops(s){this.hops=s},hovered:-1,setHovered(s){this.hovered=s},draw(s=0){const b=c.width,K=c.height;t.clearColor(0,0,0,0),t.clear(t.COLOR_BUFFER_BIT);const S=n.active,L=this.hops.hop1,ot=this.hops.hop2;for(let i=0;i<o;i++){const v=n.linkA[i],y=n.linkB[i],u=n.x[v],Y=n.y[v],N=n.x[y],E=n.y[y];p[i*4+0]=u,p[i*4+1]=Y,p[i*4+2]=u,p[i*4+3]=Y,w[i*4+0]=N,w[i*4+1]=E,w[i*4+2]=N,w[i*4+3]=E;const[I,H,B]=n.palette[n.community[v]],[j,ht,ut]=n.palette[n.community[y]];let st=n.linkIntra[i]?.38:.035;S>=0&&(v===S||y===S?st=.95:L.has(n.slugOf[v])&&L.has(n.slugOf[y])?st=.18:st=0);const lt=st*(n.edgeAlpha??1);m[i*2]=lt,m[i*2+1]=lt,F[i*6+0]=I,F[i*6+1]=H,F[i*6+2]=B,F[i*6+3]=j,F[i*6+4]=ht,F[i*6+5]=ut}t.useProgram(l),t.uniform2f(t.getUniformLocation(l,"uResolution"),b,K),t.uniform2f(t.getUniformLocation(l,"uTranslate"),h.tx,h.ty),t.uniform1f(t.getUniformLocation(l,"uScale"),h.k*C),D(l,"aPos",X,2),t.bindBuffer(t.ARRAY_BUFFER,q),t.bufferData(t.ARRAY_BUFFER,p,t.DYNAMIC_DRAW),D(l,"aA",q,2),t.bindBuffer(t.ARRAY_BUFFER,Q),t.bufferData(t.ARRAY_BUFFER,w,t.DYNAMIC_DRAW),D(l,"aB",Q,2),t.bindBuffer(t.ARRAY_BUFFER,$),t.bufferData(t.ARRAY_BUFFER,F,t.DYNAMIC_DRAW),D(l,"aColor",$,3),t.bindBuffer(t.ARRAY_BUFFER,at),t.bufferData(t.ARRAY_BUFFER,m,t.DYNAMIC_DRAW),D(l,"aAlpha",at,1),t.drawArrays(t.LINES,0,o*2);const e=.6+.4*Math.sin(s*3.4);for(let i=0;i<x;i++){const v=n.slugOf[i];let y=0,u=S<0?1:.25;i===S?(y=1,u=1):L.has(v)?(y=2,u=1):ot.has(v)?(y=3,u=.75):S>=0&&(y=4,u=.25),i===this.hovered&&(y=2,u=1);const[Y,N,E]=n.palette[n.community[i]],I=n.alpha[i],H=u*I;for(let B=0;B<6;B++){const j=i*6+B;z[j*2]=n.x[i],z[j*2+1]=n.y[i],T[j]=n.radius[i],_[j*3]=Y,_[j*3+1]=N,_[j*3+2]=E,it[j]=H,G[j]=y}}t.useProgram(f),t.uniform2f(t.getUniformLocation(f,"uResolution"),b,K),t.uniform2f(t.getUniformLocation(f,"uTranslate"),h.tx,h.ty),t.uniform1f(t.getUniformLocation(f,"uScale"),h.k*C),D(f,"aCorner",V,2),t.bindBuffer(t.ARRAY_BUFFER,tt),t.bufferData(t.ARRAY_BUFFER,z,t.DYNAMIC_DRAW),D(f,"aPos",tt,2),t.bindBuffer(t.ARRAY_BUFFER,Z),t.bufferData(t.ARRAY_BUFFER,T,t.DYNAMIC_DRAW),D(f,"aRadius",Z,1),t.bindBuffer(t.ARRAY_BUFFER,rt),t.bufferData(t.ARRAY_BUFFER,_,t.DYNAMIC_DRAW),D(f,"aColor",rt,3),t.bindBuffer(t.ARRAY_BUFFER,r),t.bufferData(t.ARRAY_BUFFER,it,t.DYNAMIC_DRAW),D(f,"aAlpha",r,1),t.bindBuffer(t.ARRAY_BUFFER,et),t.bufferData(t.ARRAY_BUFFER,G,t.DYNAMIC_DRAW),D(f,"aState",et,1),t.drawArrays(t.TRIANGLES,0,x*6)},screenToWorld(s,b){return{x:s/h.k-h.tx,y:b/h.k-h.ty}},worldToScreen(s,b){return{x:(s+h.tx)*h.k,y:(b+h.ty)*h.k}}}},window.GraphEngine=nt})();})();
|
package/dist/public/index.html
CHANGED
|
@@ -3,168 +3,146 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>Granite</title>
|
|
6
|
+
<title>Granite — Constellation</title>
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
-
|
|
9
|
+
<!-- Preload the three weights that paint above-the-fold content so titles
|
|
10
|
+
don't flash from Georgia to Fraunces after boot. -->
|
|
11
|
+
<link rel="preload" as="font" type="font/woff2" crossorigin
|
|
12
|
+
href="https://fonts.gstatic.com/s/fraunces/v38/6NU78FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0KxC9TeP2Xz5c.woff2">
|
|
13
|
+
<link rel="preload" as="font" type="font/woff2" crossorigin
|
|
14
|
+
href="https://fonts.gstatic.com/s/manrope/v20/xn7gYHE41ni1AdIRggexSvfedN4.woff2">
|
|
15
|
+
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,300..700&family=Manrope:wght@400;500;600;700;800&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
10
16
|
<link rel="stylesheet" href="/style.css">
|
|
11
17
|
</head>
|
|
12
18
|
<body>
|
|
13
|
-
<div id="
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
<div class="dirA" id="root">
|
|
20
|
+
|
|
21
|
+
<!-- Top bar -->
|
|
22
|
+
<header class="dirA-topbar">
|
|
23
|
+
<div class="dirA-brand" role="banner">
|
|
24
|
+
<div class="dirA-brand-mark" aria-hidden="true">
|
|
25
|
+
<svg width="22" height="22" viewBox="0 0 24 24">
|
|
26
|
+
<rect x="3" y="3" width="8" height="8" rx="1.6" fill="currentColor"/>
|
|
27
|
+
<rect x="13" y="3" width="8" height="8" rx="1.6" fill="currentColor" opacity="0.55"/>
|
|
28
|
+
<rect x="3" y="13" width="8" height="8" rx="1.6" fill="currentColor" opacity="0.35"/>
|
|
29
|
+
<rect x="13" y="13" width="8" height="8" rx="1.6" fill="currentColor" opacity="0.8"/>
|
|
20
30
|
</svg>
|
|
21
|
-
<input type="text" id="command-input" placeholder="Search notes..." autocomplete="off" spellcheck="false" aria-label="Search notes" role="combobox" aria-expanded="false" aria-controls="command-results" aria-autocomplete="list">
|
|
22
|
-
<kbd class="command-kbd">esc</kbd>
|
|
23
31
|
</div>
|
|
24
|
-
<div
|
|
32
|
+
<div>
|
|
33
|
+
<div class="dirA-brand-name">Granite</div>
|
|
34
|
+
<div class="dirA-brand-sub">Constellation · live</div>
|
|
35
|
+
</div>
|
|
25
36
|
</div>
|
|
37
|
+
<button class="dirA-search" id="search-trigger" type="button" aria-label="Open search (⌘K)">
|
|
38
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
39
|
+
<circle cx="11" cy="11" r="7"/><path d="m20 20-3.6-3.6"/>
|
|
40
|
+
</svg>
|
|
41
|
+
<span class="dirA-search-text">Search the vault…</span>
|
|
42
|
+
<span class="kbd">⌘K</span>
|
|
43
|
+
</button>
|
|
44
|
+
<div class="dirA-filters" id="type-filters" role="tablist" aria-label="Filter by note type"></div>
|
|
45
|
+
</header>
|
|
46
|
+
|
|
47
|
+
<!-- Title + subtitle -->
|
|
48
|
+
<div class="dirA-title">
|
|
49
|
+
<div class="eyebrow">Knowledge constellation · live physics</div>
|
|
50
|
+
<h1>Your vault,<br><em>in motion.</em></h1>
|
|
51
|
+
<div class="sub" id="dirA-sub">Booting the physics engine…</div>
|
|
26
52
|
</div>
|
|
27
53
|
|
|
28
|
-
<!--
|
|
29
|
-
<div
|
|
30
|
-
<div id="
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 4l8 8M12 4l-8 8"/></svg>
|
|
35
|
-
</button>
|
|
36
|
-
</div>
|
|
37
|
-
<div class="create-fields">
|
|
38
|
-
<div class="field-row">
|
|
39
|
-
<label for="create-type">Type</label>
|
|
40
|
-
<select id="create-type"></select>
|
|
41
|
-
</div>
|
|
42
|
-
<div class="field-row">
|
|
43
|
-
<label for="create-title">Title</label>
|
|
44
|
-
<input type="text" id="create-title" placeholder="Note title" autocomplete="off" spellcheck="false" required maxlength="200">
|
|
45
|
-
</div>
|
|
46
|
-
<div class="field-row field-grow">
|
|
47
|
-
<label for="create-body">Content</label>
|
|
48
|
-
<textarea id="create-body" placeholder="Start writing..." spellcheck="false"></textarea>
|
|
49
|
-
</div>
|
|
50
|
-
</div>
|
|
51
|
-
<button id="create-submit">Create</button>
|
|
52
|
-
</div>
|
|
54
|
+
<!-- Pulse stats -->
|
|
55
|
+
<div class="dirA-pulse" id="dirA-pulse">
|
|
56
|
+
<div class="dirA-pulse-stat"><div class="label">Notes</div><div class="value" id="stat-notes">—</div></div>
|
|
57
|
+
<div class="dirA-pulse-stat"><div class="label">Edges</div><div class="value" id="stat-edges">—</div></div>
|
|
58
|
+
<div class="dirA-pulse-stat"><div class="label">Communities</div><div class="value" id="stat-communities">—</div></div>
|
|
59
|
+
<div class="dirA-pulse-stat"><div class="label">Density</div><div class="value" id="stat-density">—</div></div>
|
|
53
60
|
</div>
|
|
54
61
|
|
|
55
|
-
<!--
|
|
56
|
-
<
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
<span class="mobile-brand">Granite</span>
|
|
61
|
-
<button id="mobile-search" class="icon-btn" aria-label="Search">
|
|
62
|
-
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="7" cy="7" r="4.5"/><path d="M10.5 10.5L14 14"/></svg>
|
|
63
|
-
</button>
|
|
64
|
-
</header>
|
|
62
|
+
<!-- Graph stage — WebGL canvas + label overlay -->
|
|
63
|
+
<div class="dirA-stage webgl" id="stage">
|
|
64
|
+
<canvas id="graph-canvas" class="dirA-canvas"></canvas>
|
|
65
|
+
<div id="graph-labels" class="dirA-labels"></div>
|
|
66
|
+
</div>
|
|
65
67
|
|
|
66
|
-
<!--
|
|
67
|
-
<div
|
|
68
|
+
<!-- Zoom control -->
|
|
69
|
+
<div class="dirA-zoom" id="zoom-control">
|
|
70
|
+
<button id="zoom-out" title="Zoom out" type="button">−</button>
|
|
71
|
+
<span class="zoom-val" id="zoom-val">100%</span>
|
|
72
|
+
<button id="zoom-in" title="Zoom in" type="button">+</button>
|
|
73
|
+
<button id="zoom-reset" title="Reset view" type="button" style="font-size:0.64rem">⟲</button>
|
|
74
|
+
</div>
|
|
68
75
|
|
|
69
|
-
<!--
|
|
70
|
-
<
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
<circle cx="7" cy="7" r="4.5"/><path d="M10.5 10.5L14 14"/>
|
|
84
|
-
</svg>
|
|
85
|
-
<span>Search</span>
|
|
86
|
-
<kbd>⌘K</kbd>
|
|
87
|
-
</button>
|
|
88
|
-
<button id="nav-new" class="nav-btn" title="New note (⌘N)">
|
|
89
|
-
<svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
90
|
-
<path d="M8 3v10M3 8h10"/>
|
|
91
|
-
</svg>
|
|
92
|
-
<span>New</span>
|
|
93
|
-
</button>
|
|
94
|
-
<button id="nav-graph" class="nav-btn" title="Graph (⌘G)">
|
|
95
|
-
<svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
96
|
-
<circle cx="4" cy="4" r="2"/><circle cx="12" cy="4" r="2"/><circle cx="8" cy="13" r="2"/>
|
|
97
|
-
<path d="M5.5 5.5L7 11M10.5 5.5L9 11M6 4h4"/>
|
|
76
|
+
<!-- Empty-state hint (shown when no reader open) -->
|
|
77
|
+
<div class="dirA-empty-hint" id="empty-hint">
|
|
78
|
+
<h3>Select a star</h3>
|
|
79
|
+
<p>Click any node to open. Drag to pull it around, scroll to zoom, use arrow keys to step through neighbours.</p>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<!-- Reader — hidden until a note is active -->
|
|
83
|
+
<aside class="dirA-reader" id="reader" hidden>
|
|
84
|
+
<div class="dirA-reader-progress" id="reader-progress"></div>
|
|
85
|
+
<div class="dirA-reader-resizer" id="reader-resizer"></div>
|
|
86
|
+
<div class="dirA-reader-chrome">
|
|
87
|
+
<nav class="dirA-reader-breadcrumb" id="reader-breadcrumb">
|
|
88
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
89
|
+
<path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/>
|
|
98
90
|
</svg>
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
<button class="
|
|
91
|
+
</nav>
|
|
92
|
+
<div class="dirA-reader-nav">
|
|
93
|
+
<button class="dirA-nav-btn" id="reader-prev" type="button" title="Previous neighbour (←)">
|
|
94
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"/></svg>
|
|
95
|
+
</button>
|
|
96
|
+
<button class="dirA-nav-btn" id="reader-next" type="button" title="Next neighbour (→)">
|
|
97
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M9 6l6 6-6 6"/></svg>
|
|
98
|
+
</button>
|
|
99
|
+
<button class="dirA-nav-btn close" id="reader-close" type="button" title="Close (Esc)">
|
|
100
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="m18 6-12 12"/><path d="m6 6 12 12"/></svg>
|
|
101
|
+
</button>
|
|
105
102
|
</div>
|
|
106
|
-
<div id="note-list" role="list" aria-label="Notes"></div>
|
|
107
103
|
</div>
|
|
108
|
-
</nav>
|
|
109
104
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
<div id="
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
<h1 id="note-title"></h1>
|
|
118
|
-
<div id="note-meta">
|
|
119
|
-
<span id="note-date"></span>
|
|
120
|
-
<div id="note-tags"></div>
|
|
121
|
-
</div>
|
|
122
|
-
<div id="note-body"></div>
|
|
123
|
-
<section id="backlinks">
|
|
124
|
-
<div class="backlinks-label">
|
|
125
|
-
<span id="backlinks-count">0</span> references
|
|
126
|
-
</div>
|
|
127
|
-
<div id="backlinks-list"></div>
|
|
128
|
-
</section>
|
|
129
|
-
</article>
|
|
130
|
-
</div>
|
|
105
|
+
<div class="dirA-reader-body" id="reader-body">
|
|
106
|
+
<div class="dirA-reader-kicker" id="reader-kicker"></div>
|
|
107
|
+
<h1 class="dirA-reader-title" id="reader-title"></h1>
|
|
108
|
+
<p class="dirA-reader-deck" id="reader-deck"></p>
|
|
109
|
+
<div class="dirA-reader-metaline" id="reader-metaline"></div>
|
|
110
|
+
<div class="dirA-reader-article" id="reader-article"></div>
|
|
111
|
+
<div class="dirA-links-grid" id="reader-links"></div>
|
|
131
112
|
</div>
|
|
113
|
+
</aside>
|
|
132
114
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
</
|
|
115
|
+
<!-- Footer hints -->
|
|
116
|
+
<footer class="dirA-footer">
|
|
117
|
+
<span><span class="kbd">⌘K</span>search</span>
|
|
118
|
+
<span class="sep"></span>
|
|
119
|
+
<span><span class="kbd">←</span><span class="kbd">→</span>traverse</span>
|
|
120
|
+
<span class="sep"></span>
|
|
121
|
+
<span><span class="kbd">Esc</span>close</span>
|
|
122
|
+
<span class="sep"></span>
|
|
123
|
+
<span>drag node · hover parts crowd · scroll zoom</span>
|
|
124
|
+
</footer>
|
|
125
|
+
|
|
126
|
+
<!-- Wikilink hover preview -->
|
|
127
|
+
<div class="wl-preview" id="wikilink-preview" hidden aria-hidden="true"></div>
|
|
138
128
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
<circle cx="
|
|
145
|
-
<circle cx="24" cy="36" r="3" stroke="currentColor" stroke-width="1" opacity="0.2"/>
|
|
146
|
-
<path d="M16.5 16.5L22 32M31.5 16.5L26 32M18 14h12" stroke="currentColor" stroke-width="0.75" opacity="0.12" stroke-dasharray="3 4"/>
|
|
129
|
+
<!-- ⌘K command palette overlay -->
|
|
130
|
+
<div id="command-bar-overlay" class="overlay hidden" role="dialog" aria-label="Search" aria-modal="true">
|
|
131
|
+
<div id="command-bar">
|
|
132
|
+
<div class="command-input-wrap">
|
|
133
|
+
<svg class="command-icon" width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
134
|
+
<circle cx="7" cy="7" r="4.5"/><path d="M10.5 10.5L14 14"/>
|
|
147
135
|
</svg>
|
|
136
|
+
<input type="text" id="command-input" placeholder="Search notes…" autocomplete="off" spellcheck="false" aria-label="Search notes" role="combobox" aria-expanded="false" aria-controls="command-results" aria-autocomplete="list">
|
|
137
|
+
<kbd class="command-kbd">esc</kbd>
|
|
148
138
|
</div>
|
|
149
|
-
<
|
|
150
|
-
<p class="empty-shortcuts"><kbd>⌘K</kbd> search <span class="dot"></span> <kbd>⌘N</kbd> new <span class="dot"></span> <kbd>⌘G</kbd> graph</p>
|
|
139
|
+
<div id="command-results" role="listbox" aria-label="Search results" aria-live="polite"></div>
|
|
151
140
|
</div>
|
|
152
|
-
</
|
|
153
|
-
</div>
|
|
154
|
-
|
|
155
|
-
<!-- Wikilink preview popup -->
|
|
156
|
-
<div id="wikilink-preview">
|
|
157
|
-
<div class="preview-type"></div>
|
|
158
|
-
<div class="preview-title"></div>
|
|
159
|
-
<div class="preview-snippet"></div>
|
|
160
|
-
<div class="preview-tags"></div>
|
|
141
|
+
</div>
|
|
161
142
|
</div>
|
|
162
143
|
|
|
163
|
-
|
|
164
|
-
<canvas id="grain" aria-hidden="true"></canvas>
|
|
165
|
-
|
|
144
|
+
<script src="/graph-engine.js"></script>
|
|
166
145
|
<script src="/markdown-renderer.js"></script>
|
|
167
|
-
<script src="/graph.js"></script>
|
|
168
146
|
<script src="/app.js"></script>
|
|
169
147
|
</body>
|
|
170
148
|
</html>
|
|
@@ -1,187 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Granite — HTML Markdown Renderer
|
|
3
|
-
*
|
|
4
|
-
* Converts markdown + wikilinks to semantic HTML.
|
|
5
|
-
* Supports: headings, lists, blockquotes, code, tables, inline formatting, wikilinks.
|
|
6
|
-
*/
|
|
7
|
-
const MarkdownRenderer = (() => {
|
|
8
|
-
|
|
9
|
-
function parseInline(text, outgoingLinks) {
|
|
10
|
-
const re = /(\[\[([^\]]+)\]\])|(`([^`]+)`)|(\*\*(.+?)\*\*)|(\*(.+?)\*)|(\[([^\]]+)\]\(([^)]+)\))/g;
|
|
11
|
-
let result = '';
|
|
12
|
-
let lastIndex = 0;
|
|
13
|
-
let match;
|
|
14
|
-
|
|
15
|
-
while ((match = re.exec(text)) !== null) {
|
|
16
|
-
if (match.index > lastIndex) result += escapeHtml(text.slice(lastIndex, match.index));
|
|
17
|
-
|
|
18
|
-
if (match[1]) {
|
|
19
|
-
const inner = match[2];
|
|
20
|
-
const parts = inner.split('|');
|
|
21
|
-
const target = parts[0].trim();
|
|
22
|
-
const display = parts.length > 1 ? parts[1].trim() : target;
|
|
23
|
-
const linkInfo = outgoingLinks?.find(l => l.target === target);
|
|
24
|
-
const resolved = linkInfo?.resolved ?? false;
|
|
25
|
-
const slug = linkInfo?.resolved_slug || null;
|
|
26
|
-
if (resolved && slug) {
|
|
27
|
-
result += `<a class="wikilink" data-slug="${escapeAttr(slug)}" href="#" role="link">${escapeHtml(display)}</a>`;
|
|
28
|
-
} else {
|
|
29
|
-
result += `<span class="wikilink-broken">${escapeHtml(display)}</span>`;
|
|
30
|
-
}
|
|
31
|
-
} else if (match[3]) {
|
|
32
|
-
result += `<code class="inline-code">${escapeHtml(match[4])}</code>`;
|
|
33
|
-
} else if (match[5]) {
|
|
34
|
-
result += `<strong>${escapeHtml(match[6])}</strong>`;
|
|
35
|
-
} else if (match[7]) {
|
|
36
|
-
result += `<em>${escapeHtml(match[8])}</em>`;
|
|
37
|
-
} else if (match[9]) {
|
|
38
|
-
result += `<a class="external-link" href="${escapeAttr(match[11])}" target="_blank" rel="noopener">${escapeHtml(match[10])}</a>`;
|
|
39
|
-
}
|
|
40
|
-
lastIndex = match.index + match[0].length;
|
|
41
|
-
}
|
|
42
|
-
if (lastIndex < text.length) result += escapeHtml(text.slice(lastIndex));
|
|
43
|
-
return result;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function render(body, outgoingLinks) {
|
|
47
|
-
const lines = body.split('\n');
|
|
48
|
-
const blocks = [];
|
|
49
|
-
let inCodeBlock = false;
|
|
50
|
-
let codeBuffer = [];
|
|
51
|
-
let codeLang = '';
|
|
52
|
-
let inList = false;
|
|
53
|
-
let listType = null;
|
|
54
|
-
let listItems = [];
|
|
55
|
-
|
|
56
|
-
function flushList() {
|
|
57
|
-
if (listItems.length > 0) {
|
|
58
|
-
const tag = listType === 'ol' ? 'ol' : 'ul';
|
|
59
|
-
blocks.push(`<${tag} class="md-list">${listItems.join('')}</${tag}>`);
|
|
60
|
-
listItems = [];
|
|
61
|
-
inList = false;
|
|
62
|
-
listType = null;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
for (let i = 0; i < lines.length; i++) {
|
|
67
|
-
const line = lines[i];
|
|
68
|
-
|
|
69
|
-
// Code block
|
|
70
|
-
if (line.startsWith('```')) {
|
|
71
|
-
if (inCodeBlock) {
|
|
72
|
-
const langLabel = codeLang ? `<span class="code-lang">${escapeHtml(codeLang)}</span>` : '';
|
|
73
|
-
blocks.push(`<div class="code-block">${langLabel}<pre><code>${escapeHtml(codeBuffer.join('\n'))}</code></pre></div>`);
|
|
74
|
-
codeBuffer = [];
|
|
75
|
-
inCodeBlock = false;
|
|
76
|
-
} else {
|
|
77
|
-
flushList();
|
|
78
|
-
inCodeBlock = true;
|
|
79
|
-
codeLang = line.slice(3).trim();
|
|
80
|
-
}
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
if (inCodeBlock) { codeBuffer.push(line); continue; }
|
|
84
|
-
|
|
85
|
-
// Blank line
|
|
86
|
-
if (line.trim() === '') { flushList(); continue; }
|
|
87
|
-
|
|
88
|
-
// Table detection: line starts with |
|
|
89
|
-
if (line.trim().startsWith('|') && line.trim().endsWith('|')) {
|
|
90
|
-
flushList();
|
|
91
|
-
const tableLines = [];
|
|
92
|
-
let j = i;
|
|
93
|
-
while (j < lines.length && lines[j].trim().startsWith('|') && lines[j].trim().endsWith('|')) {
|
|
94
|
-
tableLines.push(lines[j]);
|
|
95
|
-
j++;
|
|
96
|
-
}
|
|
97
|
-
if (tableLines.length >= 2) {
|
|
98
|
-
blocks.push(renderTable(tableLines, outgoingLinks));
|
|
99
|
-
i = j - 1;
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Headings
|
|
105
|
-
const hMatch = line.match(/^(#{1,3})\s+(.+)/);
|
|
106
|
-
if (hMatch) {
|
|
107
|
-
flushList();
|
|
108
|
-
const level = hMatch[1].length;
|
|
109
|
-
blocks.push(`<h${level} class="md-h${level}">${parseInline(hMatch[2], outgoingLinks)}</h${level}>`);
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// HR
|
|
114
|
-
if (/^(-{3,}|\*{3,}|_{3,})$/.test(line.trim())) {
|
|
115
|
-
flushList();
|
|
116
|
-
blocks.push('<hr class="md-hr">');
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Blockquote
|
|
121
|
-
if (line.startsWith('>')) {
|
|
122
|
-
flushList();
|
|
123
|
-
const text = line.replace(/^>\s?/, '');
|
|
124
|
-
blocks.push(`<blockquote class="md-blockquote">${parseInline(text, outgoingLinks)}</blockquote>`);
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Unordered list
|
|
129
|
-
const bulletMatch = line.match(/^(\s*)[*\-+]\s+(.+)/);
|
|
130
|
-
if (bulletMatch) {
|
|
131
|
-
if (listType !== 'ul') flushList();
|
|
132
|
-
inList = true;
|
|
133
|
-
listType = 'ul';
|
|
134
|
-
listItems.push(`<li>${parseInline(bulletMatch[2], outgoingLinks)}</li>`);
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Ordered list
|
|
139
|
-
const numMatch = line.match(/^(\s*)\d+\.\s+(.+)/);
|
|
140
|
-
if (numMatch) {
|
|
141
|
-
if (listType !== 'ol') flushList();
|
|
142
|
-
inList = true;
|
|
143
|
-
listType = 'ol';
|
|
144
|
-
listItems.push(`<li>${parseInline(numMatch[2], outgoingLinks)}</li>`);
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Paragraph
|
|
149
|
-
flushList();
|
|
150
|
-
blocks.push(`<p class="md-body">${parseInline(line, outgoingLinks)}</p>`);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
flushList();
|
|
154
|
-
if (inCodeBlock && codeBuffer.length > 0) {
|
|
155
|
-
blocks.push(`<div class="code-block"><pre><code>${escapeHtml(codeBuffer.join('\n'))}</code></pre></div>`);
|
|
156
|
-
}
|
|
157
|
-
return blocks.join('\n');
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function renderTable(tableLines, outgoingLinks) {
|
|
161
|
-
const parseRow = (line) => line.trim().replace(/^\||\|$/g, '').split('|').map(cell => cell.trim());
|
|
162
|
-
const headers = parseRow(tableLines[0]);
|
|
163
|
-
// Skip separator row (index 1)
|
|
164
|
-
const rows = tableLines.slice(2).map(parseRow);
|
|
165
|
-
|
|
166
|
-
let html = '<div class="md-table-wrap"><table class="md-table"><thead><tr>';
|
|
167
|
-
for (const h of headers) html += `<th>${parseInline(h, outgoingLinks)}</th>`;
|
|
168
|
-
html += '</tr></thead><tbody>';
|
|
169
|
-
for (const row of rows) {
|
|
170
|
-
html += '<tr>';
|
|
171
|
-
for (const cell of row) html += `<td>${parseInline(cell, outgoingLinks)}</td>`;
|
|
172
|
-
html += '</tr>';
|
|
173
|
-
}
|
|
174
|
-
html += '</tbody></table></div>';
|
|
175
|
-
return html;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function escapeHtml(str) {
|
|
179
|
-
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function escapeAttr(str) {
|
|
183
|
-
return str.replace(/"/g, '"').replace(/'/g, ''');
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return { render, parseInline, escapeHtml };
|
|
187
|
-
})();
|
|
1
|
+
"use strict";(()=>{})();
|