Skip to content

Commit d6ec87e

Browse files
ithiria894claude
andcommitted
feat: markdown preview + new session button (v0.15.0)
- Render markdown in detail panel for memories, skills, rules, plans, agents, commands - Load marked.js v15 from CDN with graceful fallback - Strip YAML frontmatter before rendering - Full CSS for headers, code blocks, tables, blockquotes, lists (light/dark mode) - Add "+ New" button on session category bar — copies `cd <dir> && claude` to clipboard - Add AI_INDEX.md reference to README Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent eb79660 commit d6ec87e

5 files changed

Lines changed: 96 additions & 4 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Claude Code Organizer
22

3+
> **AI agents: read [AI_INDEX.md](AI_INDEX.md) first.** It is the navigation manifest for this codebase — where to find every module, how they connect, and where to look before making any claim about the code.
4+
35
[![npm version](https://img.shields.io/npm/v/@mcpware/claude-code-organizer)](https://www.npmjs.com/package/@mcpware/claude-code-organizer)
46
[![npm downloads](https://img.shields.io/npm/dt/@mcpware/claude-code-organizer?label=downloads)](https://www.npmjs.com/package/@mcpware/claude-code-organizer)
57
[![GitHub stars](https://img.shields.io/github/stars/mcpware/claude-code-organizer)](https://github.com/mcpware/claude-code-organizer/stargazers)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@mcpware/claude-code-organizer",
3-
"version": "0.14.13",
3+
"version": "0.15.0",
44
"description": "Organize all your Claude Code memories, skills, MCP servers, commands, agents, rules, and hooks — see what loads globally vs per-project, then move items between scopes",
55
"type": "module",
66
"files": [

src/ui/app.js

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,17 @@ function setupItemList() {
322322
return;
323323
}
324324

325+
const newSessionBtn = event.target.closest(".new-session-btn");
326+
if (newSessionBtn) {
327+
const scope = getScopeById(newSessionBtn.dataset.scopeId);
328+
const dir = scope?.repoDir || "~";
329+
const cmd = `cd ${dir} && claude`;
330+
navigator.clipboard.writeText(cmd).then(() => {
331+
toast("Copied: " + cmd);
332+
});
333+
return;
334+
}
335+
325336
const sortBtn = event.target.closest(".sort-btn");
326337
if (sortBtn) {
327338
const cat = sortBtn.dataset.cat;
@@ -339,7 +350,7 @@ function setupItemList() {
339350
}
340351

341352
const catHdr = event.target.closest(".cat-hdr");
342-
if (catHdr && !event.target.closest(".sort-btn")) {
353+
if (catHdr && !event.target.closest(".sort-btn") && !event.target.closest(".new-session-btn")) {
343354
const key = `${selectedScopeId}::${catHdr.dataset.cat}`;
344355
if (uiState.collapsedCats.has(key)) uiState.collapsedCats.delete(key);
345356
else uiState.collapsedCats.add(key);
@@ -842,6 +853,7 @@ function renderMainContent() {
842853
<span class="cat-hdr-ico">${config.icon}</span>
843854
<span class="cat-hdr-nm">${esc(config.label)}</span>
844855
<span class="cat-hdr-cnt">${pluralize(catItems.length, "item")}</span>
856+
${category === "session" ? `<button type="button" class="new-session-btn" data-scope-id="${esc(scope.id)}" title="Copy command to start a new session">+ New</button>` : ""}
845857
${category === "mcp" ? "" : `<span class="cat-hdr-sort">
846858
<button type="button" class="sort-btn${(uiState.sortBy[`${scope.id}::${category}`]?.field === "size") ? " active" : ""}" data-cat="${esc(category)}" data-sort="size">Size ${sortArrow(`${scope.id}::${category}`, "size")}</button>
847859
<button type="button" class="sort-btn${(uiState.sortBy[`${scope.id}::${category}`]?.field === "date") ? " active" : ""}" data-cat="${esc(category)}" data-sort="date">Date ${sortArrow(`${scope.id}::${category}`, "date")}</button>
@@ -1901,7 +1913,16 @@ async function loadPreview(item) {
19011913

19021914
const res = await fetchJson(`/api/file-content?path=${encodeURIComponent(filePath)}`);
19031915
if (currentKey !== detailPreviewKey) return;
1904-
preview.textContent = res.ok ? res.content : (res.error || "Cannot load preview");
1916+
if (!res.ok) { preview.textContent = res.error || "Cannot load preview"; return; }
1917+
1918+
// Render markdown for file-based categories (.md files)
1919+
const isMd = filePath.endsWith(".md") || ["memory", "skill", "rule", "plan", "agent", "command", "config"].includes(item.category);
1920+
if (isMd) {
1921+
preview.textContent = "";
1922+
preview.innerHTML = `<div class="md-preview">${renderMarkdown(res.content)}</div>`;
1923+
} else {
1924+
preview.textContent = res.content;
1925+
}
19051926
} catch {
19061927
if (currentKey !== detailPreviewKey) return;
19071928
preview.textContent = "Failed to load preview";
@@ -2741,6 +2762,20 @@ function esc(value) {
27412762
.replace(/"/g, "&quot;");
27422763
}
27432764

2765+
/**
2766+
* Render markdown to HTML using marked.js.
2767+
* Strips YAML frontmatter (---...---) common in memory/skill files.
2768+
* Falls back to escaped plain text if marked is not loaded.
2769+
*/
2770+
function renderMarkdown(text) {
2771+
if (!text) return "";
2772+
// Strip YAML frontmatter
2773+
let content = text.replace(/^---\n[\s\S]*?\n---\n?/, "").trim();
2774+
// Fallback if marked CDN failed to load
2775+
if (typeof marked === "undefined") return `<pre>${esc(content)}</pre>`;
2776+
return marked.parse(content);
2777+
}
2778+
27442779
function cssEscape(value) {
27452780
if (window.CSS?.escape) return window.CSS.escape(value);
27462781
return String(value).replace(/["\\]/g, "\\$&");

src/ui/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<title>Claude Code Organizer</title>
77
<link rel="stylesheet" href="/style.css">
88
<link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;700;900&family=Geist+Mono:wght@400;500&display=swap" rel="stylesheet">
9-
<!-- SortableJS disabled — drag-and-drop removed, use Move button instead -->
9+
<script src="https://cdn.jsdelivr.net/npm/marked@15/marked.min.js"></script>
1010
</head>
1111
<body>
1212
<div class="app" id="app">

src/ui/style.css

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,21 @@ body {
444444
border-radius: 10px;
445445
}
446446

447+
.new-session-btn {
448+
margin-left: 8px;
449+
padding: 2px 10px;
450+
border-radius: 6px;
451+
font-size: 0.62rem;
452+
font-weight: 600;
453+
border: 1px solid var(--accent-light);
454+
background: var(--accent-light);
455+
color: var(--accent-text);
456+
cursor: pointer;
457+
font-family: inherit;
458+
transition: all 100ms;
459+
}
460+
.new-session-btn:hover { background: var(--accent); color: #fff; border-color: var(--accent); }
461+
447462
.cat-hdr-sort { display: flex; gap: 4px; margin-left: 8px; }
448463
.sort-btn {
449464
padding: 2px 8px;
@@ -664,6 +679,46 @@ body {
664679
white-space: pre-wrap;
665680
}
666681

682+
/* ── Markdown preview (memories, skills, rules, plans, agents, commands) ── */
683+
.d-preview:has(.md-preview) { white-space: normal; font-family: "Lato", sans-serif; font-size: 0.8rem; line-height: 1.7; }
684+
.md-preview h1 { font-size: 1.15rem; font-weight: 900; color: var(--text-primary); margin: 0 0 8px; padding-bottom: 6px; border-bottom: 1px solid var(--border-light); }
685+
.md-preview h2 { font-size: 1rem; font-weight: 800; color: var(--text-primary); margin: 14px 0 6px; padding-bottom: 4px; border-bottom: 1px solid var(--border-light); }
686+
.md-preview h3 { font-size: 0.9rem; font-weight: 700; color: var(--text-primary); margin: 12px 0 4px; }
687+
.md-preview h4, .md-preview h5, .md-preview h6 { font-size: 0.82rem; font-weight: 700; color: var(--text-secondary); margin: 10px 0 4px; }
688+
.md-preview p { margin: 0 0 8px; color: var(--text-secondary); }
689+
.md-preview p:last-child { margin-bottom: 0; }
690+
.md-preview a { color: var(--accent); text-decoration: none; font-weight: 500; }
691+
.md-preview a:hover { text-decoration: underline; }
692+
.md-preview strong { font-weight: 700; color: var(--text-primary); }
693+
.md-preview em { font-style: italic; }
694+
.md-preview ul, .md-preview ol { margin: 4px 0 8px; padding-left: 20px; }
695+
.md-preview li { margin-bottom: 3px; }
696+
.md-preview li > ul, .md-preview li > ol { margin: 2px 0 0; }
697+
.md-preview blockquote {
698+
margin: 8px 0; padding: 6px 12px;
699+
border-left: 3px solid var(--accent); background: var(--accent-light);
700+
border-radius: 0 var(--radius) var(--radius) 0;
701+
}
702+
.md-preview blockquote p { color: var(--accent-text); margin-bottom: 4px; }
703+
.md-preview pre {
704+
background: var(--bg); border: 1px solid var(--border-light);
705+
border-radius: var(--radius); padding: 8px 10px; margin: 6px 0;
706+
overflow-x: auto; white-space: pre;
707+
font-family: "Geist Mono", monospace; font-size: 0.72rem; line-height: 1.5;
708+
}
709+
.md-preview pre code { background: none; border: none; padding: 0; font-size: inherit; }
710+
.md-preview code {
711+
background: var(--bg); border: 1px solid var(--border-light);
712+
border-radius: 3px; padding: 1px 4px;
713+
font-family: "Geist Mono", monospace; font-size: 0.72rem;
714+
}
715+
.md-preview table { width: 100%; border-collapse: collapse; margin: 8px 0; font-size: 0.75rem; }
716+
.md-preview th { text-align: left; font-weight: 700; font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.3px; color: var(--text-faint); padding: 4px 8px; border-bottom: 2px solid var(--border-light); }
717+
.md-preview td { padding: 4px 8px; border-bottom: 1px solid var(--border-light); vertical-align: top; }
718+
.md-preview tr:last-child td { border-bottom: none; }
719+
.md-preview hr { border: none; border-top: 1px solid var(--border-light); margin: 12px 0; }
720+
.md-preview img { max-width: 100%; border-radius: var(--radius); }
721+
667722
/* ── Session chat preview ──────────────────────────────────────── */
668723
.d-preview:has(.chat-container) { white-space: normal; padding: 6px 10px; }
669724
.chat-container { display: flex; flex-direction: column; gap: 0; padding: 4px 0; }

0 commit comments

Comments
 (0)