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