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 = `