mirror of
https://github.com/siyuan-note/siyuan.git
synced 2025-12-16 22:50:13 +01:00
Improve Recent documents (#15824)
* ✨ Improve Recent documents 基于文档最近浏览时间进行排序 * ✨ 支持显示最近关闭文档 * ✨ 支持显示最近关闭文档 * ✨ 支持显示最近关闭文档 * ✨ 支持显示最近关闭文档 * ✨ 支持显示最近关闭文档 * ✨ 支持显示最近关闭文档 * ✨支持Ctrl+Shift+T打开最近关闭的文档 * 🎨 clean code * 🔥 移除表格插入行/列的默认快捷键 * ✨ 最近文档支持显示最近修改文档 * 🎨 * ✨ 最近文档支持查看最近打开 * 🎨 * ⏪ * Update win-build.bat
This commit is contained in:
parent
7e1306cab9
commit
d9e0c56a47
23 changed files with 10579 additions and 10202 deletions
|
|
@ -1361,6 +1361,10 @@
|
|||
"uploadError": "خطأ في الرفع",
|
||||
"uploading": "يتم الرفع...",
|
||||
"wysiwyg": "ما تراه هو ما تحصل عليه (WYSIWYG)",
|
||||
"recentViewed": "تم عرضها مؤخراً",
|
||||
"recentOpened": "تم فتحها مؤخراً",
|
||||
"recentClosed": "تم إغلاقها مؤخراً",
|
||||
"recentModified": "تم تعديلها مؤخراً",
|
||||
"_label": "العربية",
|
||||
"_time": {
|
||||
"albl": "من قبل",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1361,6 +1361,10 @@
|
|||
"uploadError": "upload error",
|
||||
"uploading": "Uploading...",
|
||||
"wysiwyg": "WYSIWYG",
|
||||
"recentViewed": "Recently Viewed",
|
||||
"recentOpened": "Recently Opened",
|
||||
"recentClosed": "Recently Closed",
|
||||
"recentModified": "Recently Modified",
|
||||
"_label": "English",
|
||||
"_time": {
|
||||
"albl": "ago",
|
||||
|
|
|
|||
|
|
@ -1361,6 +1361,10 @@
|
|||
"uploadError": "error de subida",
|
||||
"uploading": "Subiendo...",
|
||||
"wysiwyg": "WYSIWYG",
|
||||
"recentViewed": "Visto recientemente",
|
||||
"recentOpened": "Abierto recientemente",
|
||||
"recentClosed": "Cerrado recientemente",
|
||||
"recentModified": "Modificado recientemente",
|
||||
"_label": "Español",
|
||||
"_time": {
|
||||
"albl": "hace",
|
||||
|
|
|
|||
|
|
@ -1361,6 +1361,10 @@
|
|||
"uploadError": "erreur de transfert",
|
||||
"uploading": "Transfert en cours",
|
||||
"wysiwyg": "WYSIWYG",
|
||||
"recentViewed": "Récemment consulté",
|
||||
"recentOpened": "Récemment ouvert",
|
||||
"recentClosed": "Récemment fermé",
|
||||
"recentModified": "Récemment modifié",
|
||||
"_label": "Français",
|
||||
"_time": {
|
||||
"albl": "Précédemment",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1361,6 +1361,10 @@
|
|||
"uploadError": "アップロードエラー",
|
||||
"uploading": "アップロード中...",
|
||||
"wysiwyg": "WYSIWYG",
|
||||
"recentViewed": "最近閲覧",
|
||||
"recentOpened": "最近開いた",
|
||||
"recentClosed": "最近閉じる",
|
||||
"recentModified": "最近更新",
|
||||
"_label": "日本語",
|
||||
"_time": {
|
||||
"albl": "前",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1361,6 +1361,10 @@
|
|||
"uploadError": "上傳錯誤",
|
||||
"uploading": "上傳中...",
|
||||
"wysiwyg": "所見即所得",
|
||||
"recentViewed": "最近瀏覽",
|
||||
"recentOpened": "最近開啟",
|
||||
"recentClosed": "最近關閉",
|
||||
"recentModified": "最近更新",
|
||||
"_label": "繁體中文",
|
||||
"_time": {
|
||||
"albl": "前",
|
||||
|
|
|
|||
|
|
@ -1361,6 +1361,10 @@
|
|||
"uploadError": "上传错误",
|
||||
"uploading": "上传中...",
|
||||
"wysiwyg": "所见即所得",
|
||||
"recentViewed": "最近浏览",
|
||||
"recentOpened": "最近打开",
|
||||
"recentClosed": "最近关闭",
|
||||
"recentModified": "最近修改",
|
||||
"_label": "简体中文",
|
||||
"_time": {
|
||||
"albl": "前",
|
||||
|
|
|
|||
|
|
@ -1532,6 +1532,19 @@ export const windowKeyDown = (app: App, event: KeyboardEvent) => {
|
|||
return;
|
||||
}
|
||||
|
||||
if (matchHotKey("⇧⌘T", event)) {
|
||||
if ((window as any).siyuan.closedTabs && (window as any).siyuan.closedTabs.length > 0) {
|
||||
const closedTab = (window as any).siyuan.closedTabs.pop();
|
||||
openFileById({
|
||||
app,
|
||||
id: closedTab.id,
|
||||
action: [Constants.CB_GET_FOCUS, Constants.CB_GET_SCROLL]
|
||||
});
|
||||
}
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (matchHotKey(window.siyuan.config.keymap.general.goToTab1.custom, event) && !event.repeat) {
|
||||
switchTabByIndex(0);
|
||||
event.preventDefault();
|
||||
|
|
|
|||
|
|
@ -10,10 +10,18 @@ import {focusByRange} from "../protyle/util/selection";
|
|||
import {hasClosestByClassName} from "../protyle/util/hasClosest";
|
||||
import {hideElements} from "../protyle/ui/hideElements";
|
||||
|
||||
const getHTML = async (data: { rootID: string, icon: string, title: string }[], element: Element, key?: string) => {
|
||||
const getHTML = async (data: { rootID: string, icon: string, title: string, viewedAt?: number, closedAt?: number, openAt?: number, updated?: number }[], element: Element, key?: string, sortBy: "viewedAt" | "closedAt" | "openAt" | "updated" = "viewedAt") => {
|
||||
let tabHtml = "";
|
||||
let index = 0;
|
||||
data.forEach((item) => {
|
||||
|
||||
// 根据排序字段对数据进行排序
|
||||
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 += `<li data-index="${index}" data-node-id="${item.rootID}" class="b3-list-item${index === 0 ? " b3-list-item--focus" : ""}">
|
||||
${unicode2Emoji(item.icon || window.siyuan.storage[Constants.LOCAL_IMAGES].file, "b3-list-item__graphic", true)}
|
||||
|
|
@ -77,7 +85,7 @@ export const openRecentDocs = () => {
|
|||
hideElements(["dialog"]);
|
||||
return;
|
||||
}
|
||||
fetchPost("/api/storage/getRecentDocs", {}, (response) => {
|
||||
fetchPost("/api/storage/getRecentDocs", {sortBy: "viewedAt"}, (response) => {
|
||||
let range: Range;
|
||||
if (getSelection().rangeCount > 0) {
|
||||
range = getSelection().getRangeAt(0);
|
||||
|
|
@ -91,6 +99,14 @@ export const openRecentDocs = () => {
|
|||
<svg class="b3-form__icon-icon"><use xlink:href="#iconSearch"></use></svg>
|
||||
<input placeholder="${window.siyuan.languages.search}" class="b3-text-field fn__block b3-form__icon-input">
|
||||
</div>
|
||||
<div class="fn__flex-center fn__ml8">
|
||||
<select class="b3-select fn__size200" id="recentDocsSort">
|
||||
<option value="viewedAt">${window.siyuan.languages.recentViewed}</option>
|
||||
<option value="updated">${window.siyuan.languages.recentModified}</option>
|
||||
<option value="openAt">${window.siyuan.languages.recentOpened}</option>
|
||||
<option value="closedAt">${window.siyuan.languages.recentClosed}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>`,
|
||||
content: `<div class="fn__flex-column switch-doc">
|
||||
<div class="fn__flex fn__flex-1" style="overflow:auto;"></div>
|
||||
|
|
@ -106,13 +122,13 @@ export const openRecentDocs = () => {
|
|||
const searchElement = dialog.element.querySelector("input");
|
||||
searchElement.focus();
|
||||
searchElement.addEventListener("compositionend", () => {
|
||||
getHTML(response.data, dialog.element, searchElement.value);
|
||||
getHTML(response.data, dialog.element, searchElement.value, sortSelect.value as "viewedAt" | "closedAt" | "openAt" | "updated");
|
||||
});
|
||||
searchElement.addEventListener("input", (event: InputEvent) => {
|
||||
if (event.isComposing) {
|
||||
return;
|
||||
}
|
||||
getHTML(response.data, dialog.element, searchElement.value);
|
||||
getHTML(response.data, dialog.element, searchElement.value, sortSelect.value as "viewedAt" | "closedAt" | "openAt" | "updated");
|
||||
});
|
||||
dialog.element.setAttribute("data-key", Constants.DIALOG_RECENTDOCS);
|
||||
dialog.element.addEventListener("click", (event) => {
|
||||
|
|
@ -125,6 +141,45 @@ export const openRecentDocs = () => {
|
|||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// 添加排序下拉框事件监听
|
||||
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: 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 "viewedAt" | "closedAt" | "openAt");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
getHTML(response.data, dialog.element);
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -572,10 +572,10 @@ export abstract class Constants {
|
|||
checkToggle: {default: "⌘↩", custom: "⌘↩"},
|
||||
},
|
||||
table: {
|
||||
insertRowAbove: {default: "⇧⌘T", custom: "⇧⌘T"},
|
||||
insertRowBelow: {default: "⇧⌘D", custom: "⇧⌘D"},
|
||||
insertColumnLeft: {default: "⇧⌘L", custom: "⇧⌘L"},
|
||||
insertColumnRight: {default: "⇧⌘R", custom: "⇧⌘R"},
|
||||
insertRowAbove: {default: "", custom: ""},
|
||||
insertRowBelow: {default: "", custom: ""},
|
||||
insertColumnLeft: {default: "", custom: ""},
|
||||
insertColumnRight: {default: "", custom: ""},
|
||||
moveToUp: {default: "⌥⌘T", custom: "⌥⌘T"},
|
||||
moveToDown: {default: "⌥⌘B", custom: "⌥⌘B"},
|
||||
moveToLeft: {default: "⌥⌘L", custom: "⌥⌘L"},
|
||||
|
|
|
|||
|
|
@ -56,6 +56,10 @@ export const openFileById = async (options: {
|
|||
showMessage(response.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新文档浏览时间
|
||||
fetchPost("/api/storage/updateRecentDocViewTime", {rootID: response.data.rootID});
|
||||
|
||||
return openFile({
|
||||
app: options.app,
|
||||
fileName: response.data.rootTitle,
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import {Search} from "../search";
|
|||
import {showMessage} from "../dialog/message";
|
||||
import {openFileById, updatePanelByEditor} from "../editor/util";
|
||||
import {scrollCenter} from "../util/highlightById";
|
||||
import {fetchPost} from "../util/fetch";
|
||||
import {getAllModels} from "./getAll";
|
||||
import {clearCounter} from "./status";
|
||||
import {saveScroll} from "../protyle/scroll/saveScroll";
|
||||
|
|
@ -555,6 +556,9 @@ export class Wnd {
|
|||
}
|
||||
// focusin 触发前,layout__wnd--active 和 tab 已设置,需在调用里面更新
|
||||
if (update) {
|
||||
// 更新文档浏览时间
|
||||
fetchPost("/api/storage/updateRecentDocViewTime", {rootID: currentTab.model.editor.protyle.block.rootID});
|
||||
|
||||
updatePanelByEditor({
|
||||
protyle: currentTab.model.editor.protyle,
|
||||
focus: true,
|
||||
|
|
@ -632,6 +636,12 @@ export class Wnd {
|
|||
if (tab.callback) {
|
||||
tab.callback(tab);
|
||||
}
|
||||
|
||||
// 当文档第一次加载到页签时更新 openAt 时间
|
||||
if (tab.model instanceof Editor && tab.model.editor?.protyle?.block?.rootID) {
|
||||
fetchPost("/api/storage/updateRecentDocOpenTime", {rootID: tab.model.editor.protyle.block.rootID});
|
||||
}
|
||||
|
||||
// 移除 centerLayout 中的 empty
|
||||
if (this.parent.type === "center" && this.children.length === 2 && !this.children[0].headElement) {
|
||||
this.removeTab(this.children[0].id);
|
||||
|
|
@ -781,6 +791,14 @@ export class Wnd {
|
|||
}
|
||||
if (item.model instanceof Editor) {
|
||||
saveScroll(item.model.editor.protyle);
|
||||
// 更新文档关闭时间
|
||||
fetchPost("/api/storage/updateRecentDocCloseTime", {rootID: item.model.editor.protyle.block.rootID});
|
||||
if (!(window as any).siyuan.closedTabs) {
|
||||
(window as any).siyuan.closedTabs = [];
|
||||
}
|
||||
(window as any).siyuan.closedTabs.push({
|
||||
id: item.model.editor.protyle.block.rootID
|
||||
});
|
||||
}
|
||||
if (this.children.length === 1) {
|
||||
this.destroyModel(this.children[0].model);
|
||||
|
|
|
|||
|
|
@ -52,6 +52,9 @@ export const openMobileFileById = (app: App, id: string, action: TProtyleAction[
|
|||
showMessage(data.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新文档浏览时间
|
||||
fetchPost("/api/storage/updateRecentDocViewTime", {rootID: data.data.rootID});
|
||||
const protyleOptions: IProtyleOptions = {
|
||||
blockId: id,
|
||||
rootId: data.data.rootID,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {openMobileFileById} from "../editor";
|
|||
import {App} from "../../index";
|
||||
|
||||
export const getRecentDocs = (app: App) => {
|
||||
fetchPost("/api/storage/getRecentDocs", {}, (response) => {
|
||||
fetchPost("/api/storage/getRecentDocs", {sortBy: "viewedAt"}, (response) => {
|
||||
let html = "";
|
||||
response.data.forEach((item: any, index: number) => {
|
||||
html += `<li data-index="${index}" data-node-id="${item.rootID}" class="b3-list-item${index === 0 ? " b3-list-item--focus" : ""}">
|
||||
|
|
|
|||
|
|
@ -78,6 +78,10 @@ func ServeAPI(ginServer *gin.Engine) {
|
|||
ginServer.Handle("POST", "/api/storage/getCriteria", model.CheckAuth, getCriteria)
|
||||
ginServer.Handle("POST", "/api/storage/removeCriterion", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeCriterion)
|
||||
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/updateRecentDocOpenTime", model.CheckAuth, updateRecentDocOpenTime)
|
||||
|
||||
ginServer.Handle("POST", "/api/storage/getOutlineStorage", model.CheckAuth, getOutlineStorage)
|
||||
ginServer.Handle("POST", "/api/storage/setOutlineStorage", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setOutlineStorage)
|
||||
ginServer.Handle("POST", "/api/storage/removeOutlineStorage", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeOutlineStorage)
|
||||
|
|
|
|||
|
|
@ -29,7 +29,18 @@ func getRecentDocs(c *gin.Context) {
|
|||
ret := gulu.Ret.NewResult()
|
||||
defer c.JSON(http.StatusOK, ret)
|
||||
|
||||
data, err := model.GetRecentDocs()
|
||||
arg, ok := util.JsonArg(c, ret)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取排序参数
|
||||
sortBy := "viewedAt" // 默认按浏览时间排序
|
||||
if arg["sortBy"] != nil {
|
||||
sortBy = arg["sortBy"].(string)
|
||||
}
|
||||
|
||||
data, err := model.GetRecentDocs(sortBy)
|
||||
if err != nil {
|
||||
ret.Code = -1
|
||||
ret.Msg = err.Error()
|
||||
|
|
@ -236,3 +247,59 @@ func removeOutlineStorage(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func updateRecentDocViewTime(c *gin.Context) {
|
||||
ret := gulu.Ret.NewResult()
|
||||
defer c.JSON(http.StatusOK, ret)
|
||||
|
||||
arg, ok := util.JsonArg(c, ret)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
rootID := arg["rootID"].(string)
|
||||
err := model.UpdateRecentDocViewTime(rootID)
|
||||
if err != nil {
|
||||
ret.Code = -1
|
||||
ret.Msg = err.Error()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func updateRecentDocOpenTime(c *gin.Context) {
|
||||
ret := gulu.Ret.NewResult()
|
||||
defer c.JSON(http.StatusOK, ret)
|
||||
|
||||
arg, ok := util.JsonArg(c, ret)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
rootID := arg["rootID"].(string)
|
||||
err := model.UpdateRecentDocOpenTime(rootID)
|
||||
if err != nil {
|
||||
ret.Code = -1
|
||||
ret.Msg = err.Error()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func updateRecentDocCloseTime(c *gin.Context) {
|
||||
ret := gulu.Ret.NewResult()
|
||||
defer c.JSON(http.StatusOK, ret)
|
||||
|
||||
arg, ok := util.JsonArg(c, ret)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
rootID := arg["rootID"].(string)
|
||||
err := model.UpdateRecentDocCloseTime(rootID)
|
||||
if err != nil {
|
||||
ret.Code = -1
|
||||
ret.Msg = err.Error()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -21,7 +21,9 @@ import (
|
|||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/88250/gulu"
|
||||
"github.com/88250/lute/parse"
|
||||
|
|
@ -32,9 +34,12 @@ import (
|
|||
)
|
||||
|
||||
type RecentDoc struct {
|
||||
RootID string `json:"rootID"`
|
||||
Icon string `json:"icon"`
|
||||
Title string `json:"title"`
|
||||
RootID string `json:"rootID"`
|
||||
Icon string `json:"icon"`
|
||||
Title string `json:"title"`
|
||||
ViewedAt int64 `json:"viewedAt"` // 浏览时间字段
|
||||
ClosedAt int64 `json:"closedAt"` // 关闭时间字段
|
||||
OpenAt int64 `json:"openAt"` // 文档第一次从文档树加载到页签的时间
|
||||
}
|
||||
|
||||
type OutlineDoc struct {
|
||||
|
|
@ -70,9 +75,12 @@ func RemoveRecentDoc(ids []string) {
|
|||
|
||||
func setRecentDocByTree(tree *parse.Tree) {
|
||||
recentDoc := &RecentDoc{
|
||||
RootID: tree.Root.ID,
|
||||
Icon: tree.Root.IALAttr("icon"),
|
||||
Title: tree.Root.IALAttr("title"),
|
||||
RootID: tree.Root.ID,
|
||||
Icon: tree.Root.IALAttr("icon"),
|
||||
Title: tree.Root.IALAttr("title"),
|
||||
ViewedAt: time.Now().Unix(), // 使用当前时间作为浏览时间
|
||||
ClosedAt: 0, // 初始化关闭时间为0,表示未关闭
|
||||
OpenAt: time.Now().Unix(), // 设置文档打开时间
|
||||
}
|
||||
|
||||
recentDocLock.Lock()
|
||||
|
|
@ -99,10 +107,95 @@ func setRecentDocByTree(tree *parse.Tree) {
|
|||
return
|
||||
}
|
||||
|
||||
func GetRecentDocs() (ret []*RecentDoc, err error) {
|
||||
// 更新文档打开时间(只在第一次从文档树加载到页签时调用)
|
||||
func UpdateRecentDocOpenTime(rootID string) error {
|
||||
recentDocLock.Lock()
|
||||
defer recentDocLock.Unlock()
|
||||
return getRecentDocs()
|
||||
|
||||
recentDocs, err := getRecentDocs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 查找文档并更新打开时间
|
||||
found := false
|
||||
for _, doc := range recentDocs {
|
||||
if doc.RootID == rootID {
|
||||
doc.OpenAt = time.Now().Unix()
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
err = setRecentDocs(recentDocs)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新文档浏览时间
|
||||
func UpdateRecentDocViewTime(rootID string) error {
|
||||
recentDocLock.Lock()
|
||||
defer recentDocLock.Unlock()
|
||||
|
||||
recentDocs, err := getRecentDocs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 查找文档并更新浏览时间
|
||||
found := false
|
||||
for _, doc := range recentDocs {
|
||||
if doc.RootID == rootID {
|
||||
doc.ViewedAt = time.Now().Unix()
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
// 按浏览时间降序排序
|
||||
sort.Slice(recentDocs, func(i, j int) bool {
|
||||
return recentDocs[i].ViewedAt > recentDocs[j].ViewedAt
|
||||
})
|
||||
err = setRecentDocs(recentDocs)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新文档关闭时间
|
||||
func UpdateRecentDocCloseTime(rootID string) error {
|
||||
recentDocLock.Lock()
|
||||
defer recentDocLock.Unlock()
|
||||
|
||||
recentDocs, err := getRecentDocs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 查找文档并更新关闭时间
|
||||
found := false
|
||||
for _, doc := range recentDocs {
|
||||
if doc.RootID == rootID {
|
||||
doc.ClosedAt = time.Now().Unix()
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
err = setRecentDocs(recentDocs)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func GetRecentDocs(sortBy ...string) (ret []*RecentDoc, err error) {
|
||||
recentDocLock.Lock()
|
||||
defer recentDocLock.Unlock()
|
||||
return getRecentDocs(sortBy...)
|
||||
}
|
||||
|
||||
func setRecentDocs(recentDocs []*RecentDoc) (err error) {
|
||||
|
|
@ -127,7 +220,7 @@ func setRecentDocs(recentDocs []*RecentDoc) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func getRecentDocs() (ret []*RecentDoc, err error) {
|
||||
func getRecentDocs(sortBy ...string) (ret []*RecentDoc, err error) {
|
||||
tmp := []*RecentDoc{}
|
||||
dataPath := filepath.Join(util.DataDir, "storage/recent-doc.json")
|
||||
if !filelock.IsExist(dataPath) {
|
||||
|
|
@ -159,9 +252,77 @@ func getRecentDocs() (ret []*RecentDoc, err error) {
|
|||
notExists = append(notExists, doc.RootID)
|
||||
}
|
||||
}
|
||||
|
||||
if 0 < len(notExists) {
|
||||
setRecentDocs(ret)
|
||||
}
|
||||
|
||||
// 根据排序参数进行排序
|
||||
if len(sortBy) > 0 {
|
||||
switch sortBy[0] {
|
||||
case "closedAt":
|
||||
// 按关闭时间排序
|
||||
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":
|
||||
// 按打开时间排序
|
||||
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:
|
||||
// 默认按浏览时间排序
|
||||
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
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// 默认按浏览时间降序排序,如果ViewedAt为0则使用文档创建时间
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue