diff --git a/static/css/style.css b/static/css/style.css index e0a777b..9954f36 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1543,14 +1543,70 @@ body { color: var(--accent-dark); } -/* 想法页面分类筛选 */ +/* 想法页面工具栏 */ +.thoughts-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; + margin: 24px 0 20px; + flex-wrap: wrap; +} + .thoughts-filters { display: flex; gap: 8px; - margin: 24px 0 20px; flex-wrap: wrap; } +.thoughts-controls { + display: flex; + gap: 10px; + align-items: center; +} + +.thoughts-search-input { + padding: 7px 14px; + border: 1px solid var(--border); + border-radius: 20px; + background: var(--bg-card); + color: var(--text-primary); + font-size: 14px; + width: 240px; + transition: border-color 0.2s, box-shadow 0.2s; + outline: none; +} + +.thoughts-search-input:focus { + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-bg); +} + +.thoughts-search-input::placeholder { + color: var(--text-light); +} + +.thoughts-sort-select { + padding: 7px 14px; + border: 1px solid var(--border); + border-radius: 20px; + background: var(--bg-card); + color: var(--text-primary); + font-size: 14px; + cursor: pointer; + outline: none; + transition: border-color 0.2s; +} + +.thoughts-sort-select:hover { + border-color: var(--accent); +} + +.thoughts-sort-select:focus { + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-bg); +} + .thought-filter-btn { padding: 6px 18px; border: 1px solid var(--border); @@ -1830,6 +1886,20 @@ body { .global-search-wrapper { max-width: 100%; } + .thoughts-toolbar { + flex-direction: column; + align-items: stretch; + } + .thoughts-controls { + flex-direction: column; + gap: 8px; + } + .thoughts-search-input { + width: 100%; + } + .thoughts-sort-select { + width: 100%; + } .shelf-search { width: 140px; } diff --git a/static/js/app.js b/static/js/app.js index 511301b..c91cc84 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -70,6 +70,9 @@ function escapeHtml(text) { const chartInstances = {}; let chartsLoaded = false; let currentThoughtsData = null; +let currentThoughtsCategory = "all"; +let currentThoughtsSort = "time_desc"; +let currentThoughtsSearch = ""; function refreshChartsForTheme() { if (!chartsLoaded) return; @@ -840,22 +843,37 @@ function renderThoughtsPage(data) { } let html = ` -
- - - - +
+
+ + + + +
+
+ + +
`; content.innerHTML = html; + currentThoughtsCategory = "all"; + currentThoughtsSort = "time_desc"; + currentThoughtsSearch = ""; renderThoughtsList("all"); } @@ -864,16 +882,72 @@ function filterThoughts(category) { btn.classList.remove("active"); }); event.target.closest(".thought-filter-btn").classList.add("active"); + currentThoughtsCategory = category; renderThoughtsList(category); } +function onThoughtsSearch(val) { + currentThoughtsSearch = val.trim().toLowerCase(); + renderThoughtsList(currentThoughtsCategory); +} + +function onThoughtsSort(val) { + currentThoughtsSort = val; + renderThoughtsList(currentThoughtsCategory); +} + +/** 解析日期字符串为时间戳,无效则返回 0 */ +function parseDate(str) { + if (!str) return 0; + const d = new Date(str.replace(/-/g, "/")); + return isNaN(d.getTime()) ? 0 : d.getTime(); +} + +/** 对数组按排序模式排序,返回新数组 */ +function sortByMode(arr) { + const sorted = [...arr]; + if (currentThoughtsSort === "time_desc") { + sorted.sort((a, b) => parseDate(b.create_time) - parseDate(a.create_time)); + } else if (currentThoughtsSort === "time_asc") { + sorted.sort((a, b) => parseDate(a.create_time) - parseDate(b.create_time)); + } else if (currentThoughtsSort === "chapter") { + sorted.sort((a, b) => { + const ua = parseInt(a.chapter_uid) || 0; + const ub = parseInt(b.chapter_uid) || 0; + if (ua !== ub) return ua - ub; + // 同章节内按 range 起始位置排序 + const ra = a.range ? parseInt(a.range.split("-")[0]) : 0; + const rb = b.range ? parseInt(b.range.split("-")[0]) : 0; + return ra - rb; + }); + } + return sorted; +} + +/** 过滤搜索关键词 */ +function matchSearch(text) { + if (!currentThoughtsSearch) return true; + return (text || "").toLowerCase().includes(currentThoughtsSearch); +} + function renderThoughtsList(category) { const data = currentThoughtsData; if (!data) return; - const highlights = data.highlights || []; - const bookReviews = (data.reviews || []).filter((r) => r.is_finish); - const thoughts = (data.reviews || []).filter((r) => !r.is_finish); + // 原始分类 + let highlights = data.highlights || []; + let bookReviews = (data.reviews || []).filter((r) => r.is_finish); + let thoughts = (data.reviews || []).filter((r) => !r.is_finish); + + // 搜索过滤 + highlights = highlights.filter((h) => matchSearch(h.mark_text)); + bookReviews = bookReviews.filter((r) => matchSearch(r.content) || matchSearch(r.abstract)); + thoughts = thoughts.filter((r) => matchSearch(r.content) || matchSearch(r.abstract)); + + // 排序 + highlights = sortByMode(highlights); + bookReviews = sortByMode(bookReviews); + thoughts = sortByMode(thoughts); const list = document.getElementById("thoughtsList"); let html = ""; @@ -882,13 +956,33 @@ function renderThoughtsList(category) { if (category === "all" || category === "highlight") { if (highlights.length > 0) { html += `
📌 划线 ${highlights.length}
`; - const grouped = data.grouped_highlights || {}; - for (const [chTitle, items] of Object.entries(grouped)) { - if (category === "highlight") { + if (currentThoughtsSort === "chapter") { + // 按章节分组展示 + const grouped = {}; + highlights.forEach((h) => { + const ch = h.chapter_title || "未知章节"; + if (!grouped[ch]) grouped[ch] = []; + grouped[ch].push(h); + }); + for (const [chTitle, items] of Object.entries(grouped)) { html += `
`; html += `
${chTitle}
`; + items.forEach((h) => { + html += ` +
+ ${escapeHtml(h.mark_text)} +
+ ${h.chapter_title ? `${h.chapter_title}` : ""} + ${h.create_time ? `${h.create_time}` : ""} +
+
+ `; + }); + html += `
`; } - items.forEach((h) => { + } else { + // 按时间排序,平铺展示(标注章节) + highlights.forEach((h) => { html += `
${escapeHtml(h.mark_text)} @@ -899,7 +993,6 @@ function renderThoughtsList(category) {
`; }); - if (category === "highlight") html += `
`; } } } @@ -946,7 +1039,11 @@ function renderThoughtsList(category) { } if (!html) { - html = `
该分类暂无内容
`; + if (currentThoughtsSearch) { + html = `
未找到包含"${escapeHtml(currentThoughtsSearch)}"的内容
`; + } else { + html = `
该分类暂无内容
`; + } } list.innerHTML = html;