Replace replace UI with search result navigation
This commit is contained in:
+60
-40
@@ -26,7 +26,6 @@
|
|||||||
let searchWholeWord = false;
|
let searchWholeWord = false;
|
||||||
let searchRegex = false;
|
let searchRegex = false;
|
||||||
let searchFilterVisible = false;
|
let searchFilterVisible = false;
|
||||||
let searchReplaceVisible = false;
|
|
||||||
let _iconDebounceTimer = null;
|
let _iconDebounceTimer = null;
|
||||||
let activeSidebarTab = "vaults";
|
let activeSidebarTab = "vaults";
|
||||||
let filterDebounce = null;
|
let filterDebounce = null;
|
||||||
@@ -4686,13 +4685,10 @@
|
|||||||
const wordBtn = document.getElementById("search-word-btn");
|
const wordBtn = document.getElementById("search-word-btn");
|
||||||
const regexBtn = document.getElementById("search-regex-btn");
|
const regexBtn = document.getElementById("search-regex-btn");
|
||||||
const filterBtn = document.getElementById("search-filter-btn");
|
const filterBtn = document.getElementById("search-filter-btn");
|
||||||
const replaceBtn = document.getElementById("search-replace-btn");
|
|
||||||
const clearBtn = document.getElementById("search-clear-btn");
|
const clearBtn = document.getElementById("search-clear-btn");
|
||||||
const replaceInput = document.getElementById("search-replace-input");
|
|
||||||
const replaceOneBtn = document.getElementById("search-replace-one-btn");
|
|
||||||
const replaceAllBtn = document.getElementById("search-replace-all-btn");
|
|
||||||
const filterRow = document.getElementById("search-filter-row");
|
const filterRow = document.getElementById("search-filter-row");
|
||||||
const replaceRow = document.getElementById("search-replace-row");
|
const prevBtn = document.getElementById("search-prev-btn");
|
||||||
|
const nextBtn = document.getElementById("search-next-btn");
|
||||||
const countEl = document.getElementById("search-match-count");
|
const countEl = document.getElementById("search-match-count");
|
||||||
|
|
||||||
function _updateToggleUI() {
|
function _updateToggleUI() {
|
||||||
@@ -4700,47 +4696,45 @@
|
|||||||
wordBtn.classList.toggle("active", searchWholeWord);
|
wordBtn.classList.toggle("active", searchWholeWord);
|
||||||
regexBtn.classList.toggle("active", searchRegex);
|
regexBtn.classList.toggle("active", searchRegex);
|
||||||
filterBtn.classList.toggle("active", searchFilterVisible);
|
filterBtn.classList.toggle("active", searchFilterVisible);
|
||||||
replaceBtn.classList.toggle("active", searchReplaceVisible);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle buttons
|
// Toggle buttons
|
||||||
caseBtn.addEventListener("click", () => { searchCaseSensitive = !searchCaseSensitive; _updateToggleUI(); _research(); });
|
caseBtn.addEventListener("click", () => { searchCaseSensitive = !searchCaseSensitive; _updateToggleUI(); _research(); });
|
||||||
if (wordBtn) wordBtn.addEventListener("click", () => { searchWholeWord = !searchWholeWord; _updateToggleUI(); _research(); });
|
if (wordBtn) wordBtn.addEventListener("click", () => { searchWholeWord = !searchWholeWord; _updateToggleUI(); _research(); });
|
||||||
if (regexBtn) regexBtn.addEventListener("click", () => { searchRegex = !searchRegex; _updateToggleUI(); _research(); });
|
if (regexBtn) regexBtn.addEventListener("click", () => { searchRegex = !searchRegex; _updateToggleUI(); _research(); });
|
||||||
if (filterBtn) filterBtn.addEventListener("click", () => { searchFilterVisible = !searchFilterVisible; if (filterRow) filterRow.style.display = searchFilterVisible ? "flex" : "none"; searchReplaceVisible = false; if (replaceRow) replaceRow.style.display = "none"; _updateToggleUI(); });
|
if (filterBtn) filterBtn.addEventListener("click", () => { searchFilterVisible = !searchFilterVisible; if (filterRow) filterRow.style.display = searchFilterVisible ? "flex" : "none"; _updateToggleUI(); });
|
||||||
if (replaceBtn) replaceBtn.addEventListener("click", () => { searchReplaceVisible = !searchReplaceVisible; if (replaceRow) replaceRow.style.display = searchReplaceVisible ? "flex" : "none"; searchFilterVisible = false; if (filterRow) filterRow.style.display = "none"; _updateToggleUI(); });
|
|
||||||
|
|
||||||
// Replace buttons
|
// ── Result navigation (up/down arrows + Enter) ──
|
||||||
if (replaceOneBtn) replaceOneBtn.addEventListener("click", () => _doReplace(false));
|
let _searchResultIdx = -1;
|
||||||
if (replaceAllBtn) replaceAllBtn.addEventListener("click", () => _doReplace(true));
|
let _searchResultItems = [];
|
||||||
|
|
||||||
async function _doReplace(replaceAll) {
|
function _updateResultHighlight() {
|
||||||
const q = input.value.trim();
|
_searchResultItems.forEach((el, i) => {
|
||||||
const replacement = replaceInput ? replaceInput.value : "";
|
el.classList.toggle("search-result-active", i === _searchResultIdx);
|
||||||
if (!q) return;
|
});
|
||||||
try {
|
if (_searchResultIdx >= 0 && _searchResultIdx < _searchResultItems.length) {
|
||||||
const vault = document.getElementById("vault-filter").value;
|
_searchResultItems[_searchResultIdx].scrollIntoView({ block: "nearest", behavior: "smooth" });
|
||||||
const includeEl = document.getElementById("search-include-input");
|
}
|
||||||
const excludeEl = document.getElementById("search-exclude-input");
|
const countEl = document.getElementById("search-match-count");
|
||||||
const data = await api("/api/search/replace", {
|
if (countEl) countEl.textContent = _searchResultIdx >= 0 ? `${_searchResultIdx + 1}/${_searchResultItems.length}` : `0/${_searchResultItems.length}`;
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({
|
|
||||||
query: q, replacement, vault,
|
|
||||||
case_sensitive: searchCaseSensitive, whole_word: searchWholeWord, regex: searchRegex,
|
|
||||||
include_paths: includeEl?.value || null, exclude_paths: excludeEl?.value || null,
|
|
||||||
replace_all: replaceAll,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
if (data.dry_run) {
|
|
||||||
const total = data.total_matches || 0;
|
|
||||||
showToast(`${total} occurrence(s) trouvée(s) dans ${data.matches?.length || 0} fichier(s). Cliquez "Tout remplacer" pour appliquer.`, "info");
|
|
||||||
} else {
|
|
||||||
showToast(`${data.total_replacements || 0} remplacement(s) effectué(s).`, "success");
|
|
||||||
_research();
|
|
||||||
}
|
|
||||||
} catch (err) { showToast("Erreur: " + err.message, "error"); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _refreshResultItems() {
|
||||||
|
_searchResultItems = Array.from(document.querySelectorAll(".search-result-item"));
|
||||||
|
_searchResultIdx = _searchResultItems.length > 0 ? 0 : -1;
|
||||||
|
_updateResultHighlight();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.navigateSearchResults = function(delta) {
|
||||||
|
_searchResultItems = Array.from(document.querySelectorAll(".search-result-item"));
|
||||||
|
if (_searchResultItems.length === 0) return;
|
||||||
|
_searchResultIdx = Math.max(0, Math.min(_searchResultItems.length - 1, _searchResultIdx + delta));
|
||||||
|
_updateResultHighlight();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (prevBtn) prevBtn.addEventListener("click", () => navigateSearchResults(-1));
|
||||||
|
if (nextBtn) nextBtn.addEventListener("click", () => navigateSearchResults(1));
|
||||||
|
|
||||||
function _research() {
|
function _research() {
|
||||||
const q = input.value.trim();
|
const q = input.value.trim();
|
||||||
if (q.length >= _getEffective("min_query_length", 2)) {
|
if (q.length >= _getEffective("min_query_length", 2)) {
|
||||||
@@ -4761,7 +4755,6 @@
|
|||||||
else if (e.key === "w" || e.key === "W") { e.preventDefault(); if (wordBtn) wordBtn.click(); }
|
else if (e.key === "w" || e.key === "W") { e.preventDefault(); if (wordBtn) wordBtn.click(); }
|
||||||
else if (e.key === "r" || e.key === "R") { e.preventDefault(); if (regexBtn) regexBtn.click(); }
|
else if (e.key === "r" || e.key === "R") { e.preventDefault(); if (regexBtn) regexBtn.click(); }
|
||||||
else if (e.key === "f" || e.key === "F") { e.preventDefault(); if (filterBtn) filterBtn.click(); input.focus(); }
|
else if (e.key === "f" || e.key === "F") { e.preventDefault(); if (filterBtn) filterBtn.click(); input.focus(); }
|
||||||
else if (e.key === "H" || e.key === "h") { e.preventDefault(); if (replaceBtn) replaceBtn.click(); }
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -4819,6 +4812,16 @@
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
AutocompleteDropdown.navigateUp();
|
AutocompleteDropdown.navigateUp();
|
||||||
} else if (e.key === "Enter") {
|
} else if (e.key === "Enter") {
|
||||||
|
// If search results are visible, open the selected result
|
||||||
|
const results = document.querySelectorAll(".search-result-item");
|
||||||
|
if (results.length > 0 && _searchResultIdx >= 0) {
|
||||||
|
const el = results[_searchResultIdx];
|
||||||
|
if (el) {
|
||||||
|
const vault = el.dataset.vault;
|
||||||
|
const path = el.dataset.path;
|
||||||
|
if (vault && path) { TabManager.openPreview(vault, path); e.preventDefault(); return; }
|
||||||
|
}
|
||||||
|
}
|
||||||
if (AutocompleteDropdown.selectActive()) {
|
if (AutocompleteDropdown.selectActive()) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return;
|
return;
|
||||||
@@ -4835,11 +4838,26 @@
|
|||||||
performAdvancedSearch(q, vault, tagFilter);
|
performAdvancedSearch(q, vault, tagFilter);
|
||||||
}
|
}
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
} else if (e.key === "ArrowDown" && !AutocompleteDropdown.isVisible()) {
|
||||||
|
// Navigate search results when dropdown is closed
|
||||||
|
if (window.navigateSearchResults) { window.navigateSearchResults(1); e.preventDefault(); }
|
||||||
|
} else if (e.key === "ArrowUp" && !AutocompleteDropdown.isVisible()) {
|
||||||
|
if (window.navigateSearchResults) { window.navigateSearchResults(-1); e.preventDefault(); }
|
||||||
} else if (e.key === "Escape") {
|
} else if (e.key === "Escape") {
|
||||||
AutocompleteDropdown.hide();
|
AutocompleteDropdown.hide();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
} else if (e.key === "Enter") {
|
} else if (e.key === "Enter") {
|
||||||
|
// If search results are visible, open selected result
|
||||||
|
const results = document.querySelectorAll(".search-result-item");
|
||||||
|
if (results.length > 0 && _searchResultIdx >= 0) {
|
||||||
|
const el = results[_searchResultIdx];
|
||||||
|
if (el) {
|
||||||
|
const vault = el.dataset.vault;
|
||||||
|
const path = el.dataset.path;
|
||||||
|
if (vault && path) { TabManager.openPreview(vault, path); e.preventDefault(); return; }
|
||||||
|
}
|
||||||
|
}
|
||||||
const q = input.value.trim();
|
const q = input.value.trim();
|
||||||
if (q) {
|
if (q) {
|
||||||
SearchHistory.add(q);
|
SearchHistory.add(q);
|
||||||
@@ -4999,7 +5017,7 @@
|
|||||||
} else {
|
} else {
|
||||||
snippetDiv.textContent = r.snippet || "";
|
snippetDiv.textContent = r.snippet || "";
|
||||||
}
|
}
|
||||||
const item = el("div", { class: "search-result-item" }, [titleDiv, el("div", { class: "search-result-vault" }, [document.createTextNode(r.vault + " / " + r.path)]), snippetDiv]);
|
const item = el("div", { class: "search-result-item", "data-vault": r.vault, "data-path": r.path }, [titleDiv, el("div", { class: "search-result-vault" }, [document.createTextNode(r.vault + " / " + r.path)]), snippetDiv]);
|
||||||
if (r.tags && r.tags.length > 0) {
|
if (r.tags && r.tags.length > 0) {
|
||||||
const tagsDiv = el("div", { class: "search-result-tags" });
|
const tagsDiv = el("div", { class: "search-result-tags" });
|
||||||
r.tags.forEach((tag) => {
|
r.tags.forEach((tag) => {
|
||||||
@@ -5182,7 +5200,7 @@
|
|||||||
|
|
||||||
const vaultPath = el("div", { class: "search-result-vault" }, [document.createTextNode(r.vault + " / " + r.path), scoreEl]);
|
const vaultPath = el("div", { class: "search-result-vault" }, [document.createTextNode(r.vault + " / " + r.path), scoreEl]);
|
||||||
|
|
||||||
const item = el("div", { class: "search-result-item" }, [titleDiv, vaultPath, snippetDiv]);
|
const item = el("div", { class: "search-result-item", "data-vault": r.vault, "data-path": r.path }, [titleDiv, vaultPath, snippetDiv]);
|
||||||
|
|
||||||
if (r.tags && r.tags.length > 0) {
|
if (r.tags && r.tags.length > 0) {
|
||||||
const tagsDiv = el("div", { class: "search-result-tags" });
|
const tagsDiv = el("div", { class: "search-result-tags" });
|
||||||
@@ -5240,6 +5258,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
safeCreateIcons();
|
safeCreateIcons();
|
||||||
|
// Initialize result navigation (select first result)
|
||||||
|
setTimeout(() => { if (window.navigateSearchResults) window.navigateSearchResults(0); }, 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -163,9 +163,6 @@
|
|||||||
<button class="search-btn icon-only" id="search-filter-btn" type="button" title="Filtres de chemin (Alt-F)">
|
<button class="search-btn icon-only" id="search-filter-btn" type="button" title="Filtres de chemin (Alt-F)">
|
||||||
<i data-lucide="filter" style="width:14px;height:14px"></i>
|
<i data-lucide="filter" style="width:14px;height:14px"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="search-btn icon-only" id="search-replace-btn" type="button" title="Rechercher et remplacer (Alt-Shift-H)">
|
|
||||||
<i data-lucide="replace" style="width:14px;height:14px"></i>
|
|
||||||
</button>
|
|
||||||
<span class="search-sep"></span>
|
<span class="search-sep"></span>
|
||||||
<span class="search-count" id="search-match-count">0/0</span>
|
<span class="search-count" id="search-match-count">0/0</span>
|
||||||
<button class="search-btn icon-only" id="search-prev-btn" type="button" title="Résultat précédent">↑</button>
|
<button class="search-btn icon-only" id="search-prev-btn" type="button" title="Résultat précédent">↑</button>
|
||||||
@@ -180,12 +177,6 @@
|
|||||||
<input type="text" class="search-filter-input" id="search-include-input" placeholder="Inclure: **/*.md, notes/**">
|
<input type="text" class="search-filter-input" id="search-include-input" placeholder="Inclure: **/*.md, notes/**">
|
||||||
<input type="text" class="search-filter-input" id="search-exclude-input" placeholder="Exclure: vendor/*, *.lock">
|
<input type="text" class="search-filter-input" id="search-exclude-input" placeholder="Exclure: vendor/*, *.lock">
|
||||||
</div>
|
</div>
|
||||||
<!-- Replace row -->
|
|
||||||
<div class="search-replace-row" id="search-replace-row" style="display:none">
|
|
||||||
<input type="text" class="search-filter-input" id="search-replace-input" placeholder="Remplacer par...">
|
|
||||||
<button class="search-replace-btn" id="search-replace-one-btn" type="button" title="Remplacer">Remplacer</button>
|
|
||||||
<button class="search-replace-btn" id="search-replace-all-btn" type="button" title="Remplacer tout">Tout remplacer</button>
|
|
||||||
</div>
|
|
||||||
<!-- Advanced search autocomplete dropdown -->
|
<!-- Advanced search autocomplete dropdown -->
|
||||||
<div class="search-dropdown" id="search-dropdown" role="listbox" aria-label="Suggestions de recherche" hidden>
|
<div class="search-dropdown" id="search-dropdown" role="listbox" aria-label="Suggestions de recherche" hidden>
|
||||||
<div class="search-dropdown__section search-dropdown__section--history" id="search-dropdown-history">
|
<div class="search-dropdown__section search-dropdown__section--history" id="search-dropdown-history">
|
||||||
|
|||||||
@@ -5811,6 +5811,13 @@ body.popup-mode .content-area {
|
|||||||
.search-replace-btn:hover { background: var(--bg-hover); }
|
.search-replace-btn:hover { background: var(--bg-hover); }
|
||||||
.search-replace-btn.destructive { border-color: var(--text-error); color: var(--text-error); }
|
.search-replace-btn.destructive { border-color: var(--text-error); color: var(--text-error); }
|
||||||
|
|
||||||
|
/* ── Active search result highlight ── */
|
||||||
|
.search-result-item.search-result-active {
|
||||||
|
background: var(--accent-bg, rgba(99, 102, 241, 0.08));
|
||||||
|
border-left: 3px solid var(--accent);
|
||||||
|
padding-left: calc(var(--result-padding, 12px) - 3px);
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Dashboard Tab Navigation ── */
|
/* ── Dashboard Tab Navigation ── */
|
||||||
.dashboard-tabs {
|
.dashboard-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
Reference in New Issue
Block a user