Improve Recent documents (#15824)

*  Improve Recent documents
基于文档最近浏览时间进行排序

*  支持显示最近关闭文档

*  支持显示最近关闭文档

*  支持显示最近关闭文档

*  支持显示最近关闭文档

*  支持显示最近关闭文档

*  支持显示最近关闭文档

* 支持Ctrl+Shift+T打开最近关闭的文档

* 🎨 clean code

* 🔥 移除表格插入行/列的默认快捷键

*  最近文档支持显示最近修改文档

* 🎨

*  最近文档支持查看最近打开

* 🎨

* 

* Update win-build.bat
This commit is contained in:
Achuan-2 2025-10-24 11:12:14 +08:00 committed by GitHub
parent 7e1306cab9
commit d9e0c56a47
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 10579 additions and 10202 deletions

View file

@ -1361,6 +1361,10 @@
"uploadError": "خطأ في الرفع",
"uploading": "يتم الرفع...",
"wysiwyg": "‫ما تراه هو ما تحصل عليه (WYSIWYG)",
"recentViewed": "تم عرضها مؤخراً",
"recentOpened": "تم فتحها مؤخراً",
"recentClosed": "تم إغلاقها مؤخراً",
"recentModified": "تم تعديلها مؤخراً",
"_label": "العربية",
"_time": {
"albl": "من قبل",

View file

@ -1361,6 +1361,10 @@
"uploadError": "Upload-Fehler",
"uploading": "Hochladen...",
"wysiwyg": "WYSIWYG",
"recentViewed": "Zuletzt angesehen",
"recentOpened": "Zuletzt geöffnet",
"recentClosed": "Zuletzt geschlossen",
"recentModified": "Zuletzt geändert",
"_label": "Deutsch",
"_time": {
"albl": "vor",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -1361,6 +1361,10 @@
"uploadError": "שגיאת העלאה",
"uploading": "מעלה...",
"wysiwyg": "WYSIWYG",
"recentViewed": "נצפה לאחרונה",
"recentOpened": "נפתח לאחרונה",
"recentClosed": "נסגר לאחרונה",
"recentModified": "שונה לאחרונה",
"_label": "עברית",
"_time": {
"albl": "לפני",

View file

@ -1361,6 +1361,10 @@
"uploadError": "Errore di caricamento",
"uploading": "Caricamento in corso.",
"wysiwyg": "WYSIWYG",
"recentViewed": "Visualizzato di recente",
"recentOpened": "Aperto di recente",
"recentClosed": "Chiuso di recente",
"recentModified": "Modificato di recente",
"_label": "Italiano",
"_time": {
"albl": "fa",

View file

@ -1361,6 +1361,10 @@
"uploadError": "アップロードエラー",
"uploading": "アップロード中...",
"wysiwyg": "WYSIWYG",
"recentViewed": "最近閲覧",
"recentOpened": "最近開いた",
"recentClosed": "最近閉じる",
"recentModified": "最近更新",
"_label": "日本語",
"_time": {
"albl": "前",

View file

@ -1361,6 +1361,10 @@
"uploadError": "błąd przesyłania",
"uploading": "Przesyłanie...",
"wysiwyg": "WYSIWYG",
"recentViewed": "Ostatnio przeglądane",
"recentOpened": "Ostatnio otwarte",
"recentClosed": "Ostatnio zamknięte",
"recentModified": "Ostatnio modyfikowane",
"_label": "Polski",
"_time": {
"albl": "temu",

View file

@ -1361,6 +1361,10 @@
"uploadError": "erro de upload",
"uploading": "Enviando...",
"wysiwyg": "WYSIWYG",
"recentViewed": "Visualizado recentemente",
"recentOpened": "Aberto recentemente",
"recentClosed": "Fechado recentemente",
"recentModified": "Modificado recentemente",
"_label": "Português (Brasil)",
"_time": {
"albl": "atrás",

View file

@ -1361,6 +1361,10 @@
"uploadError": "Ошибка загрузки",
"uploading": "Загрузка...",
"wysiwyg": "WYSIWYG",
"recentViewed": "Недавно просмотренные",
"recentOpened": "Недавно открытые",
"recentClosed": "Недавно закрытые",
"recentModified": "Недавно измененные",
"_label": "Русский",
"_time": {
"albl": "назад",

View file

@ -1361,6 +1361,10 @@
"uploadError": "上傳錯誤",
"uploading": "上傳中...",
"wysiwyg": "所見即所得",
"recentViewed": "最近瀏覽",
"recentOpened": "最近開啟",
"recentClosed": "最近關閉",
"recentModified": "最近更新",
"_label": "繁體中文",
"_time": {
"albl": "前",

View file

@ -1361,6 +1361,10 @@
"uploadError": "上传错误",
"uploading": "上传中...",
"wysiwyg": "所见即所得",
"recentViewed": "最近浏览",
"recentOpened": "最近打开",
"recentClosed": "最近关闭",
"recentModified": "最近修改",
"_label": "简体中文",
"_time": {
"albl": "前",

View file

@ -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();

View file

@ -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);
});
};

View file

@ -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"},

View file

@ -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,

View file

@ -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);

View file

@ -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,

View file

@ -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" : ""}">

View file

@ -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)

View file

@ -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
}
}

View file

@ -21,7 +21,9 @@ import (
"os"
"path"
"path/filepath"
"sort"
"sync"
"time"
"github.com/88250/gulu"
"github.com/88250/lute/parse"
@ -35,6 +37,9 @@ type RecentDoc struct {
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 {
@ -73,6 +78,9 @@ func setRecentDocByTree(tree *parse.Tree) {
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
}