llmd 0.3.0 → 0.4.0
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 +2 -0
- package/dist/client.js +53 -1
- package/dist/llmd +578 -125
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -25,6 +25,7 @@ That's it! The docs will open in your browser. Click through the sidebar to expl
|
|
|
25
25
|
- **Live reload** - Watch mode reloads on file changes
|
|
26
26
|
- **Copy buttons** - One-click code copying
|
|
27
27
|
- **Table of contents** - Auto-generated from headings
|
|
28
|
+
- **Highlights** - Extract and save important passages from your docs
|
|
28
29
|
- **Usage Analytics** - Track which docs you view most (local-only, opt-in)
|
|
29
30
|
|
|
30
31
|
## Documentation
|
|
@@ -33,6 +34,7 @@ That's it! The docs will open in your browser. Click through the sidebar to expl
|
|
|
33
34
|
- [Usage](./docs/usage.md) - Command-line options and examples
|
|
34
35
|
- [Themes](./docs/themes.md) - Built-in and custom color themes
|
|
35
36
|
- [Fonts](./docs/fonts.md) - Built-in and custom font combinations
|
|
37
|
+
- [Highlights](./docs/highlights.md) - Extract and save important passages
|
|
36
38
|
- [Analytics](./docs/analytics.md) - Local usage tracking
|
|
37
39
|
|
|
38
40
|
## Basic Usage
|
package/dist/client.js
CHANGED
|
@@ -1 +1,53 @@
|
|
|
1
|
-
var
|
|
1
|
+
var R=()=>{let C=document.querySelector(".admin-section"),J=document.querySelector(".admin-header");if(!(C&&J))return;if(localStorage.getItem("llmd-admin-collapsed")==="true")C.classList.add("collapsed");J.addEventListener("click",()=>{let j=C.classList.toggle("collapsed");localStorage.setItem("llmd-admin-collapsed",j.toString())})};var k=/\/$/,w=(C)=>{let J=C.querySelector(":scope > .dir-label");if(!J)return null;let q=Array.from(J.querySelectorAll("span"));if(q.length===0)return null;let j=q[q.length-1];if(!j.textContent)return null;return j.textContent.replace(k,"")},T=(C)=>{let J=[],q=C;while(q){let j=w(q);if(j)J.unshift(j);let K=q.parentElement;q=K?K.closest(".dir-item"):null}return J.length>0?J.join("/"):null},v=()=>{let C=[],J=document.querySelectorAll(".dir-item.collapsed");for(let q of Array.from(J)){let j=T(q);if(j)C.push(j)}localStorage.setItem("llmd-nav-collapsed",JSON.stringify(C))},I=()=>{try{let C=localStorage.getItem("llmd-nav-collapsed");if(!C)return;let J=JSON.parse(C),q=document.querySelectorAll(".dir-item");for(let j of Array.from(q)){let K=T(j);if(K&&J.includes(K))j.classList.add("collapsed")}}catch(C){console.error("[collapsible] Failed to restore state:",C)}},P=()=>{let C=document.querySelectorAll(".dir-label");for(let J of Array.from(C)){let q=J,j=document.createElement("span");j.className="dir-chevron",j.innerHTML='<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>',q.insertBefore(j,q.firstChild),q.style.cursor="pointer",q.addEventListener("click",(K)=>{K.preventDefault();let M=q.closest(".dir-item");if(M)M.classList.toggle("collapsed"),v()})}I()},f=()=>{let C=document.querySelector(".toc");if(!C)return;let J=C.querySelector("h3");if(!J)return;let q=document.createElement("span");q.className="toc-chevron",q.innerHTML='<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>',J.style.cursor="pointer",J.insertBefore(q,J.firstChild),J.addEventListener("click",()=>{C.classList.toggle("collapsed")})};if(typeof window<"u")window.addEventListener("DOMContentLoaded",()=>{P(),f()});var z=()=>{for(let C of Array.from(document.querySelectorAll("pre code"))){let J=C.parentElement;if(!J)continue;if(J.querySelector(".copy-button"))continue;let q=document.createElement("button");q.className="copy-button",q.textContent="Copy",q.setAttribute("aria-label","Copy code to clipboard"),q.addEventListener("click",async()=>{try{await navigator.clipboard.writeText(C.textContent||""),q.textContent="Copied!",q.classList.add("copied"),setTimeout(()=>{q.textContent="Copy",q.classList.remove("copied")},2000)}catch(j){console.error("Failed to copy:",j),q.textContent="Failed",setTimeout(()=>{q.textContent="Copy"},2000)}}),J.appendChild(q)}};if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",z);else z();var m=(C)=>{let q=`${window.location.protocol==="https:"?"wss:":"ws:"}//${window.location.host}/_ws`,j=null,K=null,M=()=>{j=new WebSocket(q),j.addEventListener("open",()=>{console.log("[llmd] Connected to file watcher"),j?.send(JSON.stringify({type:"watch",file:C}))}),j.addEventListener("message",(Q)=>{try{let Z=JSON.parse(Q.data);if(Z.type==="reload"&&Z.file===C)console.log(`[llmd] File changed: ${C}, reloading...`),window.location.reload()}catch(Z){console.error("[llmd] Failed to parse message:",Z)}}),j.addEventListener("close",()=>{console.log("[llmd] Disconnected from file watcher"),K=window.setTimeout(()=>{console.log("[llmd] Reconnecting..."),M()},2000)}),j.addEventListener("error",(Q)=>{console.error("[llmd] WebSocket error:",Q),j?.close()})};M(),window.addEventListener("beforeunload",()=>{if(K)clearTimeout(K);j?.close()})};window.connectFileWatcher=m;var F=[],G=null,B=null,y=new Map,A=()=>{if(!document.querySelector(".content"))return;u(),document.addEventListener("mouseup",b),document.addEventListener("touchend",b)},u=async()=>{try{let C=window.location.pathname.replace("/view/",""),J=await fetch(`/api/highlights/resource?path=${encodeURIComponent(C)}`);if(!J.ok)return;F=(await J.json()).highlights||[],x()}catch(C){console.error("[highlights] Failed to fetch highlights:",C)}},b=(C)=>{let J=C.target;if(G&&G.contains(J))return;let q=window.getSelection();if(!q||q.isCollapsed){L();return}let j=q.toString().trim();if(!j){L();return}let K=document.querySelector(".content");if(!K)return;let M=q.getRangeAt(0),Q=M.commonAncestorContainer,Z=Q.nodeType===Node.TEXT_NODE?Q.parentElement:Q;if(!(Z&&K.contains(Z))){L();return}B={text:j,range:M.cloneRange()},d(C)},d=(C)=>{if(!G)G=p(),document.body.appendChild(G);let{pageX:J,pageY:q}=C;G.style.left=`${J}px`,G.style.top=`${q-50}px`,G.style.display="block"},L=()=>{if(G)G.style.display="none";B=null},c=(C,J)=>{let q=y.get(C.id);if(q){q.remove(),y.delete(C.id);return}let j=document.createElement("div");j.className="highlight-notes-popup",j.style.cssText=`
|
|
2
|
+
position: absolute;
|
|
3
|
+
background: var(--bg);
|
|
4
|
+
border: 1px solid var(--border);
|
|
5
|
+
border-radius: 6px;
|
|
6
|
+
padding: 12px;
|
|
7
|
+
max-width: 300px;
|
|
8
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
9
|
+
z-index: 10000;
|
|
10
|
+
font-size: 13px;
|
|
11
|
+
line-height: 1.5;
|
|
12
|
+
`;let K=document.createElement("div");K.style.cssText="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;";let M=document.createElement("span");M.textContent="Highlight Notes",M.style.cssText="font-weight: 600; color: var(--fg);";let Q=document.createElement("button");Q.textContent="×",Q.style.cssText="background: none; border: none; color: var(--fg); cursor: pointer; padding: 0; font-size: 18px; line-height: 1; opacity: 0.7;",Q.addEventListener("click",(Y)=>{Y.stopPropagation(),j.remove(),y.delete(C.id)}),K.appendChild(M),K.appendChild(Q);let Z=document.createElement("div");Z.style.cssText="color: var(--fg); white-space: pre-wrap; word-wrap: break-word;",Z.textContent=C.notes||"(No notes)",j.appendChild(K),j.appendChild(Z),j.addEventListener("click",(Y)=>{Y.stopPropagation()}),j.style.left=`${J.pageX}px`,j.style.top=`${J.pageY+10}px`,document.body.appendChild(j),y.set(C.id,j);let $=(Y)=>{if(!j.contains(Y.target))j.remove(),y.delete(C.id),document.removeEventListener("click",$)};setTimeout(()=>{document.addEventListener("click",$)},0)},p=()=>{let C=document.createElement("div");C.className="highlight-popup",C.innerHTML=`
|
|
13
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
|
14
|
+
<span style="font-size: 13px; font-weight: 500; color: var(--fg);">Add Highlight</span>
|
|
15
|
+
<button class="highlight-close-btn" type="button" style="background: none; border: none; color: var(--fg); cursor: pointer; padding: 0; font-size: 18px; line-height: 1; opacity: 0.7;">×</button>
|
|
16
|
+
</div>
|
|
17
|
+
<textarea
|
|
18
|
+
class="highlight-notes-input"
|
|
19
|
+
placeholder="Add a note (optional)"
|
|
20
|
+
rows="2"
|
|
21
|
+
style="width: 100%; padding: 8px; border: 1px solid var(--border); border-radius: 4px; background: var(--bg); color: var(--fg); font-size: 13px; resize: vertical; margin-bottom: 8px; font-family: inherit;"
|
|
22
|
+
></textarea>
|
|
23
|
+
<button class="highlight-create-btn" type="button">
|
|
24
|
+
Create Highlight
|
|
25
|
+
</button>
|
|
26
|
+
`;let J=C.querySelector(".highlight-create-btn");if(J)J.addEventListener("click",async(j)=>{j.stopPropagation(),await g()});let q=C.querySelector(".highlight-close-btn");if(q)q.addEventListener("click",(j)=>{j.stopPropagation(),L()});return C},g=async()=>{if(!B)return;try{let C=document.querySelector(".content");if(!C)return;let J=C.textContent||"",q=B.range,j=document.createRange();j.selectNodeContents(C),j.setEnd(q.startContainer,q.startOffset);let K=j.toString().length,M=K+B.text.length,Q=G?.querySelector(".highlight-notes-input"),Z=Q?.value.trim()||void 0,$=window.location.pathname.replace("/view/",""),Y=await fetch("/api/highlights",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({resourcePath:$,startOffset:K,endOffset:M,highlightedText:B.text,notes:Z})});if(!Y.ok)throw Error("Failed to create highlight");let _=await Y.json();if(F.push({id:_.id,startOffset:K,endOffset:M,highlightedText:B.text,isStale:!1,notes:Z||null,createdAt:Date.now()}),x(),Q)Q.value="";L(),window.getSelection()?.removeAllRanges()}catch(C){console.error("[highlights] Failed to create highlight:",C)}},x=()=>{let C=document.querySelector(".content");if(!C)return;let J=C.querySelectorAll("mark.llmd-highlight");for(let K of J){let M=K.parentNode;if(M)M.replaceChild(document.createTextNode(K.textContent||""),K)}if(C.normalize(),F.length===0)return;let q=[...F].sort((K,M)=>M.startOffset-K.startOffset),j=C.textContent||"";for(let K of q)try{let M=document.createTreeWalker(C,NodeFilter.SHOW_TEXT),Q=0,Z=null,$=0,Y=null,_=0,U=M.nextNode();while(U){let D=U.textContent?.length||0,W=Q+D;if(Z===null&&Q<=K.startOffset&&K.startOffset<W)Z=U,$=K.startOffset-Q;if(Q<K.endOffset&&K.endOffset<=W){Y=U,_=K.endOffset-Q;break}Q=W,U=M.nextNode()}if(!(Z&&Y))continue;let V=document.createRange();V.setStart(Z,$),V.setEnd(Y,_);let X=document.createElement("mark");X.className=K.isStale?"llmd-highlight llmd-highlight-stale":"llmd-highlight",X.dataset.highlightId=K.id,X.title=K.isStale?"This highlight may be outdated":"",X.style.cursor="pointer",X.addEventListener("click",(D)=>{D.stopPropagation(),c(K,D)}),V.surroundContents(X)}catch(M){console.error("[highlights] Failed to render highlight:",M)}};var o=(C)=>{return new Date(C).toLocaleString("en-US",{month:"short",day:"numeric",hour:"numeric",minute:"2-digit"})},l=(C)=>{let J=document.querySelector(`[data-highlight-id="${C}"]`);if(J)J.scrollIntoView({behavior:"smooth",block:"center"}),J.classList.add("highlight-flash"),setTimeout(()=>{J.classList.remove("highlight-flash")},1000)},a=(C,J)=>C.length>J?`${C.slice(0,J)}...`:C,O=()=>{let C=document.querySelector(".content");if(!C)return;let J=window.location.pathname;if(!J.startsWith("/view/"))return;let q=J.slice(6);fetch(`/api/highlights/resource?path=${encodeURIComponent(q)}`).then((j)=>j.json()).then((j)=>{let K=j.highlights||[];if(K.length===0)return;let M=document.createElement("div");M.className="highlights-summary",M.innerHTML=`
|
|
27
|
+
<div style="background: var(--code-bg); border: 1px solid var(--border); border-radius: 8px; padding: 16px 20px; margin: 24px 0;">
|
|
28
|
+
<h3 style="font-size: 0.875rem; margin: 0 0 12px 0; text-transform: uppercase; letter-spacing: 0.08em; font-weight: 700; opacity: 0.8; display: flex; align-items: center; gap: 6px;">
|
|
29
|
+
<span style="font-size: 16px;">✨</span>
|
|
30
|
+
Highlights (${K.length})
|
|
31
|
+
</h3>
|
|
32
|
+
<ul style="list-style: none; padding: 0; margin: 0; font-size: 14px;">
|
|
33
|
+
${K.map(($)=>{let Y=$.isStale?'<span style="color: #f44336; margin-left: 4px;" title="Stale - file has changed">⚠️</span>':"",_=$.isStale?' style="text-decoration: line-through; opacity: 0.5;"':"",U=$.highlightedText.length>80?`${$.highlightedText.slice(0,80)}...`:$.highlightedText,V=$.notes?a($.notes,60):"",X=V?`<div style="margin-top: 4px; padding: 6px; background: var(--sidebar-bg); border-radius: 3px; font-size: 11px; opacity: 0.8;">\uD83D\uDCAD ${V}</div>`:"";return`
|
|
34
|
+
<li style="margin: 8px 0; padding: 8px; background: var(--bg); border-radius: 4px; border-left: 3px solid var(--accent); cursor: pointer; transition: background 0.2s;"
|
|
35
|
+
data-highlight-id="${$.id}"
|
|
36
|
+
class="highlight-summary-item"
|
|
37
|
+
onmouseover="this.style.background='var(--hover)'"
|
|
38
|
+
onmouseout="this.style.background='var(--bg)'">
|
|
39
|
+
<div${_}>${U}</div>
|
|
40
|
+
${X}
|
|
41
|
+
<div style="opacity: 0.5; font-size: 12px; margin-top: 4px;">
|
|
42
|
+
${o($.createdAt)}${Y}
|
|
43
|
+
</div>
|
|
44
|
+
</li>
|
|
45
|
+
`}).join("")}
|
|
46
|
+
</ul>
|
|
47
|
+
<div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border);">
|
|
48
|
+
<a href="/highlights" style="color: var(--accent); text-decoration: none; font-size: 13px; font-weight: 500;">
|
|
49
|
+
View all highlights →
|
|
50
|
+
</a>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
`;let Q=C.querySelector("h1, h2");if(Q)Q.parentNode?.insertBefore(M,Q);else C.insertBefore(M,C.firstChild);let Z=M.querySelectorAll(".highlight-summary-item");for(let $ of Array.from(Z)){let Y=$.getAttribute("data-highlight-id");if(Y)$.addEventListener("click",()=>l(Y))}}).catch((j)=>{console.error("Failed to load highlights summary:",j)})};var s=()=>{let C=document.querySelector(".sidebar");if(!C)return;let J=document.createElement("div");J.className="sidebar-resize-handle",C.appendChild(J);let q=!1,j=0,K=0,M=(Y)=>{q=!0,j=Y.clientX,K=C.offsetWidth,document.body.style.cursor="ew-resize",document.body.style.userSelect="none",Y.preventDefault()},Q=(Y)=>{if(!q)return;let _=Y.clientX-j,U=K+_,V=Math.max(200,Math.min(600,U));C.style.width=`${V}px`,localStorage.setItem("llmd-sidebar-width",V.toString())},Z=()=>{if(!q)return;q=!1,document.body.style.cursor="",document.body.style.userSelect=""};J.addEventListener("mousedown",M),document.addEventListener("mousemove",Q),document.addEventListener("mouseup",Z);let $=localStorage.getItem("llmd-sidebar-width");if($){let Y=Number.parseInt($,10);if(Y>=200&&Y<=600)C.style.width=`${Y}px`}};if(typeof window<"u")window.addEventListener("DOMContentLoaded",()=>{s()});var S=(C,J,q)=>{fetch("/api/events",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({type:C,path:J,resourceType:q})}).catch(()=>{})},N=(C)=>{S("open",C,"dir")},H=(C)=>{S("view",C,"file")};var E=async()=>{try{let C=window.location.pathname.replace("/view/",""),J=C.includes("/")?C.substring(0,C.lastIndexOf("/")):"",q=await fetch(`/api/highlights/directory?path=${encodeURIComponent(J||".")}`);if(!q.ok)return;let K=(await q.json()).highlights||[],M=new Set;for(let Z of K)M.add(Z.resourcePath);let Q=document.querySelectorAll("a[data-file-path]");for(let Z of Array.from(Q)){let $=Z.getAttribute("data-file-path");if($&&M.has($)){let Y=Z.querySelector(".file-icon svg");if(Y)Y.setAttribute("fill","var(--accent)"),Y.setAttribute("fill-opacity","0.3")}}}catch(C){console.error("[file-highlights] Failed to load highlight indicators:",C)}};if(typeof window<"u")window.addEventListener("DOMContentLoaded",()=>{E()});window.trackDirectoryOpen=N;window.trackFileView=H;R();E();if(document.querySelector(".content"))A(),O();console.log("[llmd] Client initialized");
|