diff --git a/frontend/app.js b/frontend/app.js
index 54e99f1..f989588 100644
--- a/frontend/app.js
+++ b/frontend/app.js
@@ -427,7 +427,8 @@
async function refreshTagsForContext() {
const vaultParam = selectedContextVault === "all" ? "" : `?vault=${encodeURIComponent(selectedContextVault)}`;
const data = await api(`/api/tags${vaultParam}`);
- renderTagCloud(data.tags);
+ const filteredTags = TagFilterService.filterTags(data.tags);
+ renderTagCloud(filteredTags);
}
// ---------------------------------------------------------------------------
@@ -673,12 +674,73 @@
});
}
+ // ---------------------------------------------------------------------------
+ // Tag Filter Service
+ // ---------------------------------------------------------------------------
+ const TagFilterService = {
+ defaultFilters: [
+ { pattern: "#<% ... %>", regex: "^#<%.*%>$", enabled: true },
+ { pattern: "#{{ ... }}", regex: "^#\\{\\{.*\\}\\}$", enabled: true }
+ ],
+
+ getConfig() {
+ const stored = localStorage.getItem("obsigate-tag-filters");
+ if (stored) {
+ try {
+ return JSON.parse(stored);
+ } catch (e) {
+ return { tagFilters: this.defaultFilters };
+ }
+ }
+ return { tagFilters: this.defaultFilters };
+ },
+
+ saveConfig(config) {
+ localStorage.setItem("obsigate-tag-filters", JSON.stringify(config));
+ },
+
+ patternToRegex(pattern) {
+ let regex = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
+ regex = regex.replace(/\\\.\\\.\\\./g, '.*');
+ return regex;
+ },
+
+ isTagFiltered(tag) {
+ const config = this.getConfig();
+ const filters = config.tagFilters || this.defaultFilters;
+
+ for (const filter of filters) {
+ if (!filter.enabled) continue;
+ try {
+ const regex = new RegExp(`^${filter.regex}$`);
+ if (regex.test(`#${tag}`)) {
+ return true;
+ }
+ } catch (e) {
+ console.warn("Invalid regex:", filter.regex, e);
+ }
+ }
+ return false;
+ },
+
+ filterTags(tags) {
+ const filtered = {};
+ Object.entries(tags).forEach(([tag, count]) => {
+ if (!this.isTagFiltered(tag)) {
+ filtered[tag] = count;
+ }
+ });
+ return filtered;
+ }
+ };
+
// ---------------------------------------------------------------------------
// Tags
// ---------------------------------------------------------------------------
async function loadTags() {
const data = await api("/api/tags");
- renderTagCloud(data.tags);
+ const filteredTags = TagFilterService.filterTags(data.tags);
+ renderTagCloud(filteredTags);
}
function renderTagCloud(tags) {
@@ -828,9 +890,11 @@
// Tags
const tagsDiv = el("div", { class: "file-tags" });
(data.tags || []).forEach((tag) => {
- const t = el("span", { class: "file-tag" }, [document.createTextNode(`#${tag}`)]);
- t.addEventListener("click", () => searchByTag(tag));
- tagsDiv.appendChild(t);
+ if (!TagFilterService.isTagFiltered(tag)) {
+ const t = el("span", { class: "file-tag" }, [document.createTextNode(`#${tag}`)]);
+ t.addEventListener("click", () => searchByTag(tag));
+ tagsDiv.appendChild(t);
+ }
});
// Action buttons
@@ -1026,6 +1090,147 @@
if (modal) modal.classList.remove("active");
}
+ function initConfigModal() {
+ const openBtn = document.getElementById("config-open-btn");
+ const closeBtn = document.getElementById("config-close");
+ const modal = document.getElementById("config-modal");
+ const addBtn = document.getElementById("config-add-btn");
+ const patternInput = document.getElementById("config-pattern-input");
+
+ if (!openBtn || !closeBtn || !modal) return;
+
+ openBtn.addEventListener("click", () => {
+ modal.classList.add("active");
+ closeHeaderMenu();
+ renderConfigFilters();
+ safeCreateIcons();
+ });
+
+ closeBtn.addEventListener("click", closeConfigModal);
+ modal.addEventListener("click", (e) => {
+ if (e.target === modal) {
+ closeConfigModal();
+ }
+ });
+
+ addBtn.addEventListener("click", addConfigFilter);
+ patternInput.addEventListener("keypress", (e) => {
+ if (e.key === "Enter") {
+ addConfigFilter();
+ }
+ });
+
+ patternInput.addEventListener("input", updateRegexPreview);
+
+ document.addEventListener("keydown", (e) => {
+ if (e.key === "Escape" && modal.classList.contains("active")) {
+ closeConfigModal();
+ }
+ });
+ }
+
+ function closeConfigModal() {
+ const modal = document.getElementById("config-modal");
+ if (modal) modal.classList.remove("active");
+ }
+
+ function renderConfigFilters() {
+ const config = TagFilterService.getConfig();
+ const filters = config.tagFilters || TagFilterService.defaultFilters;
+ const container = document.getElementById("config-filters-list");
+
+ container.innerHTML = "";
+
+ filters.forEach((filter, index) => {
+ const badge = el("div", { class: `config-filter-badge ${!filter.enabled ? "disabled" : ""}` }, [
+ el("span", {}, [document.createTextNode(filter.pattern)]),
+ el("button", {
+ class: "config-filter-toggle",
+ title: filter.enabled ? "Désactiver" : "Activer",
+ type: "button"
+ }, [document.createTextNode(filter.enabled ? "✓" : "○")]),
+ el("button", {
+ class: "config-filter-remove",
+ title: "Supprimer",
+ type: "button"
+ }, [document.createTextNode("×")]),
+ ]);
+
+ const toggleBtn = badge.querySelector(".config-filter-toggle");
+ const removeBtn = badge.querySelector(".config-filter-remove");
+
+ toggleBtn.addEventListener("click", (e) => {
+ e.stopPropagation();
+ toggleConfigFilter(index);
+ });
+
+ removeBtn.addEventListener("click", (e) => {
+ e.stopPropagation();
+ removeConfigFilter(index);
+ });
+
+ container.appendChild(badge);
+ });
+ }
+
+ function toggleConfigFilter(index) {
+ const config = TagFilterService.getConfig();
+ const filters = config.tagFilters || TagFilterService.defaultFilters;
+ if (filters[index]) {
+ filters[index].enabled = !filters[index].enabled;
+ config.tagFilters = filters;
+ TagFilterService.saveConfig(config);
+ renderConfigFilters();
+ refreshTagsForContext().catch(err => console.error("Error refreshing tags:", err));
+ }
+ }
+
+ function removeConfigFilter(index) {
+ const config = TagFilterService.getConfig();
+ let filters = config.tagFilters || TagFilterService.defaultFilters;
+ filters = filters.filter((_, i) => i !== index);
+ config.tagFilters = filters;
+ TagFilterService.saveConfig(config);
+ renderConfigFilters();
+ refreshTagsForContext().catch(err => console.error("Error refreshing tags:", err));
+ }
+
+ function addConfigFilter() {
+ const input = document.getElementById("config-pattern-input");
+ const pattern = input.value.trim();
+
+ if (!pattern) return;
+
+ const regex = TagFilterService.patternToRegex(pattern);
+ const config = TagFilterService.getConfig();
+ const filters = config.tagFilters || TagFilterService.defaultFilters;
+
+ const newFilter = { pattern, regex, enabled: true };
+ filters.push(newFilter);
+ config.tagFilters = filters;
+ TagFilterService.saveConfig(config);
+
+ input.value = "";
+ renderConfigFilters();
+ refreshTagsForContext().catch(err => console.error("Error refreshing tags:", err));
+ updateRegexPreview();
+ }
+
+ function updateRegexPreview() {
+ const input = document.getElementById("config-pattern-input");
+ const preview = document.getElementById("config-regex-preview");
+ const code = document.getElementById("config-regex-code");
+ const pattern = input.value.trim();
+
+ if (pattern) {
+ const regex = TagFilterService.patternToRegex(pattern);
+ code.textContent = `^${regex}$`;
+ preview.style.display = "block";
+ } else {
+ preview.style.display = "none";
+ }
+ }
+
// ---------------------------------------------------------------------------
// Search
// ---------------------------------------------------------------------------
@@ -1081,14 +1286,18 @@
if (r.tags && r.tags.length > 0) {
const tagsDiv = el("div", { class: "search-result-tags" });
r.tags.forEach((tag) => {
- const tagEl = el("span", { class: "file-tag" }, [document.createTextNode(`#${tag}`)]);
- tagEl.addEventListener("click", (e) => {
- e.stopPropagation();
- addTagFilter(tag);
- });
- tagsDiv.appendChild(tagEl);
+ if (!TagFilterService.isTagFiltered(tag)) {
+ const tagEl = el("span", { class: "file-tag" }, [document.createTextNode(`#${tag}`)]);
+ tagEl.addEventListener("click", (e) => {
+ e.stopPropagation();
+ addTagFilter(tag);
+ });
+ tagsDiv.appendChild(tagEl);
+ }
});
- item.appendChild(tagsDiv);
+ if (tagsDiv.children.length > 0) {
+ item.appendChild(tagsDiv);
+ }
}
item.addEventListener("click", () => openFile(r.vault, r.path));
@@ -1473,6 +1682,7 @@
initVaultContext();
initCollapsiblePanels();
initHelpModal();
+ initConfigModal();
initSidebarFilter();
initSidebarResize();
initTagResize();
diff --git a/frontend/index.html b/frontend/index.html
index 8b11d8d..335bb8f 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -116,6 +116,15 @@
+