Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 40 additions & 71 deletions app/src/business/openRecentDocs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 += `<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)}
<span class="b3-list-item__text">${escapeHtml(item.title)}</span>
if (key) {
data = data.filter((item) => {
return item.title.toLowerCase().includes(key.toLowerCase());
});
}
data.forEach((item) => {
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)}
<span class="b3-list-item__text">${escapeHtml(item.title)}</span>
</li>`;
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 = '<ul class="b3-list b3-list--background" style="overflow: auto;width: 200px;">';
let docIndex = 0;
if (!key || window.siyuan.languages.riffCard.toLowerCase().includes(key.toLowerCase())) {
dockHtml += `<li data-type="riffCard" data-index="0" class="b3-list-item${!switchPath ? " b3-list-item--focus" : ""}">
<svg class="b3-list-item__graphic"><use xlink:href="#iconRiffCard"></use></svg>
Expand All @@ -57,30 +52,30 @@ ${unicode2Emoji(item.icon || window.siyuan.storage[Constants.LOCAL_IMAGES].file,
if (!switchPath) {
switchPath = window.siyuan.languages.riffCard;
}
docIndex++;
}
let docIndex = 1;
getAllDocks().forEach((item) => {
if (!key || item.title.toLowerCase().includes(key.toLowerCase())) {
dockHtml += `<li data-type="${item.type}" data-index="${docIndex}" class="b3-list-item${!switchPath ? " b3-list-item--focus" : ""}">
<svg class="b3-list-item__graphic"><use xlink:href="#${item.icon}"></use></svg>
<span class="b3-list-item__text">${item.title}</span>
<span class="b3-list-item__meta">${updateHotkeyTip(item.hotkey || "")}</span>
<span class="b3-list-item__meta">${updateHotkeyTip(item.hotkey)}</span>
</li>`;
docIndex++;
if (!switchPath) {
switchPath = window.siyuan.languages.riffCard;
switchPath = item.title;
}
docIndex++;
}
});
dockHtml = dockHtml + "</ul>";
dockHtml = '<ul class="b3-list b3-list--background" style="overflow: auto;width: 200px;">' + dockHtml + "</ul>";
}

const pathElement = element.querySelector(".switch-doc__path");
pathElement.innerHTML = switchPath;
pathElement.previousElementSibling.innerHTML = `<div class="fn__flex fn__flex-1" style="overflow:auto;">
${dockHtml}
<ul style="${isWindow() ? "border-left:0;" : ""}min-width:360px;" class="b3-list b3-list--background fn__flex-1">${tabHtml}</ul>
</div>`;
pathElement.previousElementSibling.innerHTML = `<div class="fn__flex fn__flex-1" style="overflow: auto;">
${dockHtml}
<ul style="${isWindow() ? "border-left: 0;" : ""}min-width: 360px;" class="b3-list b3-list--background fn__flex-1">${tabHtml}</ul>
</div>`;
};

export const openRecentDocs = () => {
Expand All @@ -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);
Expand All @@ -110,15 +106,15 @@ export const openRecentDocs = () => {
<span class="fn__space"></span>
<div class="fn__flex-center">
<select class="b3-select" id="recentDocsSort">
<option value="viewedAt"${window.siyuan.storage[Constants.LOCAL_RECENT_DOCS].type === "viewedAt" ? " selected" : ""}>${window.siyuan.languages.recentViewed}</option>
<option value="updated"${window.siyuan.storage[Constants.LOCAL_RECENT_DOCS].type === "updated" ? " selected" : ""}>${window.siyuan.languages.recentModified}</option>
<option value="openAt"${window.siyuan.storage[Constants.LOCAL_RECENT_DOCS].type === "openAt" ? " selected" : ""}>${window.siyuan.languages.recentOpened}</option>
<option value="closedAt"${window.siyuan.storage[Constants.LOCAL_RECENT_DOCS].type === "closedAt" ? " selected" : ""}>${window.siyuan.languages.recentClosed}</option>
<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>
<div class="fn__flex fn__flex-1" style="overflow: auto;"></div>
<div class="switch-doc__path"></div>
</div>`,
height: "80vh",
Expand All @@ -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) => {
Expand All @@ -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);
});
};
16 changes: 9 additions & 7 deletions app/src/layout/Wnd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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);
Expand Down Expand Up @@ -841,7 +843,7 @@ export class Wnd {
}
}
});
if (latestHeadElement && !closeAll) {
if (latestHeadElement && !isBatchClose) {
this.switchTab(latestHeadElement, true, true, false, false);
this.showHeading();
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
}
Expand Down
55 changes: 43 additions & 12 deletions app/src/layout/tabUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions kernel/api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
27 changes: 25 additions & 2 deletions kernel/api/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,15 +293,38 @@ 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
ret.Msg = err.Error()
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
}
}
2 changes: 0 additions & 2 deletions kernel/model/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,6 @@ func GetDoc(startID, endID, id string, index int, query string, queryTypes map[s
}
keywords = gulu.Str.RemoveDuplicatedElem(keywords)

go setRecentDocByTree(tree)
return
}

Expand Down Expand Up @@ -1597,7 +1596,6 @@ func removeDoc(box *Box, p string, luteEngine *lute.Lute) {
logging.LogInfof("removed doc [%s%s]", box.ID, p)

box.removeSort(removeIDs)
RemoveRecentDoc(removeIDs)
if "/" != dir {
others, err := os.ReadDir(filepath.Join(util.DataDir, box.ID, dir))
if err == nil && 1 > len(others) {
Expand Down
Loading