Add pinned files and filter hiding

This commit is contained in:
2026-02-17 13:54:53 -05:00
parent ca3fbe4149
commit b737200b21
3 changed files with 263 additions and 47 deletions

View File

@ -17,12 +17,17 @@
<span class="search-label">Search titles</span>
<input id="title-search" class="search-input" type="search" placeholder="Type to filter files">
</label>
<div class="nav-actions">
<button id="shuffle-notes" class="shuffle-btn" type="button">Shuffle notes</button>
<button id="sort-backlinks" class="shuffle-btn" type="button">Sort by backlinks</button>
<button id="sort-created" class="shuffle-btn" type="button">Sort by created date</button>
<button id="sort-modified" class="shuffle-btn" type="button">Sort by modified date</button>
<button id="jump-current" class="shuffle-btn" type="button">Jump to current note</button>
<div class="nav-toolbar-header">
<button id="toggle-nav-toolbar" class="toggle-toolbar-btn" type="button" aria-label="Toggle filter and sort controls" title="Toggle filter and sort controls">&#x25BC; Filters</button>
</div>
<div id="nav-toolbar" class="nav-toolbar">
<div class="nav-actions">
<button id="shuffle-notes" class="shuffle-btn" type="button">Shuffle notes</button>
<button id="sort-backlinks" class="shuffle-btn" type="button">Sort by backlinks</button>
<button id="sort-created" class="shuffle-btn" type="button">Sort by created date</button>
<button id="sort-modified" class="shuffle-btn" type="button">Sort by modified date</button>
<button id="jump-current" class="shuffle-btn" type="button">Jump to current note</button>
</div>
</div>
<div id="file-list">{{NAV_ITEMS}}</div>
</nav>
@ -70,6 +75,43 @@
applyTheme(next);
});
var navToolbar = document.getElementById("nav-toolbar");
var toggleBtn = document.getElementById("toggle-nav-toolbar");
function getCookie(name) {
var match = document.cookie.match(new RegExp("(?:^|;\\s*)" + name + "=([^;]*)"));
return match ? decodeURIComponent(match[1]) : null;
}
function setCookie(name, value, days) {
var expires = "";
if (days) {
var d = new Date();
d.setTime(d.getTime() + days * 86400000);
expires = "; expires=" + d.toUTCString();
}
document.cookie = name + "=" + encodeURIComponent(value) + expires + "; path=/; SameSite=Lax";
}
function applyToolbarState(collapsed) {
if (collapsed) {
navToolbar.classList.add("nav-toolbar-hidden");
toggleBtn.innerHTML = "&#x25B6; Filters";
} else {
navToolbar.classList.remove("nav-toolbar-hidden");
toggleBtn.innerHTML = "&#x25BC; Filters";
}
}
var toolbarCollapsed = getCookie("org_web_nav_toolbar") === "hidden";
applyToolbarState(toolbarCollapsed);
toggleBtn.addEventListener("click", function () {
toolbarCollapsed = !toolbarCollapsed;
setCookie("org_web_nav_toolbar", toolbarCollapsed ? "hidden" : "visible", 365);
applyToolbarState(toolbarCollapsed);
});
const searchInput = document.getElementById("title-search");
const fileList = document.getElementById("file-list");
const shuffleNotesBtn = document.getElementById("shuffle-notes");
@ -79,22 +121,110 @@
const jumpCurrentBtn = document.getElementById("jump-current");
const fileLinks = Array.from(document.querySelectorAll("#file-list .file-link"));
const activeFileLink = document.querySelector("#file-list .file-link.active");
searchInput.addEventListener("input", function () {
const query = searchInput.value.trim().toLowerCase();
for (const link of fileLinks) {
const haystack = link.getAttribute("data-search") || "";
link.style.display = haystack.includes(query) ? "" : "none";
var PIN_STORAGE_KEY = "org_web_viewer_pins";
function loadUserPins() {
try {
var raw = localStorage.getItem(PIN_STORAGE_KEY);
return raw ? new Set(JSON.parse(raw)) : new Set();
} catch (e) {
return new Set();
}
});
}
function saveUserPins(pinSet) {
localStorage.setItem(PIN_STORAGE_KEY, JSON.stringify(Array.from(pinSet)));
}
var userPins = loadUserPins();
function applyUserPins() {
for (var i = 0; i < fileLinks.length; i++) {
var link = fileLinks[i];
var filePath = link.getAttribute("data-file-path") || "";
var serverPinned = link.getAttribute("data-pinned") === "server";
if (serverPinned) {
continue;
}
if (userPins.has(filePath)) {
link.setAttribute("data-pinned", "user");
var titleSpan = link.querySelector(".file-title");
if (titleSpan && titleSpan.textContent.indexOf("\uD83D\uDCCC") !== 0) {
titleSpan.textContent = "\uD83D\uDCCC " + titleSpan.textContent;
}
} else {
link.setAttribute("data-pinned", "");
var titleSpan2 = link.querySelector(".file-title");
if (titleSpan2 && titleSpan2.textContent.indexOf("\uD83D\uDCCC") === 0) {
titleSpan2.textContent = titleSpan2.textContent.replace(/^\uD83D\uDCCC\s*/, "");
}
}
}
}
function pinRank(link) {
var p = link.getAttribute("data-pinned") || "";
if (p === "server") return 0;
if (p === "user") return 1;
return 2;
}
function stableSortWithPins(links, compareFn) {
links.sort(function (a, b) {
var ra = pinRank(a);
var rb = pinRank(b);
if (ra !== rb) return ra - rb;
if (ra < 2) return 0;
return compareFn(a, b);
});
}
function reorderLinks(links) {
const fragment = document.createDocumentFragment();
for (const link of links) {
fragment.appendChild(link);
var fragment = document.createDocumentFragment();
for (var i = 0; i < links.length; i++) {
fragment.appendChild(links[i]);
}
fileList.appendChild(fragment);
}
function reorderWithPins() {
var links = Array.from(fileList.querySelectorAll(".file-link"));
stableSortWithPins(links, function () { return 0; });
reorderLinks(links);
}
applyUserPins();
reorderWithPins();
fileList.addEventListener("click", function (e) {
var btn = e.target.closest(".pin-toggle");
if (!btn) return;
e.preventDefault();
e.stopPropagation();
var link = btn.closest(".file-link");
if (!link) return;
var filePath = link.getAttribute("data-file-path") || "";
if (link.getAttribute("data-pinned") === "server") return;
if (userPins.has(filePath)) {
userPins.delete(filePath);
} else {
userPins.add(filePath);
}
saveUserPins(userPins);
applyUserPins();
reorderWithPins();
});
searchInput.addEventListener("input", function () {
var query = searchInput.value.trim().toLowerCase();
for (var i = 0; i < fileLinks.length; i++) {
var link = fileLinks[i];
var haystack = link.getAttribute("data-search") || "";
link.style.display = haystack.includes(query) ? "" : "none";
}
});
jumpCurrentBtn.addEventListener("click", function () {
if (!activeFileLink) {
return;
@ -103,38 +233,47 @@
});
shuffleNotesBtn.addEventListener("click", function () {
const links = Array.from(fileList.querySelectorAll(".file-link"));
for (let i = links.length - 1; i > 0; i -= 1) {
const j = Math.floor(Math.random() * (i + 1));
const tmp = links[i];
links[i] = links[j];
links[j] = tmp;
var links = Array.from(fileList.querySelectorAll(".file-link"));
var pinned = [];
var rest = [];
for (var i = 0; i < links.length; i++) {
if (pinRank(links[i]) < 2) {
pinned.push(links[i]);
} else {
rest.push(links[i]);
}
}
reorderLinks(links);
for (var j = rest.length - 1; j > 0; j -= 1) {
var k = Math.floor(Math.random() * (j + 1));
var tmp = rest[j];
rest[j] = rest[k];
rest[k] = tmp;
}
reorderLinks(pinned.concat(rest));
});
sortBacklinksBtn.addEventListener("click", function () {
const links = Array.from(fileList.querySelectorAll(".file-link"));
links.sort(function (a, b) {
const aCount = Number.parseInt(a.getAttribute("data-backlinks") || "0", 10);
const bCount = Number.parseInt(b.getAttribute("data-backlinks") || "0", 10);
var links = Array.from(fileList.querySelectorAll(".file-link"));
stableSortWithPins(links, function (a, b) {
var aCount = Number.parseInt(a.getAttribute("data-backlinks") || "0", 10);
var bCount = Number.parseInt(b.getAttribute("data-backlinks") || "0", 10);
if (aCount !== bCount) {
return bCount - aCount;
}
const aKey = a.getAttribute("data-search") || "";
const bKey = b.getAttribute("data-search") || "";
var aKey = a.getAttribute("data-search") || "";
var bKey = b.getAttribute("data-search") || "";
return aKey.localeCompare(bKey);
});
reorderLinks(links);
});
sortCreatedBtn.addEventListener("click", function () {
const links = Array.from(fileList.querySelectorAll(".file-link"));
links.sort(function (a, b) {
const aRaw = a.getAttribute("data-created-ts") || "";
const bRaw = b.getAttribute("data-created-ts") || "";
const aMissing = aRaw === "";
const bMissing = bRaw === "";
var links = Array.from(fileList.querySelectorAll(".file-link"));
stableSortWithPins(links, function (a, b) {
var aRaw = a.getAttribute("data-created-ts") || "";
var bRaw = b.getAttribute("data-created-ts") || "";
var aMissing = aRaw === "";
var bMissing = bRaw === "";
if (aMissing && !bMissing) {
return -1;
}
@ -142,29 +281,29 @@
return 1;
}
if (!aMissing && !bMissing) {
const aTs = Number.parseInt(aRaw, 10);
const bTs = Number.parseInt(bRaw, 10);
var aTs = Number.parseInt(aRaw, 10);
var bTs = Number.parseInt(bRaw, 10);
if (aTs !== bTs) {
return aTs - bTs;
}
}
const aKey = a.getAttribute("data-search") || "";
const bKey = b.getAttribute("data-search") || "";
var aKey = a.getAttribute("data-search") || "";
var bKey = b.getAttribute("data-search") || "";
return aKey.localeCompare(bKey);
});
reorderLinks(links);
});
sortModifiedBtn.addEventListener("click", function () {
const links = Array.from(fileList.querySelectorAll(".file-link"));
links.sort(function (a, b) {
const aTs = Number.parseInt(a.getAttribute("data-modified-ts") || "0", 10);
const bTs = Number.parseInt(b.getAttribute("data-modified-ts") || "0", 10);
var links = Array.from(fileList.querySelectorAll(".file-link"));
stableSortWithPins(links, function (a, b) {
var aTs = Number.parseInt(a.getAttribute("data-modified-ts") || "0", 10);
var bTs = Number.parseInt(b.getAttribute("data-modified-ts") || "0", 10);
if (aTs !== bTs) {
return bTs - aTs;
}
const aKey = a.getAttribute("data-search") || "";
const bKey = b.getAttribute("data-search") || "";
var aKey = a.getAttribute("data-search") || "";
var bKey = b.getAttribute("data-search") || "";
return aKey.localeCompare(bKey);
});
reorderLinks(links);