From fe87cf521af8fc2a16467d03152cebba35a5ca6d Mon Sep 17 00:00:00 2001 From: Jeffrey Chen <78434827+TCOTC@users.noreply.github.com> Date: Wed, 5 Nov 2025 15:07:41 +0800 Subject: [PATCH] :recycle: Refactor recent documents handling --- app/src/business/openRecentDocs.ts | 111 +++++------- app/src/layout/Wnd.ts | 16 +- app/src/layout/tabUtil.ts | 55 ++++-- kernel/api/router.go | 1 + kernel/api/storage.go | 27 ++- kernel/model/storage.go | 260 +++++++++++++++++++++++------ 6 files changed, 330 insertions(+), 140 deletions(-) diff --git a/app/src/business/openRecentDocs.ts b/app/src/business/openRecentDocs.ts index 5e1f421af..f1e7b3805 100644 --- a/app/src/business/openRecentDocs.ts +++ b/app/src/business/openRecentDocs.ts @@ -10,44 +10,39 @@ import {focusByRange} from "../protyle/util/selection"; import {hasClosestByClassName} from "../protyle/util/hasClosest"; import {hideElements} from "../protyle/ui/hideElements"; -const getHTML = async (data: { +const renderRecentDocsContent = async (data: { rootID: string, icon: string, title: string, viewedAt?: number, closedAt?: number, openAt?: number, - updated?: number -}[], element: Element, key?: string, sortBy: TRecentDocsSort = "viewedAt") => { +}[], element: Element, key?: string) => { let tabHtml = ""; let index = 0; - // 根据排序字段对数据进行排序 - const sortedData = [...data].sort((a, b) => { - const aValue = a[sortBy] || 0; - const bValue = b[sortBy] || 0; - return bValue - aValue; // 降序排序 - }); - - sortedData.forEach((item) => { - if (!key || item.title.toLowerCase().includes(key.toLowerCase())) { - tabHtml += `
  • -${unicode2Emoji(item.icon || window.siyuan.storage[Constants.LOCAL_IMAGES].file, "b3-list-item__graphic", true)} -${escapeHtml(item.title)} + if (key) { + data = data.filter((item) => { + return item.title.toLowerCase().includes(key.toLowerCase()); + }); + } + data.forEach((item) => { + tabHtml += `
  • + ${unicode2Emoji(item.icon || window.siyuan.storage[Constants.LOCAL_IMAGES].file, "b3-list-item__graphic", true)} + ${escapeHtml(item.title)}
  • `; - index++; - } + index++; }); let switchPath = ""; if (tabHtml) { const pathResponse = await fetchSyncPost("/api/filetree/getFullHPathByID", { - id: data[0].rootID + id: data[0].rootID // 过滤后的第一个文档 ID }); switchPath = escapeHtml(pathResponse.data); } let dockHtml = ""; if (!isWindow()) { - dockHtml = '"; + dockHtml = '"; } const pathElement = element.querySelector(".switch-doc__path"); pathElement.innerHTML = switchPath; - pathElement.previousElementSibling.innerHTML = `
    - ${dockHtml} - -
    `; + pathElement.previousElementSibling.innerHTML = `
    + ${dockHtml} + +
    `; }; export const openRecentDocs = () => { @@ -93,7 +88,8 @@ export const openRecentDocs = () => { hideElements(["dialog"]); return; } - fetchPost("/api/storage/getRecentDocs", {sortBy: "viewedAt"}, (response) => { + const sortBy = window.siyuan.storage[Constants.LOCAL_RECENT_DOCS].type; + fetchPost("/api/storage/getRecentDocs", {sortBy}, (response) => { let range: Range; if (getSelection().rangeCount > 0) { range = getSelection().getRangeAt(0); @@ -110,15 +106,15 @@ export const openRecentDocs = () => {
    `, content: `
    -
    +
    `, height: "80vh", @@ -128,16 +124,18 @@ export const openRecentDocs = () => { } } }); + const sortSelect = dialog.element.querySelector("#recentDocsSort") as HTMLSelectElement; + sortSelect.value = sortBy; const searchElement = dialog.element.querySelector("input"); searchElement.focus(); searchElement.addEventListener("compositionend", () => { - getHTML(response.data, dialog.element, searchElement.value, sortSelect.value as TRecentDocsSort); + renderRecentDocsContent(response.data, dialog.element, searchElement.value); }); searchElement.addEventListener("input", (event: InputEvent) => { if (event.isComposing) { return; } - getHTML(response.data, dialog.element, searchElement.value, sortSelect.value as TRecentDocsSort); + renderRecentDocsContent(response.data, dialog.element, searchElement.value); }); dialog.element.setAttribute("data-key", Constants.DIALOG_RECENTDOCS); dialog.element.addEventListener("click", (event) => { @@ -152,45 +150,16 @@ export const openRecentDocs = () => { }); // 添加排序下拉框事件监听 - const sortSelect = dialog.element.querySelector("#recentDocsSort") as HTMLSelectElement; sortSelect.addEventListener("change", () => { - // 重新调用API获取排序后的数据 - if (sortSelect.value === "updated") { - // 使用SQL查询获取最近修改的文档 - const data = { - stmt: "SELECT * FROM blocks WHERE type = 'd' ORDER BY updated DESC LIMIT 33" - }; - fetchSyncPost("/api/query/sql", data).then((sqlResponse) => { - if (sqlResponse.data && sqlResponse.data.length > 0) { - // 转换SQL查询结果格式 - const recentModifiedDocs = sqlResponse.data.map((block: any) => { - // 从ial中解析icon - let icon = ""; - if (block.ial) { - const iconMatch = block.ial.match(/icon="([^"]*)"/); - if (iconMatch) { - icon = iconMatch[1]; - } - } - return { - rootID: block.id, - icon, - title: block.content, - updated: block.updated - }; - }); - getHTML(recentModifiedDocs, dialog.element, searchElement.value, "updated"); - } - }); - } else { - fetchPost("/api/storage/getRecentDocs", {sortBy: sortSelect.value}, (newResponse) => { - getHTML(newResponse.data, dialog.element, searchElement.value, sortSelect.value as TRecentDocsSort); - }); - } + // 重新调用 API 获取排序后的数据 + fetchPost("/api/storage/getRecentDocs", {sortBy: sortSelect.value}, (newResponse) => { + response = newResponse; + renderRecentDocsContent(newResponse.data, dialog.element, searchElement.value); + }); window.siyuan.storage[Constants.LOCAL_RECENT_DOCS].type = sortSelect.value; setStorageVal(Constants.LOCAL_RECENT_DOCS, window.siyuan.storage[Constants.LOCAL_RECENT_DOCS]); }); - getHTML(response.data, dialog.element); + renderRecentDocsContent(response.data, dialog.element); }); }; diff --git a/app/src/layout/Wnd.ts b/app/src/layout/Wnd.ts index 5e8e636c6..878fb5d38 100644 --- a/app/src/layout/Wnd.ts +++ b/app/src/layout/Wnd.ts @@ -778,7 +778,7 @@ export class Wnd { model.send("closews", {}); } - private removeTabAction = (id: string, closeAll = false, animate = true, isSaveLayout = true) => { + private removeTabAction = (id: string, isBatchClose = false, animate = true, isSaveLayout = true) => { clearCounter(); this.children.find((item, index) => { if (item.id === id) { @@ -794,8 +794,10 @@ export class Wnd { } if (item.model instanceof Editor) { saveScroll(item.model.editor.protyle); - // 更新文档关闭时间 - fetchPost("/api/storage/updateRecentDocCloseTime", {rootID: item.model.editor.protyle.block.rootID}); + // 更新文档关闭时间(批量关闭页签时由 closeTabByType 批量处理,这里不单独调用) + if (!isBatchClose) { + fetchPost("/api/storage/updateRecentDocCloseTime", {rootID: item.model.editor.protyle.block.rootID}); + } } if (this.children.length === 1) { this.destroyModel(this.children[0].model); @@ -841,7 +843,7 @@ export class Wnd { } } }); - if (latestHeadElement && !closeAll) { + if (latestHeadElement && !isBatchClose) { this.switchTab(latestHeadElement, true, true, false, false); this.showHeading(); } @@ -889,7 +891,7 @@ export class Wnd { /// #endif }; - public removeTab(id: string, closeAll = false, animate = true, isSaveLayout = true) { + public removeTab(id: string, isBatchClose = false, animate = true, isSaveLayout = true) { for (let index = 0; index < this.children.length; index++) { const item = this.children[index]; if (item.id === id) { @@ -898,9 +900,9 @@ export class Wnd { showMessage(window.siyuan.languages.uploading); return; } - this.removeTabAction(id, closeAll, animate, isSaveLayout); + this.removeTabAction(id, isBatchClose, animate, isSaveLayout); } else { - this.removeTabAction(id, closeAll, animate, isSaveLayout); + this.removeTabAction(id, isBatchClose, animate, isSaveLayout); } return; } diff --git a/app/src/layout/tabUtil.ts b/app/src/layout/tabUtil.ts index eb4d229bd..c45d68e19 100644 --- a/app/src/layout/tabUtil.ts +++ b/app/src/layout/tabUtil.ts @@ -23,6 +23,7 @@ import {openHistory} from "../history/history"; import {newFile} from "../util/newFile"; import {mountHelp, newNotebook} from "../util/mount"; import {Constants} from "../constants"; +import {fetchPost} from "../util/fetch"; export const getActiveTab = (wndActive = true) => { const activeTabElement = document.querySelector(".layout__wnd--active .item--focus"); @@ -360,28 +361,58 @@ export const copyTab = (app: App, tab: Tab) => { }; export const closeTabByType = async (tab: Tab, type: "closeOthers" | "closeAll" | "other", tabs?: Tab[]) => { + const tabsToClose: Tab[] = []; if (type === "closeOthers") { - for (let index = 0; index < tab.parent.children.length; index++) { - if (tab.parent.children[index].id !== tab.id && !tab.parent.children[index].headElement.classList.contains("item--pin")) { - await tab.parent.children[index].parent.removeTab(tab.parent.children[index].id, true, false); - index--; + for (const item of tab.parent.children) { + if (item.id !== tab.id && !item.headElement.classList.contains("item--pin")) { + tabsToClose.push(item); } } } else if (type === "closeAll") { - for (let index = 0; index < tab.parent.children.length; index++) { - if (!tab.parent.children[index].headElement.classList.contains("item--pin")) { - await tab.parent.children[index].parent.removeTab(tab.parent.children[index].id, true); - index--; + for (const item of tab.parent.children) { + if (!item.headElement.classList.contains("item--pin")) { + tabsToClose.push(item); } } - } else if (tabs.length > 0) { - for (let index = 0; index < tabs.length; index++) { - if (!tabs[index].headElement.classList.contains("item--pin")) { - await tabs[index].parent.removeTab(tabs[index].id); + } else if (tabs && tabs.length > 0) { + for (const item of tabs) { + if (!item.headElement.classList.contains("item--pin")) { + tabsToClose.push(item); } } } + // 收集所有需要关闭的文档 rootID 并批量关闭页签 + const rootIDs: string[] = []; + for (const item of tabsToClose) { + let rootID; + if (item.model instanceof Editor) { + rootID = item.model.editor.protyle.block.rootID; + } else if (!item.model) { + const initTab = item.headElement.getAttribute("data-initdata"); + if (initTab) { + const initTabData = JSON.parse(initTab); + if (initTabData && initTabData.instance === "Editor" && initTabData.rootId) { + rootID = initTabData.rootId; + } + } + } + if (rootID) { + rootIDs.push(rootID); + } + + if (type === "closeOthers") { + item.parent.removeTab(item.id, true, false); + } else { + item.parent.removeTab(item.id, true); + } + } + + // 批量更新文档关闭时间 + if (rootIDs.length > 0) { + fetchPost("/api/storage/batchUpdateRecentDocCloseTime", {rootIDs}); + } + if (tab.headElement.parentElement && !tab.headElement.parentElement.querySelector(".item--focus")) { tab.parent.switchTab(tab.headElement, true); } else if (tab.parent.children.length > 0) { diff --git a/kernel/api/router.go b/kernel/api/router.go index 7b5f603d9..f41ba1170 100644 --- a/kernel/api/router.go +++ b/kernel/api/router.go @@ -80,6 +80,7 @@ func ServeAPI(ginServer *gin.Engine) { ginServer.Handle("POST", "/api/storage/getRecentDocs", model.CheckAuth, getRecentDocs) ginServer.Handle("POST", "/api/storage/updateRecentDocViewTime", model.CheckAuth, updateRecentDocViewTime) ginServer.Handle("POST", "/api/storage/updateRecentDocCloseTime", model.CheckAuth, updateRecentDocCloseTime) + ginServer.Handle("POST", "/api/storage/batchUpdateRecentDocCloseTime", model.CheckAuth, batchUpdateRecentDocCloseTime) ginServer.Handle("POST", "/api/storage/updateRecentDocOpenTime", model.CheckAuth, updateRecentDocOpenTime) ginServer.Handle("POST", "/api/storage/getOutlineStorage", model.CheckAuth, getOutlineStorage) diff --git a/kernel/api/storage.go b/kernel/api/storage.go index fa223bc61..5f68e63d4 100644 --- a/kernel/api/storage.go +++ b/kernel/api/storage.go @@ -293,11 +293,11 @@ func updateRecentDocCloseTime(c *gin.Context) { return } - if nil == arg["rootID"] { + rootID, ok := arg["rootID"].(string) + if !ok || rootID == "" { return } - rootID := arg["rootID"].(string) err := model.UpdateRecentDocCloseTime(rootID) if err != nil { ret.Code = -1 @@ -305,3 +305,26 @@ func updateRecentDocCloseTime(c *gin.Context) { return } } + +func batchUpdateRecentDocCloseTime(c *gin.Context) { + ret := gulu.Ret.NewResult() + defer c.JSON(http.StatusOK, ret) + + arg, ok := util.JsonArg(c, ret) + if !ok { + return + } + + rootIDsArg := arg["rootIDs"].([]interface{}) + var rootIDs []string + for _, id := range rootIDsArg { + rootIDs = append(rootIDs, id.(string)) + } + + err := model.BatchUpdateRecentDocCloseTime(rootIDs) + if err != nil { + ret.Code = -1 + ret.Msg = err.Error() + return + } +} diff --git a/kernel/model/storage.go b/kernel/model/storage.go index 25e3cffdf..9a1eea4e2 100644 --- a/kernel/model/storage.go +++ b/kernel/model/storage.go @@ -22,6 +22,7 @@ import ( "path" "path/filepath" "sort" + "strings" "sync" "time" @@ -29,14 +30,15 @@ import ( "github.com/88250/lute/parse" "github.com/siyuan-note/filelock" "github.com/siyuan-note/logging" + "github.com/siyuan-note/siyuan/kernel/sql" "github.com/siyuan-note/siyuan/kernel/treenode" "github.com/siyuan-note/siyuan/kernel/util" ) type RecentDoc struct { RootID string `json:"rootID"` - Icon string `json:"icon"` - Title string `json:"title"` + Icon string `json:"icon,omitempty"` + Title string `json:"title,omitempty"` ViewedAt int64 `json:"viewedAt"` // 浏览时间字段 ClosedAt int64 `json:"closedAt"` // 关闭时间字段 OpenAt int64 `json:"openAt"` // 文档第一次从文档树加载到页签的时间 @@ -49,6 +51,73 @@ type OutlineDoc struct { var recentDocLock = sync.Mutex{} +// 三种类型各保留 32 条记录 +func trimRecentDocs(recentDocs []*RecentDoc) []*RecentDoc { + if len(recentDocs) <= 32 { + return recentDocs + } + + // 分别统计三种类型的记录 + var viewedDocs []*RecentDoc + var openedDocs []*RecentDoc + var closedDocs []*RecentDoc + + for _, doc := range recentDocs { + if doc.ViewedAt > 0 { + viewedDocs = append(viewedDocs, doc) + } + if doc.OpenAt > 0 { + openedDocs = append(openedDocs, doc) + } + if doc.ClosedAt > 0 { + closedDocs = append(closedDocs, doc) + } + } + + // 分别按时间排序并截取 32 条记录 + if len(viewedDocs) > 32 { + sort.Slice(viewedDocs, func(i, j int) bool { + return viewedDocs[i].ViewedAt > viewedDocs[j].ViewedAt + }) + viewedDocs = viewedDocs[:32] + } + if len(openedDocs) > 32 { + sort.Slice(openedDocs, func(i, j int) bool { + return openedDocs[i].OpenAt > openedDocs[j].OpenAt + }) + openedDocs = openedDocs[:32] + } + if len(closedDocs) > 32 { + sort.Slice(closedDocs, func(i, j int) bool { + return closedDocs[i].ClosedAt > closedDocs[j].ClosedAt + }) + closedDocs = closedDocs[:32] + } + + // 合并三类记录 + docMap := make(map[string]*RecentDoc, 64) + for _, doc := range viewedDocs { + docMap[doc.RootID] = doc + } + for _, doc := range openedDocs { + if _, ok := docMap[doc.RootID]; !ok { + docMap[doc.RootID] = doc + } + } + for _, doc := range closedDocs { + if _, ok := docMap[doc.RootID]; !ok { + docMap[doc.RootID] = doc + } + } + + result := make([]*RecentDoc, 0, len(docMap)) + for _, doc := range docMap { + result = append(result, doc) + } + + return result +} + func RemoveRecentDoc(ids []string) { recentDocLock.Lock() defer recentDocLock.Unlock() @@ -70,14 +139,13 @@ func RemoveRecentDoc(ids []string) { if err != nil { return } - return } func setRecentDocByTree(tree *parse.Tree) { recentDoc := &RecentDoc{ RootID: tree.Root.ID, - Icon: tree.Root.IALAttr("icon"), - Title: tree.Root.IALAttr("title"), + Icon: "", + Title: "", ViewedAt: time.Now().Unix(), // 使用当前时间作为浏览时间 ClosedAt: 0, // 初始化关闭时间为0,表示未关闭 OpenAt: time.Now().Unix(), // 设置文档打开时间 @@ -93,18 +161,20 @@ func setRecentDocByTree(tree *parse.Tree) { for i, c := range recentDocs { if c.RootID == recentDoc.RootID { + recentDoc.ClosedAt = c.ClosedAt recentDocs = append(recentDocs[:i], recentDocs[i+1:]...) break } } recentDocs = append([]*RecentDoc{recentDoc}, recentDocs...) - if 32 < len(recentDocs) { - recentDocs = recentDocs[:32] - } + + recentDocs = trimRecentDocs(recentDocs) err = setRecentDocs(recentDocs) - return + if err != nil { + return + } } // UpdateRecentDocOpenTime 更新文档打开时间(只在第一次从文档树加载到页签时调用) @@ -122,6 +192,7 @@ func UpdateRecentDocOpenTime(rootID string) (err error) { for _, doc := range recentDocs { if doc.RootID == rootID { doc.OpenAt = time.Now().Unix() + doc.ClosedAt = 0 found = true break } @@ -148,6 +219,7 @@ func UpdateRecentDocViewTime(rootID string) (err error) { for _, doc := range recentDocs { if doc.RootID == rootID { doc.ViewedAt = time.Now().Unix() + doc.ClosedAt = 0 found = true break } @@ -165,6 +237,15 @@ func UpdateRecentDocViewTime(rootID string) (err error) { // UpdateRecentDocCloseTime 更新文档关闭时间 func UpdateRecentDocCloseTime(rootID string) (err error) { + return BatchUpdateRecentDocCloseTime([]string{rootID}) +} + +// BatchUpdateRecentDocCloseTime 批量更新文档关闭时间 +func BatchUpdateRecentDocCloseTime(rootIDs []string) (err error) { + if len(rootIDs) == 0 { + return + } + recentDocLock.Lock() defer recentDocLock.Unlock() @@ -173,17 +254,45 @@ func UpdateRecentDocCloseTime(rootID string) (err error) { return } - // 查找文档并更新关闭时间 - found := false + rootIDs = gulu.Str.RemoveDuplicatedElem(rootIDs) + rootIDsMap := make(map[string]bool, len(rootIDs)) + for _, id := range rootIDs { + rootIDsMap[id] = true + } + + closeTime := time.Now().Unix() + + // 更新已存在的文档 + updated := false for _, doc := range recentDocs { - if doc.RootID == rootID { - doc.ClosedAt = time.Now().Unix() - found = true - break + if rootIDsMap[doc.RootID] { + doc.ClosedAt = closeTime + updated = true + delete(rootIDsMap, doc.RootID) // 标记已处理 } } - if found { + // 为不存在的文档创建新记录 + for rootID := range rootIDsMap { + tree, loadErr := LoadTreeByBlockID(rootID) + if loadErr != nil { + continue + } + + recentDoc := &RecentDoc{ + RootID: tree.Root.ID, + Icon: tree.Root.IALAttr("icon"), + Title: tree.Root.IALAttr("title"), + ViewedAt: 0, // 未浏览过,设为 0 + ClosedAt: closeTime, // 设置关闭时间 + OpenAt: 0, // 未记录打开时间,设为 0 + } + + recentDocs = append([]*RecentDoc{recentDoc}, recentDocs...) + updated = true + } + + if updated { err = setRecentDocs(recentDocs) } return @@ -202,6 +311,12 @@ func setRecentDocs(recentDocs []*RecentDoc) (err error) { return } + // 不保存 Title 和 Icon + for _, doc := range recentDocs { + doc.Title = "" + doc.Icon = "" + } + data, err := gulu.JSON.MarshalIndentJSON(recentDocs, "", " ") if err != nil { logging.LogErrorf("marshal storage [recent-doc] failed: %s", err) @@ -218,7 +333,7 @@ func setRecentDocs(recentDocs []*RecentDoc) (err error) { } func getRecentDocs(sortBy string) (ret []*RecentDoc, err error) { - tmp := []*RecentDoc{} + var tmp []*RecentDoc dataPath := filepath.Join(util.DataDir, "storage/recent-doc.json") if !filelock.IsExist(dataPath) { return @@ -247,7 +362,12 @@ func getRecentDocs(sortBy string) (ret []*RecentDoc, err error) { var notExists []string for _, doc := range tmp { if bt := bts[doc.RootID]; nil != bt { + // 获取最新的文档标题和图标 doc.Title = path.Base(bt.HPath) // Recent docs not updated after renaming https://github.com/siyuan-note/siyuan/issues/7827 + ial := sql.GetBlockAttrs(doc.RootID) + if "" != ial["icon"] { + doc.Icon = ial["icon"] + } ret = append(ret, doc) } else { notExists = append(notExists, doc.RootID) @@ -255,51 +375,95 @@ func getRecentDocs(sortBy string) (ret []*RecentDoc, err error) { } if 0 < len(notExists) { - setRecentDocs(ret) + err := setRecentDocs(ret) + if err != nil { + return nil, err + } } // 根据排序参数进行排序 switch sortBy { + case "updated": // 按更新时间排序 + // 从数据库查询最近修改的文档 + sqlBlocks := sql.SelectBlocksRawStmt("SELECT * FROM blocks WHERE type = 'd' ORDER BY updated DESC", 1, 32) + ret = []*RecentDoc{} + if 1 > len(sqlBlocks) { + return + } + + // 获取文档树信息 + var rootIDs []string + for _, sqlBlock := range sqlBlocks { + rootIDs = append(rootIDs, sqlBlock.ID) + } + bts := treenode.GetBlockTrees(rootIDs) + + for _, sqlBlock := range sqlBlocks { + // 解析 IAL 获取 icon + icon := "" + if sqlBlock.IAL != "" { + ialStr := strings.TrimPrefix(sqlBlock.IAL, "{:") + ialStr = strings.TrimSuffix(ialStr, "}") + ial := parse.Tokens2IAL([]byte(ialStr)) + for _, kv := range ial { + if kv[0] == "icon" { + icon = kv[1] + break + } + } + } + // 获取文档标题 + title := "" + if bt := bts[sqlBlock.ID]; nil != bt { + title = path.Base(bt.HPath) + } + if title == "" { + title = sqlBlock.Content + if title == "" { + title = sqlBlock.HPath + if title == "" { + title = sqlBlock.ID + } + } + } + doc := &RecentDoc{ + RootID: sqlBlock.ID, + Icon: icon, + Title: title, + } + ret = append(ret, doc) + } case "closedAt": // 按关闭时间排序 + var filtered []*RecentDoc + for _, doc := range ret { + if doc.ClosedAt > 0 { + filtered = append(filtered, doc) + } + } + ret = filtered sort.Slice(ret, func(i, j int) bool { - if ret[i].ClosedAt == 0 && ret[j].ClosedAt == 0 { - // 如果都没有关闭时间,按浏览时间排序 - return ret[i].ViewedAt > ret[j].ViewedAt - } - if ret[i].ClosedAt == 0 { - return false // 没有关闭时间的排在后面 - } - if ret[j].ClosedAt == 0 { - return true // 有关闭时间的排在前面 - } return ret[i].ClosedAt > ret[j].ClosedAt }) case "openAt": // 按打开时间排序 + var filtered []*RecentDoc + for _, doc := range ret { + if doc.OpenAt > 0 { + filtered = append(filtered, doc) + } + } + ret = filtered sort.Slice(ret, func(i, j int) bool { - if ret[i].OpenAt == 0 && ret[j].OpenAt == 0 { - // 如果都没有打开时间,按ID时间排序(ID包含时间信息) - return ret[i].RootID > ret[j].RootID - } - if ret[i].OpenAt == 0 { - return false // 没有打开时间的排在后面 - } - if ret[j].OpenAt == 0 { - return true // 有打开时间的排在前面 - } return ret[i].OpenAt > ret[j].OpenAt }) default: // 默认按浏览时间排序 + var filtered []*RecentDoc + for _, doc := range ret { + if doc.ViewedAt > 0 { + filtered = append(filtered, doc) + } + } + ret = filtered sort.Slice(ret, func(i, j int) bool { - if ret[i].ViewedAt == 0 && ret[j].ViewedAt == 0 { - // 如果都没有浏览时间,按ID时间排序(ID包含时间信息) - return ret[i].RootID > ret[j].RootID - } - if ret[i].ViewedAt == 0 { - return false // 没有浏览时间的排在后面 - } - if ret[j].ViewedAt == 0 { - return true // 有浏览时间的排在前面 - } return ret[i].ViewedAt > ret[j].ViewedAt }) }