granite-mem 0.1.7 → 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 -207
- package/dist/index.js +4997 -1444
- package/dist/public/app.js +17 -588
- package/dist/public/graph-engine.js +102 -0
- package/dist/public/index.html +117 -126
- package/dist/public/markdown-renderer.js +1 -189
- package/dist/public/style.css +1 -923
- package/dist/templates/founder-os.yml +204 -0
- package/package.json +9 -3
- package/templates/founder-os.yml +204 -0
- package/dist/public/canvas-renderer.js +0 -524
- package/dist/public/graph.js +0 -380
|
@@ -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,155 +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
|
-
|
|
20
|
-
<rect x="
|
|
21
|
-
<rect x="
|
|
22
|
-
<rect x="
|
|
23
|
-
|
|
24
|
-
</button>
|
|
25
|
-
<button id="rail-search" class="rail-btn" title="Search (⌘K)">
|
|
26
|
-
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
27
|
-
<circle cx="7" cy="7" r="4.5"/>
|
|
28
|
-
<path d="M10.5 10.5L14 14"/>
|
|
29
|
-
</svg>
|
|
30
|
-
</button>
|
|
31
|
-
<button id="rail-graph" class="rail-btn" title="Graph (⌘G)">
|
|
32
|
-
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
33
|
-
<circle cx="4" cy="4" r="2"/>
|
|
34
|
-
<circle cx="12" cy="4" r="2"/>
|
|
35
|
-
<circle cx="8" cy="13" r="2"/>
|
|
36
|
-
<path d="M5.5 5.5L7 11M10.5 5.5L9 11M6 4h4"/>
|
|
37
|
-
</svg>
|
|
38
|
-
</button>
|
|
39
|
-
<button id="rail-new" class="rail-btn" title="New note (⌘N)">
|
|
40
|
-
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
41
|
-
<path d="M8 3v10M3 8h10"/>
|
|
42
|
-
</svg>
|
|
43
|
-
</button>
|
|
44
|
-
<div style="flex:1"></div>
|
|
45
|
-
<button id="rail-toggle" class="rail-btn" title="Toggle sidebar">
|
|
46
|
-
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
47
|
-
<rect x="1" y="2" width="14" height="12" rx="2"/>
|
|
48
|
-
<path d="M5.5 2v12"/>
|
|
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"/>
|
|
49
30
|
</svg>
|
|
50
|
-
</button>
|
|
51
|
-
</div>
|
|
52
|
-
<div id="sidebar-panel">
|
|
53
|
-
<!-- Search -->
|
|
54
|
-
<div id="sidebar-search">
|
|
55
|
-
<div id="search-wrapper">
|
|
56
|
-
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" opacity="0.4">
|
|
57
|
-
<circle cx="7" cy="7" r="4.5"/>
|
|
58
|
-
<path d="M10.5 10.5L14 14"/>
|
|
59
|
-
</svg>
|
|
60
|
-
<input type="text" id="search-box" placeholder="Search…" autocomplete="off" spellcheck="false">
|
|
61
|
-
<kbd>⌘K</kbd>
|
|
62
|
-
</div>
|
|
63
31
|
</div>
|
|
64
|
-
<div
|
|
65
|
-
<
|
|
32
|
+
<div>
|
|
33
|
+
<div class="dirA-brand-name">Granite</div>
|
|
34
|
+
<div class="dirA-brand-sub">Constellation · live</div>
|
|
66
35
|
</div>
|
|
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>
|
|
67
46
|
|
|
68
|
-
|
|
69
|
-
|
|
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>
|
|
52
|
+
</div>
|
|
70
53
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
<label class="create-label">Title</label>
|
|
85
|
-
<input type="text" id="create-title" class="create-input" placeholder="Note title…" autocomplete="off" spellcheck="false">
|
|
86
|
-
<label class="create-label">Content</label>
|
|
87
|
-
<textarea id="create-body" class="create-textarea" placeholder="Start writing… (markdown supported)" spellcheck="false"></textarea>
|
|
88
|
-
<button id="create-submit" class="create-submit">Create note</button>
|
|
89
|
-
</div>
|
|
90
|
-
</div>
|
|
91
|
-
</div>
|
|
92
|
-
</aside>
|
|
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>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
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>
|
|
93
67
|
|
|
94
|
-
<!--
|
|
95
|
-
<
|
|
96
|
-
|
|
97
|
-
<
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
<div id="note-meta">
|
|
102
|
-
<span id="note-type-badge" class="meta-badge"></span>
|
|
103
|
-
<span id="note-date" class="meta-date"></span>
|
|
104
|
-
<div id="note-tags"></div>
|
|
105
|
-
</div>
|
|
106
|
-
</header>
|
|
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>
|
|
107
75
|
|
|
108
|
-
|
|
109
|
-
|
|
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>
|
|
110
81
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
</
|
|
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"/>
|
|
90
|
+
</svg>
|
|
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>
|
|
102
|
+
</div>
|
|
121
103
|
</div>
|
|
122
104
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
<
|
|
126
|
-
<
|
|
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>
|
|
127
112
|
</div>
|
|
113
|
+
</aside>
|
|
128
114
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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>
|
|
128
|
+
|
|
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"/>
|
|
137
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>
|
|
138
138
|
</div>
|
|
139
|
-
<
|
|
140
|
-
<p class="empty-hint"><kbd>⌘K</kbd> search · <kbd>⌘N</kbd> new note · <kbd>⌘G</kbd> graph</p>
|
|
139
|
+
<div id="command-results" role="listbox" aria-label="Search results" aria-live="polite"></div>
|
|
141
140
|
</div>
|
|
142
|
-
</
|
|
143
|
-
</div>
|
|
144
|
-
|
|
145
|
-
<!-- Wikilink preview popup -->
|
|
146
|
-
<div id="wikilink-preview" class="wikilink-preview">
|
|
147
|
-
<div class="preview-type"></div>
|
|
148
|
-
<div class="preview-title"></div>
|
|
149
|
-
<div class="preview-snippet"></div>
|
|
150
|
-
<div class="preview-tags"></div>
|
|
141
|
+
</div>
|
|
151
142
|
</div>
|
|
152
143
|
|
|
144
|
+
<script src="/graph-engine.js"></script>
|
|
153
145
|
<script src="/markdown-renderer.js"></script>
|
|
154
|
-
<script src="/graph.js"></script>
|
|
155
146
|
<script src="/app.js"></script>
|
|
156
147
|
</body>
|
|
157
148
|
</html>
|
|
@@ -1,189 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Granite — HTML Markdown Renderer
|
|
3
|
-
*
|
|
4
|
-
* Converts markdown + wikilinks to semantic HTML.
|
|
5
|
-
* Wikilinks become real <a> elements with hover previews.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const MarkdownRenderer = (() => {
|
|
9
|
-
|
|
10
|
-
// ── Inline parser — handles [[wikilinks]], **bold**, *italic*, `code`, [links](url) ──
|
|
11
|
-
function parseInline(text, outgoingLinks) {
|
|
12
|
-
const re = /(\[\[([^\]]+)\]\])|(`([^`]+)`)|(\*\*(.+?)\*\*)|(\*(.+?)\*)|(\[([^\]]+)\]\(([^)]+)\))/g;
|
|
13
|
-
let result = '';
|
|
14
|
-
let lastIndex = 0;
|
|
15
|
-
let match;
|
|
16
|
-
|
|
17
|
-
while ((match = re.exec(text)) !== null) {
|
|
18
|
-
if (match.index > lastIndex) {
|
|
19
|
-
result += escapeHtml(text.slice(lastIndex, match.index));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (match[1]) {
|
|
23
|
-
// [[wikilink]] or [[target|display]]
|
|
24
|
-
const inner = match[2];
|
|
25
|
-
const parts = inner.split('|');
|
|
26
|
-
const target = parts[0].trim();
|
|
27
|
-
const display = parts.length > 1 ? parts[1].trim() : target;
|
|
28
|
-
const linkInfo = outgoingLinks?.find(l => l.target === target);
|
|
29
|
-
const resolved = linkInfo?.resolved ?? false;
|
|
30
|
-
const slug = linkInfo?.resolved_slug || null;
|
|
31
|
-
|
|
32
|
-
if (resolved && slug) {
|
|
33
|
-
result += `<a class="wikilink" data-slug="${escapeAttr(slug)}" data-resolved="true" href="javascript:void(0)">${escapeHtml(display)}</a>`;
|
|
34
|
-
} else {
|
|
35
|
-
result += `<span class="wikilink-broken">${escapeHtml(display)}</span>`;
|
|
36
|
-
}
|
|
37
|
-
} else if (match[3]) {
|
|
38
|
-
// `inline code`
|
|
39
|
-
result += `<code class="inline-code">${escapeHtml(match[4])}</code>`;
|
|
40
|
-
} else if (match[5]) {
|
|
41
|
-
// **bold**
|
|
42
|
-
result += `<strong>${escapeHtml(match[6])}</strong>`;
|
|
43
|
-
} else if (match[7]) {
|
|
44
|
-
// *italic*
|
|
45
|
-
result += `<em>${escapeHtml(match[8])}</em>`;
|
|
46
|
-
} else if (match[9]) {
|
|
47
|
-
// [text](url)
|
|
48
|
-
result += `<a class="external-link" href="${escapeAttr(match[11])}" target="_blank" rel="noopener">${escapeHtml(match[10])}</a>`;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
lastIndex = match.index + match[0].length;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (lastIndex < text.length) {
|
|
55
|
-
result += escapeHtml(text.slice(lastIndex));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return result;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ── Block-level markdown parser ──
|
|
62
|
-
function render(body, outgoingLinks) {
|
|
63
|
-
const lines = body.split('\n');
|
|
64
|
-
const blocks = [];
|
|
65
|
-
let inCodeBlock = false;
|
|
66
|
-
let codeBuffer = [];
|
|
67
|
-
let codeLang = '';
|
|
68
|
-
let inList = false;
|
|
69
|
-
let listType = null; // 'ul' or 'ol'
|
|
70
|
-
let listItems = [];
|
|
71
|
-
|
|
72
|
-
function flushList() {
|
|
73
|
-
if (listItems.length > 0) {
|
|
74
|
-
const tag = listType === 'ol' ? 'ol' : 'ul';
|
|
75
|
-
blocks.push(`<${tag} class="md-list">${listItems.join('')}</${tag}>`);
|
|
76
|
-
listItems = [];
|
|
77
|
-
inList = false;
|
|
78
|
-
listType = null;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
for (let i = 0; i < lines.length; i++) {
|
|
83
|
-
const line = lines[i];
|
|
84
|
-
|
|
85
|
-
// Code block toggle
|
|
86
|
-
if (line.startsWith('```')) {
|
|
87
|
-
if (inCodeBlock) {
|
|
88
|
-
const langClass = codeLang ? ` data-lang="${escapeAttr(codeLang)}"` : '';
|
|
89
|
-
const langLabel = codeLang ? `<span class="code-lang">${escapeHtml(codeLang)}</span>` : '';
|
|
90
|
-
blocks.push(`<div class="code-block"${langClass}>${langLabel}<pre><code>${escapeHtml(codeBuffer.join('\n'))}</code></pre></div>`);
|
|
91
|
-
codeBuffer = [];
|
|
92
|
-
inCodeBlock = false;
|
|
93
|
-
} else {
|
|
94
|
-
flushList();
|
|
95
|
-
inCodeBlock = true;
|
|
96
|
-
codeLang = line.slice(3).trim();
|
|
97
|
-
}
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (inCodeBlock) {
|
|
102
|
-
codeBuffer.push(line);
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Blank line
|
|
107
|
-
if (line.trim() === '') {
|
|
108
|
-
flushList();
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Headings
|
|
113
|
-
const hMatch = line.match(/^(#{1,3})\s+(.+)/);
|
|
114
|
-
if (hMatch) {
|
|
115
|
-
flushList();
|
|
116
|
-
const level = hMatch[1].length;
|
|
117
|
-
const content = parseInline(hMatch[2], outgoingLinks);
|
|
118
|
-
blocks.push(`<h${level} class="md-h${level}">${content}</h${level}>`);
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Horizontal rule
|
|
123
|
-
if (/^(-{3,}|\*{3,}|_{3,})$/.test(line.trim())) {
|
|
124
|
-
flushList();
|
|
125
|
-
blocks.push('<hr class="md-hr">');
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Blockquote
|
|
130
|
-
if (line.startsWith('>')) {
|
|
131
|
-
flushList();
|
|
132
|
-
const text = line.replace(/^>\s?/, '');
|
|
133
|
-
const content = parseInline(text, outgoingLinks);
|
|
134
|
-
blocks.push(`<blockquote class="md-blockquote">${content}</blockquote>`);
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Unordered list
|
|
139
|
-
const bulletMatch = line.match(/^(\s*)[*\-+]\s+(.+)/);
|
|
140
|
-
if (bulletMatch) {
|
|
141
|
-
if (listType !== 'ul') flushList();
|
|
142
|
-
inList = true;
|
|
143
|
-
listType = 'ul';
|
|
144
|
-
const content = parseInline(bulletMatch[2], outgoingLinks);
|
|
145
|
-
listItems.push(`<li>${content}</li>`);
|
|
146
|
-
continue;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Ordered list
|
|
150
|
-
const numMatch = line.match(/^(\s*)\d+\.\s+(.+)/);
|
|
151
|
-
if (numMatch) {
|
|
152
|
-
if (listType !== 'ol') flushList();
|
|
153
|
-
inList = true;
|
|
154
|
-
listType = 'ol';
|
|
155
|
-
const content = parseInline(numMatch[2], outgoingLinks);
|
|
156
|
-
listItems.push(`<li>${content}</li>`);
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Body paragraph
|
|
161
|
-
flushList();
|
|
162
|
-
const content = parseInline(line, outgoingLinks);
|
|
163
|
-
blocks.push(`<p class="md-body">${content}</p>`);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Flush remaining
|
|
167
|
-
flushList();
|
|
168
|
-
if (inCodeBlock && codeBuffer.length > 0) {
|
|
169
|
-
blocks.push(`<div class="code-block"><pre><code>${escapeHtml(codeBuffer.join('\n'))}</code></pre></div>`);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return blocks.join('\n');
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// ── Helpers ──
|
|
176
|
-
function escapeHtml(str) {
|
|
177
|
-
return str
|
|
178
|
-
.replace(/&/g, '&')
|
|
179
|
-
.replace(/</g, '<')
|
|
180
|
-
.replace(/>/g, '>')
|
|
181
|
-
.replace(/"/g, '"');
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function escapeAttr(str) {
|
|
185
|
-
return str.replace(/"/g, '"').replace(/'/g, ''');
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return { render, parseInline, escapeHtml };
|
|
189
|
-
})();
|
|
1
|
+
"use strict";(()=>{})();
|