mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 18:10:18 +05:30
fix(batch): resolve pandoc path handling and include-subfolders option
- Normalize pandoc command parsing with path.basename() to support bundled binary paths - Use bundled pandoc binary in convertWithPandoc instead of relying on PATH - Forward includeSubfolders checkbox state from renderer to main process - Add pandoc availability check before batch conversion - Re-enable Start button when batch conversion completes - Clean up obsolete dist build artifact causing test snapshot warning - Bump version to 4.4.4
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
const path = require('path');
|
||||
|
||||
function renderExplorerPanel(container, { listDirectory, onFileOpen, currentDir }) {
|
||||
container.innerHTML = `
|
||||
container.innerHTML = `
|
||||
<div class="explorer-panel">
|
||||
<div class="explorer-toolbar">
|
||||
<input type="text" class="explorer-path" id="explorer-path" value="${currentDir || ''}" placeholder="Open a folder..." readonly>
|
||||
@@ -11,60 +9,86 @@ function renderExplorerPanel(container, { listDirectory, onFileOpen, currentDir
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.getElementById('explorer-browse')?.addEventListener('click', async () => {
|
||||
const dir = await listDirectory(null); // null means open folder dialog
|
||||
if (dir) {
|
||||
document.getElementById('explorer-path').value = dir.path;
|
||||
renderTree(document.getElementById('explorer-tree'), dir.entries, listDirectory, onFileOpen, dir.path);
|
||||
}
|
||||
});
|
||||
|
||||
if (currentDir) {
|
||||
listDirectory(currentDir).then(dir => {
|
||||
if (dir) renderTree(document.getElementById('explorer-tree'), dir.entries, listDirectory, onFileOpen, currentDir);
|
||||
});
|
||||
document.getElementById('explorer-browse')?.addEventListener('click', async () => {
|
||||
const dir = await listDirectory(null); // null means open folder dialog
|
||||
if (dir) {
|
||||
document.getElementById('explorer-path').value = dir.path;
|
||||
renderTree(
|
||||
document.getElementById('explorer-tree'),
|
||||
dir.entries,
|
||||
listDirectory,
|
||||
onFileOpen,
|
||||
dir.path
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if (currentDir) {
|
||||
listDirectory(currentDir).then((dir) => {
|
||||
if (dir)
|
||||
renderTree(
|
||||
document.getElementById('explorer-tree'),
|
||||
dir.entries,
|
||||
listDirectory,
|
||||
onFileOpen,
|
||||
currentDir
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function renderTree(container, entries, listDirectory, onFileOpen, basePath) {
|
||||
container.innerHTML = entries.map(entry => {
|
||||
if (entry.isDirectory) {
|
||||
return `<div class="tree-item tree-folder collapsed" data-path="${entry.path}">
|
||||
function renderTree(container, entries, listDirectory, onFileOpen, _basePath) {
|
||||
container.innerHTML = entries
|
||||
.map((entry) => {
|
||||
if (entry.isDirectory) {
|
||||
return `<div class="tree-item tree-folder collapsed" data-path="${entry.path}">
|
||||
<span class="tree-icon">▶</span>
|
||||
<span class="tree-name">${entry.name}</span>
|
||||
<div class="tree-children"></div>
|
||||
</div>`;
|
||||
}
|
||||
return `<div class="tree-item tree-file" data-path="${entry.path}">
|
||||
}
|
||||
return `<div class="tree-item tree-file" data-path="${entry.path}">
|
||||
<span class="tree-icon">${getFileIcon(entry.name)}</span>
|
||||
<span class="tree-name">${entry.name}</span>
|
||||
</div>`;
|
||||
}).join('');
|
||||
})
|
||||
.join('');
|
||||
|
||||
container.querySelectorAll('.tree-folder').forEach(el => {
|
||||
el.querySelector('.tree-name').addEventListener('click', async () => {
|
||||
const isCollapsed = el.classList.contains('collapsed');
|
||||
if (isCollapsed) {
|
||||
const dir = await listDirectory(el.dataset.path);
|
||||
if (dir) {
|
||||
const childContainer = el.querySelector('.tree-children');
|
||||
renderTree(childContainer, dir.entries, listDirectory, onFileOpen, el.dataset.path);
|
||||
}
|
||||
}
|
||||
el.classList.toggle('collapsed');
|
||||
el.querySelector('.tree-icon').textContent = el.classList.contains('collapsed') ? '\u25B6' : '\u25BC';
|
||||
});
|
||||
container.querySelectorAll('.tree-folder').forEach((el) => {
|
||||
el.querySelector('.tree-name').addEventListener('click', async () => {
|
||||
const isCollapsed = el.classList.contains('collapsed');
|
||||
if (isCollapsed) {
|
||||
const dir = await listDirectory(el.dataset.path);
|
||||
if (dir) {
|
||||
const childContainer = el.querySelector('.tree-children');
|
||||
renderTree(childContainer, dir.entries, listDirectory, onFileOpen, el.dataset.path);
|
||||
}
|
||||
}
|
||||
el.classList.toggle('collapsed');
|
||||
el.querySelector('.tree-icon').textContent = el.classList.contains('collapsed')
|
||||
? '\u25B6'
|
||||
: '\u25BC';
|
||||
});
|
||||
});
|
||||
|
||||
container.querySelectorAll('.tree-file').forEach(el => {
|
||||
el.addEventListener('click', () => onFileOpen(el.dataset.path));
|
||||
});
|
||||
container.querySelectorAll('.tree-file').forEach((el) => {
|
||||
el.addEventListener('click', () => onFileOpen(el.dataset.path));
|
||||
});
|
||||
}
|
||||
|
||||
function getFileIcon(filename) {
|
||||
const ext = filename.split('.').pop().toLowerCase();
|
||||
const icons = { md: '\u{1F4DD}', js: '\u{1F4DC}', json: '{}', html: '\u{1F310}', css: '\u{1F3A8}', py: '\u{1F40D}', pdf: '\u{1F4D5}', txt: '\u{1F4C4}' };
|
||||
return icons[ext] || '\u{1F4C4}';
|
||||
const ext = filename.split('.').pop().toLowerCase();
|
||||
const icons = {
|
||||
md: '\u{1F4DD}',
|
||||
js: '\u{1F4DC}',
|
||||
json: '{}',
|
||||
html: '\u{1F310}',
|
||||
css: '\u{1F3A8}',
|
||||
py: '\u{1F40D}',
|
||||
pdf: '\u{1F4D5}',
|
||||
txt: '\u{1F4C4}',
|
||||
};
|
||||
return icons[ext] || '\u{1F4C4}';
|
||||
}
|
||||
|
||||
module.exports = { renderExplorerPanel };
|
||||
|
||||
+55
-45
@@ -1,5 +1,5 @@
|
||||
function renderGitPanel(container, { gitStatus, gitDiff, gitStage, gitCommit, gitLog }) {
|
||||
container.innerHTML = `
|
||||
function renderGitPanel(container, { gitStatus, _gitDiff, gitStage, gitCommit, gitLog }) {
|
||||
container.innerHTML = `
|
||||
<div class="git-panel">
|
||||
<div class="git-section">
|
||||
<h4 class="git-section-title">Changes</h4>
|
||||
@@ -19,66 +19,76 @@ function renderGitPanel(container, { gitStatus, gitDiff, gitStage, gitCommit, gi
|
||||
</div>
|
||||
`;
|
||||
|
||||
loadGitStatus();
|
||||
loadGitStatus();
|
||||
|
||||
async function loadGitStatus() {
|
||||
const status = await gitStatus();
|
||||
const changesEl = document.getElementById('git-changes');
|
||||
if (!status || !changesEl) return;
|
||||
async function loadGitStatus() {
|
||||
const status = await gitStatus();
|
||||
const changesEl = document.getElementById('git-changes');
|
||||
if (!status || !changesEl) return;
|
||||
|
||||
if (status.error) {
|
||||
changesEl.innerHTML = `<p class="git-info">${status.error}</p>`;
|
||||
return;
|
||||
}
|
||||
if (status.error) {
|
||||
changesEl.innerHTML = `<p class="git-info">${status.error}</p>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const files = [
|
||||
...status.modified.map(f => ({ file: f, status: 'M', color: '#f59e0b' })),
|
||||
...status.not_added.map(f => ({ file: f, status: '?', color: '#6b7280' })),
|
||||
...status.created.map(f => ({ file: f, status: 'A', color: '#10b981' })),
|
||||
...status.deleted.map(f => ({ file: f, status: 'D', color: '#ef4444' })),
|
||||
...status.staged.map(f => ({ file: f, status: 'S', color: '#3b82f6' })),
|
||||
];
|
||||
const files = [
|
||||
...status.modified.map((f) => ({ file: f, status: 'M', color: '#f59e0b' })),
|
||||
...status.not_added.map((f) => ({ file: f, status: '?', color: '#6b7280' })),
|
||||
...status.created.map((f) => ({ file: f, status: 'A', color: '#10b981' })),
|
||||
...status.deleted.map((f) => ({ file: f, status: 'D', color: '#ef4444' })),
|
||||
...status.staged.map((f) => ({ file: f, status: 'S', color: '#3b82f6' })),
|
||||
];
|
||||
|
||||
if (files.length === 0) {
|
||||
changesEl.innerHTML = '<p class="git-info">No changes</p>';
|
||||
} else {
|
||||
changesEl.innerHTML = files.map(f => `
|
||||
if (files.length === 0) {
|
||||
changesEl.innerHTML = '<p class="git-info">No changes</p>';
|
||||
} else {
|
||||
changesEl.innerHTML = files
|
||||
.map(
|
||||
(f) => `
|
||||
<div class="git-file" data-file="${f.file}">
|
||||
<span class="git-file-status" style="color:${f.color}">${f.status}</span>
|
||||
<span class="git-file-name">${f.file}</span>
|
||||
<button class="git-stage-btn" data-file="${f.file}" title="Stage file">+</button>
|
||||
</div>
|
||||
`).join('');
|
||||
`
|
||||
)
|
||||
.join('');
|
||||
|
||||
changesEl.querySelectorAll('.git-stage-btn').forEach(btn => {
|
||||
btn.addEventListener('click', async (e) => {
|
||||
e.stopPropagation();
|
||||
await gitStage([btn.dataset.file]);
|
||||
loadGitStatus();
|
||||
});
|
||||
});
|
||||
}
|
||||
changesEl.querySelectorAll('.git-stage-btn').forEach((btn) => {
|
||||
btn.addEventListener('click', async (e) => {
|
||||
e.stopPropagation();
|
||||
await gitStage([btn.dataset.file]);
|
||||
loadGitStatus();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Load log
|
||||
const log = await gitLog();
|
||||
const logEl = document.getElementById('git-log');
|
||||
if (log && logEl) {
|
||||
logEl.innerHTML = (log.all || []).slice(0, 10).map(entry => `
|
||||
// Load log
|
||||
const log = await gitLog();
|
||||
const logEl = document.getElementById('git-log');
|
||||
if (log && logEl) {
|
||||
logEl.innerHTML =
|
||||
(log.all || [])
|
||||
.slice(0, 10)
|
||||
.map(
|
||||
(entry) => `
|
||||
<div class="git-log-entry">
|
||||
<div class="git-log-msg">${entry.message}</div>
|
||||
<div class="git-log-meta">${entry.date?.substring(0, 10) || ''} · ${entry.author_name || ''}</div>
|
||||
</div>
|
||||
`).join('') || '<p class="git-info">No commits</p>';
|
||||
}
|
||||
`
|
||||
)
|
||||
.join('') || '<p class="git-info">No commits</p>';
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('git-commit-btn')?.addEventListener('click', async () => {
|
||||
const msg = document.getElementById('git-commit-msg')?.value?.trim();
|
||||
if (!msg) return;
|
||||
await gitCommit(msg);
|
||||
document.getElementById('git-commit-msg').value = '';
|
||||
loadGitStatus();
|
||||
});
|
||||
document.getElementById('git-commit-btn')?.addEventListener('click', async () => {
|
||||
const msg = document.getElementById('git-commit-msg')?.value?.trim();
|
||||
if (!msg) return;
|
||||
await gitCommit(msg);
|
||||
document.getElementById('git-commit-msg').value = '';
|
||||
loadGitStatus();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { renderGitPanel };
|
||||
|
||||
@@ -4,54 +4,54 @@
|
||||
*/
|
||||
|
||||
function renderOutlinePanel(container, { getEditorContent, getActiveLine, onHeadingClick }) {
|
||||
container.innerHTML = `
|
||||
container.innerHTML = `
|
||||
<div class="outline-panel">
|
||||
<div class="outline-list" id="outline-list"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const listEl = document.getElementById('outline-list');
|
||||
let headings = [];
|
||||
let activeLine = 1;
|
||||
let debounceTimer = null;
|
||||
const listEl = document.getElementById('outline-list');
|
||||
let headings = [];
|
||||
let activeLine = 1;
|
||||
let debounceTimer = null;
|
||||
|
||||
function parseHeadings(content) {
|
||||
const result = [];
|
||||
if (!content) return result;
|
||||
const lines = content.split('\n');
|
||||
const regex = /^(#{1,6})\s+(.+)$/;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const match = regex.exec(lines[i]);
|
||||
if (match) {
|
||||
result.push({
|
||||
level: match[1].length,
|
||||
text: match[2].trim(),
|
||||
line: i + 1
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
function parseHeadings(content) {
|
||||
const result = [];
|
||||
if (!content) return result;
|
||||
const lines = content.split('\n');
|
||||
const regex = /^(#{1,6})\s+(.+)$/;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const match = regex.exec(lines[i]);
|
||||
if (match) {
|
||||
result.push({
|
||||
level: match[1].length,
|
||||
text: match[2].trim(),
|
||||
line: i + 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function findActiveHeading(currentLine) {
|
||||
let active = null;
|
||||
for (const h of headings) {
|
||||
if (h.line <= currentLine) {
|
||||
active = h;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return active;
|
||||
function findActiveHeading(currentLine) {
|
||||
let active = null;
|
||||
for (const h of headings) {
|
||||
if (h.line <= currentLine) {
|
||||
active = h;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return active;
|
||||
}
|
||||
|
||||
function renderHeadings() {
|
||||
const content = getEditorContent();
|
||||
headings = parseHeadings(content);
|
||||
activeLine = getActiveLine();
|
||||
function renderHeadings() {
|
||||
const content = getEditorContent();
|
||||
headings = parseHeadings(content);
|
||||
activeLine = getActiveLine();
|
||||
|
||||
if (headings.length === 0) {
|
||||
listEl.innerHTML = `
|
||||
if (headings.length === 0) {
|
||||
listEl.innerHTML = `
|
||||
<div class="outline-empty">
|
||||
<p>No headings found</p>
|
||||
<p class="outline-hint"># Heading 1</p>
|
||||
@@ -59,56 +59,62 @@ function renderOutlinePanel(container, { getEditorContent, getActiveLine, onHead
|
||||
<p class="outline-hint">### Heading 3</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const activeHeading = findActiveHeading(activeLine);
|
||||
const activeHeading = findActiveHeading(activeLine);
|
||||
|
||||
listEl.innerHTML = headings.map((h, idx) => `
|
||||
listEl.innerHTML =
|
||||
headings
|
||||
.map(
|
||||
(h, idx) => `
|
||||
<div class="outline-item outline-level-${h.level}${activeHeading && h.line === activeHeading.line ? ' active' : ''}"
|
||||
data-line="${h.line}" data-index="${idx}">
|
||||
<span class="outline-text">${escapeHtml(h.text)}</span>
|
||||
<span class="outline-badge">H${h.level}</span>
|
||||
</div>
|
||||
`).join('') + `<div class="outline-footer">${headings.length} heading${headings.length !== 1 ? 's' : ''}</div>`;
|
||||
`
|
||||
)
|
||||
.join('') +
|
||||
`<div class="outline-footer">${headings.length} heading${headings.length !== 1 ? 's' : ''}</div>`;
|
||||
|
||||
listEl.querySelectorAll('.outline-item').forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
const line = parseInt(item.dataset.line, 10);
|
||||
onHeadingClick(line);
|
||||
});
|
||||
});
|
||||
}
|
||||
listEl.querySelectorAll('.outline-item').forEach((item) => {
|
||||
item.addEventListener('click', () => {
|
||||
const line = parseInt(item.dataset.line, 10);
|
||||
onHeadingClick(line);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
clearTimeout(debounceTimer);
|
||||
debounceTimer = setTimeout(renderHeadings, 300);
|
||||
}
|
||||
function refresh() {
|
||||
clearTimeout(debounceTimer);
|
||||
debounceTimer = setTimeout(renderHeadings, 300);
|
||||
}
|
||||
|
||||
function setActiveHeading(line) {
|
||||
activeLine = line;
|
||||
const activeHeading = findActiveHeading(line);
|
||||
function setActiveHeading(line) {
|
||||
activeLine = line;
|
||||
const activeHeading = findActiveHeading(line);
|
||||
|
||||
listEl.querySelectorAll('.outline-item').forEach(item => {
|
||||
const itemLine = parseInt(item.dataset.line, 10);
|
||||
if (activeHeading && itemLine === activeHeading.line) {
|
||||
item.classList.add('active');
|
||||
} else {
|
||||
item.classList.remove('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
listEl.querySelectorAll('.outline-item').forEach((item) => {
|
||||
const itemLine = parseInt(item.dataset.line, 10);
|
||||
if (activeHeading && itemLine === activeHeading.line) {
|
||||
item.classList.add('active');
|
||||
} else {
|
||||
item.classList.remove('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
container._refreshOutline = refresh;
|
||||
container._setActiveHeading = setActiveHeading;
|
||||
container._refreshOutline = refresh;
|
||||
container._setActiveHeading = setActiveHeading;
|
||||
|
||||
renderHeadings();
|
||||
renderHeadings();
|
||||
}
|
||||
|
||||
module.exports = { renderOutlinePanel };
|
||||
|
||||
@@ -1,50 +1,52 @@
|
||||
class SidebarManager {
|
||||
constructor() {
|
||||
this.sidebar = document.getElementById('sidebar');
|
||||
this.panelContent = document.getElementById('sidebar-panel-content');
|
||||
this.panelTitle = document.querySelector('.sidebar-panel-title');
|
||||
this.activePanel = null;
|
||||
this.panels = new Map();
|
||||
this.setupEventListeners();
|
||||
}
|
||||
constructor() {
|
||||
this.sidebar = document.getElementById('sidebar');
|
||||
this.panelContent = document.getElementById('sidebar-panel-content');
|
||||
this.panelTitle = document.querySelector('.sidebar-panel-title');
|
||||
this.activePanel = null;
|
||||
this.panels = new Map();
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
document.querySelectorAll('.sidebar-icon').forEach(btn => {
|
||||
btn.addEventListener('click', () => this.togglePanel(btn.dataset.panel));
|
||||
});
|
||||
document.querySelector('.sidebar-panel-close')?.addEventListener('click', () => this.collapse());
|
||||
}
|
||||
setupEventListeners() {
|
||||
document.querySelectorAll('.sidebar-icon').forEach((btn) => {
|
||||
btn.addEventListener('click', () => this.togglePanel(btn.dataset.panel));
|
||||
});
|
||||
document
|
||||
.querySelector('.sidebar-panel-close')
|
||||
?.addEventListener('click', () => this.collapse());
|
||||
}
|
||||
|
||||
registerPanel(name, { title, render }) {
|
||||
this.panels.set(name, { title, render });
|
||||
}
|
||||
registerPanel(name, { title, render }) {
|
||||
this.panels.set(name, { title, render });
|
||||
}
|
||||
|
||||
togglePanel(name) {
|
||||
if (this.activePanel === name) {
|
||||
this.collapse();
|
||||
} else {
|
||||
this.expand(name);
|
||||
}
|
||||
togglePanel(name) {
|
||||
if (this.activePanel === name) {
|
||||
this.collapse();
|
||||
} else {
|
||||
this.expand(name);
|
||||
}
|
||||
}
|
||||
|
||||
expand(name) {
|
||||
const panel = this.panels.get(name);
|
||||
if (!panel) return;
|
||||
this.sidebar.classList.remove('collapsed');
|
||||
this.panelTitle.textContent = panel.title;
|
||||
this.panelContent.innerHTML = '';
|
||||
panel.render(this.panelContent);
|
||||
this.activePanel = name;
|
||||
document.querySelectorAll('.sidebar-icon').forEach(btn => {
|
||||
btn.classList.toggle('active', btn.dataset.panel === name);
|
||||
});
|
||||
}
|
||||
expand(name) {
|
||||
const panel = this.panels.get(name);
|
||||
if (!panel) return;
|
||||
this.sidebar.classList.remove('collapsed');
|
||||
this.panelTitle.textContent = panel.title;
|
||||
this.panelContent.innerHTML = '';
|
||||
panel.render(this.panelContent);
|
||||
this.activePanel = name;
|
||||
document.querySelectorAll('.sidebar-icon').forEach((btn) => {
|
||||
btn.classList.toggle('active', btn.dataset.panel === name);
|
||||
});
|
||||
}
|
||||
|
||||
collapse() {
|
||||
this.sidebar.classList.add('collapsed');
|
||||
this.activePanel = null;
|
||||
document.querySelectorAll('.sidebar-icon').forEach(btn => btn.classList.remove('active'));
|
||||
}
|
||||
collapse() {
|
||||
this.sidebar.classList.add('collapsed');
|
||||
this.activePanel = null;
|
||||
document.querySelectorAll('.sidebar-icon').forEach((btn) => btn.classList.remove('active'));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { SidebarManager };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
function renderSnippetsPanel(container, { getSnippets, saveSnippet, deleteSnippet, onInsert }) {
|
||||
container.innerHTML = `
|
||||
container.innerHTML = `
|
||||
<div class="snippets-panel">
|
||||
<div class="snippets-toolbar">
|
||||
<input type="text" class="snippets-search" id="snippets-search" placeholder="Search snippets...">
|
||||
@@ -9,22 +9,29 @@ function renderSnippetsPanel(container, { getSnippets, saveSnippet, deleteSnippe
|
||||
</div>
|
||||
`;
|
||||
|
||||
let snippets = [];
|
||||
let snippets = [];
|
||||
|
||||
async function loadSnippets() {
|
||||
snippets = await getSnippets() || [];
|
||||
renderList(document.getElementById('snippets-search')?.value || '');
|
||||
}
|
||||
async function loadSnippets() {
|
||||
snippets = (await getSnippets()) || [];
|
||||
renderList(document.getElementById('snippets-search')?.value || '');
|
||||
}
|
||||
|
||||
function renderList(query) {
|
||||
const list = document.getElementById('snippets-list');
|
||||
if (!list) return;
|
||||
function renderList(query) {
|
||||
const list = document.getElementById('snippets-list');
|
||||
if (!list) return;
|
||||
|
||||
const filtered = query
|
||||
? snippets.filter(s => s.name.toLowerCase().includes(query.toLowerCase()) || (s.language || '').toLowerCase().includes(query.toLowerCase()))
|
||||
: snippets;
|
||||
const filtered = query
|
||||
? snippets.filter(
|
||||
(s) =>
|
||||
s.name.toLowerCase().includes(query.toLowerCase()) ||
|
||||
(s.language || '').toLowerCase().includes(query.toLowerCase())
|
||||
)
|
||||
: snippets;
|
||||
|
||||
list.innerHTML = filtered.length ? filtered.map(s => `
|
||||
list.innerHTML = filtered.length
|
||||
? filtered
|
||||
.map(
|
||||
(s) => `
|
||||
<div class="snippet-item" data-id="${s.id}">
|
||||
<div class="snippet-header">
|
||||
<span class="snippet-name">${s.name}</span>
|
||||
@@ -36,36 +43,41 @@ function renderSnippetsPanel(container, { getSnippets, saveSnippet, deleteSnippe
|
||||
<button class="snippet-delete" data-id="${s.id}" title="Delete">×</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('') : '<p class="git-info">No snippets yet. Click + to add one.</p>';
|
||||
`
|
||||
)
|
||||
.join('')
|
||||
: '<p class="git-info">No snippets yet. Click + to add one.</p>';
|
||||
|
||||
list.querySelectorAll('.snippet-insert').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const s = snippets.find(sn => sn.id === btn.dataset.id);
|
||||
if (s) onInsert(s.code);
|
||||
});
|
||||
});
|
||||
|
||||
list.querySelectorAll('.snippet-delete').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
await deleteSnippet(btn.dataset.id);
|
||||
loadSnippets();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('snippets-search')?.addEventListener('input', (e) => renderList(e.target.value));
|
||||
|
||||
document.getElementById('snippets-add')?.addEventListener('click', () => {
|
||||
const name = prompt('Snippet name:');
|
||||
if (!name) return;
|
||||
const language = prompt('Language (e.g., javascript, python, html):') || 'text';
|
||||
const code = prompt('Paste your code snippet:');
|
||||
if (!code) return;
|
||||
|
||||
saveSnippet({ id: Date.now().toString(), name, language, code }).then(() => loadSnippets());
|
||||
list.querySelectorAll('.snippet-insert').forEach((btn) => {
|
||||
btn.addEventListener('click', () => {
|
||||
const s = snippets.find((sn) => sn.id === btn.dataset.id);
|
||||
if (s) onInsert(s.code);
|
||||
});
|
||||
});
|
||||
|
||||
loadSnippets();
|
||||
list.querySelectorAll('.snippet-delete').forEach((btn) => {
|
||||
btn.addEventListener('click', async () => {
|
||||
await deleteSnippet(btn.dataset.id);
|
||||
loadSnippets();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
document
|
||||
.getElementById('snippets-search')
|
||||
?.addEventListener('input', (e) => renderList(e.target.value));
|
||||
|
||||
document.getElementById('snippets-add')?.addEventListener('click', () => {
|
||||
const name = prompt('Snippet name:');
|
||||
if (!name) return;
|
||||
const language = prompt('Language (e.g., javascript, python, html):') || 'text';
|
||||
const code = prompt('Paste your code snippet:');
|
||||
if (!code) return;
|
||||
|
||||
saveSnippet({ id: Date.now().toString(), name, language, code }).then(() => loadSnippets());
|
||||
});
|
||||
|
||||
loadSnippets();
|
||||
}
|
||||
|
||||
module.exports = { renderSnippetsPanel };
|
||||
|
||||
@@ -1,34 +1,39 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const templates = [
|
||||
{ name: 'Blog Post', file: 'blog-post.md', description: 'Article with frontmatter' },
|
||||
{ name: 'Meeting Notes', file: 'meeting-notes.md', description: 'Agenda, notes, action items' },
|
||||
{ name: 'Technical Spec', file: 'technical-spec.md', description: 'Requirements and architecture' },
|
||||
{ name: 'Changelog', file: 'changelog.md', description: 'Keep a Changelog format' },
|
||||
{ name: 'README', file: 'readme.md', description: 'Project documentation' },
|
||||
{ name: 'Project Plan', file: 'project-plan.md', description: 'Goals, milestones, timeline' },
|
||||
{ name: 'API Docs', file: 'api-docs.md', description: 'API endpoint documentation' },
|
||||
{ name: 'Tutorial', file: 'tutorial.md', description: 'Step-by-step guide' },
|
||||
{ name: 'Release Notes', file: 'release-notes.md', description: 'Version release summary' },
|
||||
{ name: 'Comparison', file: 'comparison.md', description: 'Feature comparison table' },
|
||||
{ name: 'Blog Post', file: 'blog-post.md', description: 'Article with frontmatter' },
|
||||
{ name: 'Meeting Notes', file: 'meeting-notes.md', description: 'Agenda, notes, action items' },
|
||||
{
|
||||
name: 'Technical Spec',
|
||||
file: 'technical-spec.md',
|
||||
description: 'Requirements and architecture',
|
||||
},
|
||||
{ name: 'Changelog', file: 'changelog.md', description: 'Keep a Changelog format' },
|
||||
{ name: 'README', file: 'readme.md', description: 'Project documentation' },
|
||||
{ name: 'Project Plan', file: 'project-plan.md', description: 'Goals, milestones, timeline' },
|
||||
{ name: 'API Docs', file: 'api-docs.md', description: 'API endpoint documentation' },
|
||||
{ name: 'Tutorial', file: 'tutorial.md', description: 'Step-by-step guide' },
|
||||
{ name: 'Release Notes', file: 'release-notes.md', description: 'Version release summary' },
|
||||
{ name: 'Comparison', file: 'comparison.md', description: 'Feature comparison table' },
|
||||
];
|
||||
|
||||
function renderTemplatesPanel(container, onSelect) {
|
||||
container.innerHTML = `
|
||||
container.innerHTML = `
|
||||
<div class="panel-list">
|
||||
${templates.map(t => `
|
||||
${templates
|
||||
.map(
|
||||
(t) => `
|
||||
<div class="panel-list-item template-item" data-file="${t.file}">
|
||||
<div class="panel-list-item-title">${t.name}</div>
|
||||
<div class="panel-list-item-desc">${t.description}</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
`
|
||||
)
|
||||
.join('')}
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.querySelectorAll('.template-item').forEach(el => {
|
||||
el.addEventListener('click', () => onSelect(el.dataset.file));
|
||||
});
|
||||
container.querySelectorAll('.template-item').forEach((el) => {
|
||||
el.addEventListener('click', () => onSelect(el.dataset.file));
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { renderTemplatesPanel, templates };
|
||||
|
||||
Reference in New Issue
Block a user