feat: roadmap items #7 #8 #9 #10 — recent files, semver tag, search filters, dedup

- #7: Section « Récents » dans la page d'accueil vault (5 derniers fichiers)
- #8: Git tag v1.8.0
- #9: Filtres search avancés created:/modified:/size: avec parsing dates/sizes
- #10: Déduplication IGNORED_DIRS (indexer.py source unique, watcher+main importent)
- ROADMAP: tous les items marqués FAIT, version → 1.9.0
This commit is contained in:
2026-06-04 12:11:51 -04:00
parent fc1a4e336e
commit 0b3a7d5c95
7 changed files with 353 additions and 30 deletions
+23 -1
View File
@@ -56,7 +56,8 @@ export const SearchHistory = {
// ---------------------------------------------------------------------------
export const QueryParser = {
parse(raw) {
const result = { tags: [], vault: null, title: null, path: null, ext: null, freeText: "" };
const result = { tags: [], vault: null, title: null, path: null, ext: null,
created: null, modified: null, size: null, freeText: "" };
if (!raw) return result;
const tokens = this._tokenize(raw);
const freeTokens = [];
@@ -75,6 +76,12 @@ export const QueryParser = {
result.path = tok.slice(5).replace(/"/g, "").trim();
} else if (lower.startsWith("ext:")) {
result.ext = tok.slice(4).replace(/"/g, "").trim().replace(/^\./, "").toLowerCase();
} else if (lower.startsWith("created:")) {
result.created = tok.slice(8).replace(/"/g, "").trim();
} else if (lower.startsWith("modified:")) {
result.modified = tok.slice(9).replace(/"/g, "").trim();
} else if (lower.startsWith("size:")) {
result.size = tok.slice(5).replace(/"/g, "").trim();
} else {
freeTokens.push(tok);
}
@@ -434,6 +441,18 @@ export const SearchChips = {
this._addChip("ext", `ext:${parsed.ext}`, parsed.ext);
hasChips = true;
}
if (parsed.created) {
this._addChip("date", `created:${parsed.created}`, `créé ${parsed.created}`);
hasChips = true;
}
if (parsed.modified) {
this._addChip("date", `modified:${parsed.modified}`, `modifié ${parsed.modified}`);
hasChips = true;
}
if (parsed.size) {
this._addChip("size", `size:${parsed.size}`, `taille ${parsed.size}`);
hasChips = true;
}
this._container.hidden = !hasChips;
},
clear() {
@@ -762,6 +781,9 @@ export async function performAdvancedSearch(query, vaultFilter, tagFilter, offse
const excludeEl = document.getElementById("search-exclude-input");
if (includeEl?.value.trim()) url += `&include_paths=${encodeURIComponent(includeEl.value.trim())}`;
if (excludeEl?.value.trim()) url += `&exclude_paths=${encodeURIComponent(excludeEl.value.trim())}`;
if (parsed.created) url += `&created=${encodeURIComponent(parsed.created)}`;
if (parsed.modified) url += `&modified=${encodeURIComponent(parsed.modified)}`;
if (parsed.size) url += `&size=${encodeURIComponent(parsed.size)}`;
// Search timeout — abort if server takes too long
const timeoutId = setTimeout(
+61
View File
@@ -80,6 +80,11 @@ export async function showVaultHome(vaultName, dirPath = '') {
</div>`;
await loadAndRender(vaultName, state.currentDir, resultsArea);
// Also load recent files for this vault (only at root level)
if (!state.currentDir) {
loadRecentSection(vaultName, container);
}
}
/**
@@ -285,6 +290,62 @@ async function loadAndRender(vaultName, dirPath, container) {
}
}
// ---------------------------------------------------------------------------
// Recent files section (loaded at vault root only)
// ---------------------------------------------------------------------------
async function loadRecentSection(vaultName, container) {
try {
const data = await api(`/api/recent?mode=opened&vault=${encodeURIComponent(vaultName)}&limit=5`);
const files = data.files || [];
if (files.length === 0) return;
// Insert recent section BEFORE the results area
const resultsArea = document.getElementById('vault-home-results');
const section = el('div', { class: 'vault-home-recent' });
const header = el('div', { class: 'vault-home-recent-header' });
header.innerHTML = `
<i data-lucide="clock" style="width:14px;height:14px"></i>
<span>Récents</span>`;
section.appendChild(header);
const list = el('div', { class: 'vault-home-recent-list' });
files.forEach((f) => {
const title = f.title || f.path.split('/').pop() || '';
const displayTitle = title.match(/\.md$/i) ? title.replace(/\.md$/i, '') : title;
const dir = f.path.includes('/') ? f.path.split('/').slice(0, -1).join('/') + '/' : '';
const item = el('div', { class: 'vault-home-recent-item' });
item.innerHTML = `
<i data-lucide="${getFileIcon ? getFileIcon(f.path) : 'file'}" style="width:14px;height:14px;opacity:0.6;flex-shrink:0"></i>
<span class="vault-home-recent-title">${escapeHtml(displayTitle)}</span>
<span class="vault-home-recent-dir">${escapeHtml(dir)}</span>
<span class="vault-home-recent-time">${escapeHtml(f.mtime_human || '')}</span>`;
item.addEventListener('click', () => TabManager.openPreview(f.vault, f.path));
item.addEventListener('dblclick', (e) => {
e.preventDefault();
TabManager.openPersistent(f.vault, f.path);
});
list.appendChild(item);
});
section.appendChild(list);
// Insert before the results area
if (resultsArea) {
container.insertBefore(section, resultsArea);
} else {
container.appendChild(section);
}
safeCreateIcons();
} catch {
// Silently skip if recent files can't be loaded
}
}
// ---------------------------------------------------------------------------
// Utility
// ---------------------------------------------------------------------------
+71
View File
@@ -4114,6 +4114,77 @@ body.resizing-v {
font-size: 0.85rem;
}
/* ── Recent files section on vault home ── */
.vault-home-recent {
margin-bottom: 20px;
padding: 12px 16px;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 8px;
}
.vault-home-recent-header {
display: flex;
align-items: center;
gap: 6px;
font-size: 0.75rem;
font-weight: 600;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 8px;
padding-bottom: 6px;
border-bottom: 1px solid var(--border);
}
.vault-home-recent-list {
display: flex;
flex-direction: column;
gap: 2px;
}
.vault-home-recent-item {
display: flex;
align-items: center;
gap: 8px;
padding: 5px 6px;
border-radius: 4px;
cursor: pointer;
transition: background 0.12s;
font-size: 0.82rem;
}
.vault-home-recent-item:hover {
background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.vault-home-recent-title {
color: var(--text-primary);
font-weight: 500;
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.vault-home-recent-dir {
color: var(--text-muted);
font-size: 0.72rem;
font-family: var(--font-mono);
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.vault-home-recent-time {
color: var(--text-muted);
font-size: 0.7rem;
white-space: nowrap;
flex-shrink: 0;
}
.vault-home-item {
cursor: pointer;
}