mirror of
https://github.com/siyuan-note/siyuan.git
synced 2025-12-16 14:40:12 +01:00
Improve the outline panel (#15814)
* ✨ 实现大纲持久化 - 添加折叠状态变化的实时保存功能 - 在文档更新时恢复折叠状态 - 保存拖拽前的折叠状态并在拖拽后恢复 - 更新本地存储结构以支持折叠状态 * 🎨 clean code * ✨ 在`data/storage`文件夹下创建`outline/${docID}.json`文件,存储标题outline展开信息 * ✨ alt click折叠/展开统计标题 * ✨添加层级控制功能 - 新增层级控制滑条,允许用户展开指定层级 * 🌐 添加多语言支持的展开层级功能 * ✨ f添加层级重置显示功能 - 新增 resetLevelDisplay 方法以重置层级显示状态 - 更新层级控制的初始化逻辑,默认不显示层级 - 在文档切换时重置层级显示状态 * ✨优化层级控制功能 - 添加用户主动层级控制标记 - 修改层级显示重置逻辑,仅在非用户操作时重置 - 更新层级控制滑条的点击事件处理 * ♻️ 重构大纲存储逻辑 - 合并大纲存储为单一文件 outline.json * ♻️ outline.json 单行存储 * ♻️ outline.json参考recent-doc.json,只存储前1000个文档信息,每次新增信息会把数据放在最前面 * ♻️单行存储json * ♻️ 增加outline.json存储限制至2000个 * ✨ 新增`保持当前标题展开`按钮,`保持全部展开`改为`全部展开`按钮 * ✨ 保持当前标题展开优化 - 超过两级折叠,也能都展开 - 如果父节点折叠,展开时自动折叠兄弟节点,只展开当前节点路径,如果父节点是展开状态,则不影响兄弟节点折叠状态 * 🔥 移除层级文本 * ✨ 右键点击toggle时展开所有子标题 * ✨ 右键click点击折叠图标,会折叠/展开所有子标题 * ✨ 大纲支持筛选功能 * ✨ feat(大纲): 优化筛选功能以显示所有子标题 - 添加 showAllDescendants 函数以显示所有子标题 - 修改 processUL 函数以在父标题命中时显示所有子标题 - 确保未命中的子标题隐藏 * ✨ 优化大纲筛选 - ✅如果标题命中筛选,这个标题的所有父标题展开,以显示出这个标题位置 - ✅如果父标题命中筛选,子项都没有命中筛选,则折叠全部子项(依然可以展开显示) - ✅如果父标题命中筛选,子项也有命中筛选,则展开命中的子项,其他无关子项隐藏 * 💄 展开层级改为按钮,原先的圆点样式碍眼 * 💄 展开层级改为按钮,原先的圆点样式碍眼 * 💄 style(菜单): 优化展开层级菜单的样式和位置 - 添加图标以增强可视化 - 调整菜单弹出位置以改善用户体验 * ♻️ refactor(大纲): 优化标题级别获取逻辑 - 调整展开到指定层级的逻辑,使用标题级别进行判断 * 🎨 * ✨ feat(大纲): 添加右键菜单功能 - 实现右键点击标题时显示上下文菜单 - 增加标题升级、降级、插入、删除等操作 - 基于标题级别展开/折叠同级标题 * ✨ feat(大纲): 添加子标题功能 - 在右键菜单中添加“添加子标题”选项 - 实现子标题的添加逻辑,支持最大级别为H6 - 使用当前标题作为父标题,插入新子标题 * ✨ feat(大纲): "添加子标题"确保父标题展开状态 * ✨ feat(大纲): 使用expandIds方式保存父标题展开状态 - 确保父标题保持展开状态 - 保存展开状态到持久化存储 - 移除冗余的状态保存逻辑 * ✨ feat(大纲): 调整右键菜单顺序,将“全部折叠”功能移至“全部展开”之后 * 🌐 i18n optimization
This commit is contained in:
parent
0211e04c2b
commit
7545c2517f
19 changed files with 1768 additions and 55 deletions
|
|
@ -977,6 +977,9 @@
|
|||
"heading4": "عنوان 4",
|
||||
"heading5": "عنوان 5",
|
||||
"heading6": "عنوان 6",
|
||||
"outlineExpandLevel": "مستوى التوسيع",
|
||||
"expandAll": "توسيع الكل",
|
||||
"outlineKeepCurrentExpand": "الحفاظ على التوسيع الحالي",
|
||||
"general": "عام",
|
||||
"list1": "قائمة",
|
||||
"element": "عناصر",
|
||||
|
|
|
|||
|
|
@ -977,6 +977,9 @@
|
|||
"heading4": "Überschrift 4",
|
||||
"heading5": "Überschrift 5",
|
||||
"heading6": "Überschrift 6",
|
||||
"outlineExpandLevel": "Expansionsebene",
|
||||
"expandAll": "Alle erweitern",
|
||||
"outlineKeepCurrentExpand": "Aktuelle Erweiterung beibehalten",
|
||||
"general": "Allgemein",
|
||||
"list1": "Liste",
|
||||
"element": "Element",
|
||||
|
|
|
|||
|
|
@ -977,6 +977,9 @@
|
|||
"heading4": "Heading 4",
|
||||
"heading5": "Heading 5",
|
||||
"heading6": "Heading 6",
|
||||
"outlineExpandLevel": "Expand level",
|
||||
"expandAll": "Expand all",
|
||||
"outlineKeepCurrentExpand": "Keep current expand",
|
||||
"general": "General",
|
||||
"list1": "List",
|
||||
"element": "Element",
|
||||
|
|
|
|||
|
|
@ -977,6 +977,9 @@
|
|||
"heading4": "Encabezado 4",
|
||||
"heading5": "Encabezado 5",
|
||||
"heading6": "Encabezado 6",
|
||||
"outlineExpandLevel": "Nivel de expansión",
|
||||
"expandAll": "Expandir todo",
|
||||
"outlineKeepCurrentExpand": "Mantener la expansión actual",
|
||||
"general": "General",
|
||||
"list1": "Lista",
|
||||
"element": "elemento",
|
||||
|
|
|
|||
|
|
@ -977,6 +977,9 @@
|
|||
"heading4": "Titre 4",
|
||||
"heading5": "Titre 5",
|
||||
"heading6": "Titre 6",
|
||||
"outlineExpandLevel": "Niveau d'expansion",
|
||||
"expandAll": "Tout développer",
|
||||
"outlineKeepCurrentExpand": "Maintenir le titre actuel développé",
|
||||
"general": "Général",
|
||||
"list1": "Liste",
|
||||
"element": "élément",
|
||||
|
|
|
|||
|
|
@ -977,6 +977,9 @@
|
|||
"heading4": "כותרת 4",
|
||||
"heading5": "כותרת 5",
|
||||
"heading6": "כותרת 6",
|
||||
"outlineExpandLevel": "רמת הרחבה",
|
||||
"expandAll": "הרחב הכל",
|
||||
"outlineKeepCurrentExpand": "שמור כותרת נוכחית מורחבת",
|
||||
"general": "כללי",
|
||||
"list1": "רשימה",
|
||||
"element": "אלמנט",
|
||||
|
|
|
|||
|
|
@ -977,6 +977,9 @@
|
|||
"heading4": "Titolo 4",
|
||||
"heading5": "Titolo 5",
|
||||
"heading6": "Titolo 6",
|
||||
"outlineExpandLevel": "展開レベル",
|
||||
"expandAll": "すべて展開",
|
||||
"outlineKeepCurrentExpand": "現在の見出しを展開し続ける",
|
||||
"general": "Generale",
|
||||
"list1": "Lista",
|
||||
"element": "elemento",
|
||||
|
|
|
|||
|
|
@ -977,6 +977,9 @@
|
|||
"heading4": "見出し4",
|
||||
"heading5": "見出し5",
|
||||
"heading6": "見出し6",
|
||||
"outlineExpandLevel": "展開レベル",
|
||||
"expandAll": "すべて展開",
|
||||
"outlineKeepCurrentExpand": "現在の見出しを展開し続ける",
|
||||
"general": "一般",
|
||||
"list1": "リスト",
|
||||
"element": "要素",
|
||||
|
|
|
|||
|
|
@ -977,6 +977,9 @@
|
|||
"heading4": "Nagłówek 4",
|
||||
"heading5": "Nagłówek 5",
|
||||
"heading6": "Nagłówek 6",
|
||||
"outlineExpandLevel": "Poziom rozwinięcia",
|
||||
"expandAll": "Rozwiń wszystko",
|
||||
"outlineKeepCurrentExpand": "Utrzymuj bieżący tytuł rozwinięty",
|
||||
"general": "Ogólne",
|
||||
"list1": "Lista",
|
||||
"element": "element",
|
||||
|
|
|
|||
|
|
@ -977,6 +977,9 @@
|
|||
"heading4": "Título 4",
|
||||
"heading5": "Título 5",
|
||||
"heading6": "Título 6",
|
||||
"outlineExpandLevel": "Nível de expansão",
|
||||
"expandAll": "Expandir tudo",
|
||||
"outlineKeepCurrentExpand": "Manter título atual expandido",
|
||||
"general": "Geral",
|
||||
"list1": "Lista",
|
||||
"element": "Elemento",
|
||||
|
|
|
|||
|
|
@ -977,6 +977,9 @@
|
|||
"heading4": "Заголовок 4",
|
||||
"heading5": "Заголовок 5",
|
||||
"heading6": "Заголовок 6",
|
||||
"outlineExpandLevel": "Уровень развертывания",
|
||||
"expandAll": "Развернуть все",
|
||||
"outlineKeepCurrentExpand": "Сохранять текущий заголовок развернутым",
|
||||
"general": "Общее",
|
||||
"list1": "Список",
|
||||
"element": "элемент",
|
||||
|
|
|
|||
|
|
@ -977,6 +977,9 @@
|
|||
"heading4": "四級標題",
|
||||
"heading5": "五級標題",
|
||||
"heading6": "六級標題",
|
||||
"outlineExpandLevel": "展開層級",
|
||||
"expandAll": "全部展開",
|
||||
"outlineKeepCurrentExpand": "保持當前標題展開",
|
||||
"general": "通用",
|
||||
"list1": "列表",
|
||||
"element": "元素",
|
||||
|
|
|
|||
|
|
@ -977,6 +977,9 @@
|
|||
"heading4": "四级标题",
|
||||
"heading5": "五级标题",
|
||||
"heading6": "六级标题",
|
||||
"outlineExpandLevel": "展开层级",
|
||||
"expandAll": "全部展开",
|
||||
"outlineKeepCurrentExpand": "保持当前标题展开",
|
||||
"general": "通用",
|
||||
"list1": "列表",
|
||||
"element": "元素",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -388,7 +388,7 @@ export const getLocalStorage = (cb: () => void) => {
|
|||
defaultStorage[Constants.LOCAL_AI] = []; // {name: "", memo: ""}
|
||||
defaultStorage[Constants.LOCAL_PLUGIN_DOCKS] = {}; // { pluginName: {dockId: IPluginDockTab}}
|
||||
defaultStorage[Constants.LOCAL_PLUGINTOPUNPIN] = [];
|
||||
defaultStorage[Constants.LOCAL_OUTLINE] = {keepExpand: true};
|
||||
defaultStorage[Constants.LOCAL_OUTLINE] = {keepExpand: true, expand: {}};
|
||||
defaultStorage[Constants.LOCAL_FILEPOSITION] = {}; // {id: IScrollAttr}
|
||||
defaultStorage[Constants.LOCAL_DIALOGPOSITION] = {}; // {id: IPosition}
|
||||
defaultStorage[Constants.LOCAL_HISTORY] = {
|
||||
|
|
|
|||
|
|
@ -16,8 +16,9 @@ export class Tree {
|
|||
private ctrlClick: (element: HTMLElement) => void;
|
||||
private toggleClick: (element: Element) => void;
|
||||
private shiftClick: (element: HTMLElement) => void;
|
||||
private altClick: (element: HTMLElement) => void;
|
||||
private altClick: (element: HTMLElement, event?: MouseEvent) => void;
|
||||
private rightClick: (element: HTMLElement, event: MouseEvent) => void;
|
||||
public onToggleChange: () => void;
|
||||
|
||||
constructor(options: {
|
||||
element: HTMLElement,
|
||||
|
|
@ -26,10 +27,11 @@ export class Tree {
|
|||
topExtHTML?: string,
|
||||
click?(element: HTMLElement, event: MouseEvent): void
|
||||
ctrlClick?(element: HTMLElement): void
|
||||
altClick?(element: HTMLElement): void
|
||||
altClick?(element: HTMLElement, event?: MouseEvent): void
|
||||
shiftClick?(element: HTMLElement): void
|
||||
toggleClick?(element: HTMLElement): void
|
||||
rightClick?(element: HTMLElement, event: MouseEvent): void
|
||||
onToggleChange?: () => void
|
||||
}) {
|
||||
this.click = options.click;
|
||||
this.ctrlClick = options.ctrlClick;
|
||||
|
|
@ -37,6 +39,7 @@ export class Tree {
|
|||
this.shiftClick = options.shiftClick;
|
||||
this.rightClick = options.rightClick;
|
||||
this.toggleClick = options.toggleClick;
|
||||
this.onToggleChange = options.onToggleChange;
|
||||
this.element = options.element;
|
||||
this.blockExtHTML = options.blockExtHTML;
|
||||
this.topExtHTML = options.topExtHTML;
|
||||
|
|
@ -204,7 +207,18 @@ data-def-path="${item.defPath}">
|
|||
this.element.addEventListener("contextmenu", (event) => {
|
||||
let target = event.target as HTMLElement;
|
||||
while (target && !target.isEqualNode(this.element)) {
|
||||
if (target.tagName === "LI" && this.rightClick) {
|
||||
if (target.classList.contains("b3-list-item__toggle") && !target.classList.contains("fn__hidden")) {
|
||||
// 右键点击toggle时,展开所有子标题
|
||||
this.expandAllChildren(target.parentElement);
|
||||
this.setCurrent(target.parentElement);
|
||||
// 触发折叠状态变化事件
|
||||
if (this.onToggleChange) {
|
||||
this.onToggleChange();
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
break;
|
||||
} else if (target.tagName === "LI" && this.rightClick) {
|
||||
this.rightClick(target, event);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
|
@ -219,6 +233,10 @@ data-def-path="${item.defPath}">
|
|||
if (target.classList.contains("b3-list-item__toggle") && !target.classList.contains("fn__hidden")) {
|
||||
this.toggleBlocks(target.parentElement);
|
||||
this.setCurrent(target.parentElement);
|
||||
// 触发折叠状态变化事件
|
||||
if (this.onToggleChange) {
|
||||
this.onToggleChange();
|
||||
}
|
||||
event.preventDefault();
|
||||
break;
|
||||
}
|
||||
|
|
@ -237,7 +255,7 @@ data-def-path="${item.defPath}">
|
|||
if (this.ctrlClick && window.siyuan.ctrlIsPressed) {
|
||||
this.ctrlClick(target);
|
||||
} else if (this.altClick && window.siyuan.altIsPressed) {
|
||||
this.altClick(target);
|
||||
this.altClick(target, event);
|
||||
} else if (this.shiftClick && window.siyuan.shiftIsPressed) {
|
||||
this.shiftClick(target);
|
||||
} else if (this.click) {
|
||||
|
|
@ -271,6 +289,86 @@ data-def-path="${item.defPath}">
|
|||
});
|
||||
}
|
||||
|
||||
public expandAllChildren(liElement: Element) {
|
||||
if (!liElement || !liElement.nextElementSibling) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取当前项的子列表
|
||||
const nextElement = liElement.nextElementSibling;
|
||||
if (!nextElement || nextElement.tagName !== "UL") {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查子元素的展开状态,如果所有子元素都已展开,则折叠;否则展开所有
|
||||
const areAllChildrenExpanded = this.areAllChildrenExpanded(nextElement);
|
||||
|
||||
// 确保当前元素保持展开状态
|
||||
const svgElement = liElement.firstElementChild.firstElementChild;
|
||||
if (!svgElement.classList.contains("b3-list-item__arrow--open")) {
|
||||
svgElement.classList.add("b3-list-item__arrow--open");
|
||||
nextElement.classList.remove("fn__none");
|
||||
if (nextElement.nextElementSibling && nextElement.nextElementSibling.tagName === "UL") {
|
||||
nextElement.nextElementSibling.classList.remove("fn__none");
|
||||
}
|
||||
}
|
||||
|
||||
if (areAllChildrenExpanded) {
|
||||
// 折叠所有子元素,但保持当前元素展开
|
||||
this.collapseAllChildren(nextElement);
|
||||
} else {
|
||||
// 展开所有子元素
|
||||
this.expandAllChildrenRecursive(nextElement);
|
||||
}
|
||||
}
|
||||
|
||||
private areAllChildrenExpanded(ulElement: Element): boolean {
|
||||
const childItems = ulElement.querySelectorAll(":scope > li");
|
||||
for (const childLi of childItems) {
|
||||
const arrow = childLi.querySelector(".b3-list-item__arrow");
|
||||
if (arrow && !arrow.classList.contains("b3-list-item__arrow--open")) {
|
||||
return false;
|
||||
}
|
||||
const childUl = childLi.nextElementSibling;
|
||||
if (childUl && childUl.tagName === "UL") {
|
||||
if (!this.areAllChildrenExpanded(childUl)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private expandAllChildrenRecursive(ulElement: Element) {
|
||||
ulElement.classList.remove("fn__none");
|
||||
const childItems = ulElement.querySelectorAll(":scope > li");
|
||||
childItems.forEach(childLi => {
|
||||
const arrow = childLi.querySelector(".b3-list-item__arrow");
|
||||
if (arrow) {
|
||||
arrow.classList.add("b3-list-item__arrow--open");
|
||||
}
|
||||
const childUl = childLi.nextElementSibling;
|
||||
if (childUl && childUl.tagName === "UL") {
|
||||
this.expandAllChildrenRecursive(childUl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private collapseAllChildren(ulElement: Element) {
|
||||
const childItems = ulElement.querySelectorAll(":scope > li");
|
||||
childItems.forEach(childLi => {
|
||||
const arrow = childLi.querySelector(".b3-list-item__arrow");
|
||||
if (arrow) {
|
||||
arrow.classList.remove("b3-list-item__arrow--open");
|
||||
}
|
||||
const childUl = childLi.nextElementSibling;
|
||||
if (childUl && childUl.tagName === "UL") {
|
||||
childUl.classList.add("fn__none");
|
||||
this.collapseAllChildren(childUl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public expandAll() {
|
||||
this.element.querySelectorAll("ul").forEach(item => {
|
||||
if (!item.classList.contains("b3-list")) {
|
||||
|
|
|
|||
|
|
@ -78,6 +78,9 @@ 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/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)
|
||||
|
||||
ginServer.Handle("POST", "/api/account/login", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, login)
|
||||
ginServer.Handle("POST", "/api/account/checkActivationcode", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, checkActivationcode)
|
||||
|
|
|
|||
|
|
@ -180,3 +180,59 @@ func getLocalStorage(c *gin.Context) {
|
|||
data := model.GetLocalStorage()
|
||||
ret.Data = data
|
||||
}
|
||||
|
||||
func getOutlineStorage(c *gin.Context) {
|
||||
ret := gulu.Ret.NewResult()
|
||||
defer c.JSON(http.StatusOK, ret)
|
||||
|
||||
arg, ok := util.JsonArg(c, ret)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
docID := arg["docID"].(string)
|
||||
data, err := model.GetOutlineStorage(docID)
|
||||
if err != nil {
|
||||
ret.Code = -1
|
||||
ret.Msg = err.Error()
|
||||
return
|
||||
}
|
||||
ret.Data = data
|
||||
}
|
||||
|
||||
func setOutlineStorage(c *gin.Context) {
|
||||
ret := gulu.Ret.NewResult()
|
||||
defer c.JSON(http.StatusOK, ret)
|
||||
|
||||
arg, ok := util.JsonArg(c, ret)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
docID := arg["docID"].(string)
|
||||
val := arg["val"].(interface{})
|
||||
err := model.SetOutlineStorage(docID, val)
|
||||
if err != nil {
|
||||
ret.Code = -1
|
||||
ret.Msg = err.Error()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func removeOutlineStorage(c *gin.Context) {
|
||||
ret := gulu.Ret.NewResult()
|
||||
defer c.JSON(http.StatusOK, ret)
|
||||
|
||||
arg, ok := util.JsonArg(c, ret)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
docID := arg["docID"].(string)
|
||||
err := model.RemoveOutlineStorage(docID)
|
||||
if err != nil {
|
||||
ret.Code = -1
|
||||
ret.Msg = err.Error()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,11 @@ type RecentDoc struct {
|
|||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
type OutlineDoc struct {
|
||||
DocID string `json:"docID"`
|
||||
Data map[string]interface{} `json:"data"`
|
||||
}
|
||||
|
||||
var recentDocLock = sync.Mutex{}
|
||||
|
||||
func RemoveRecentDoc(ids []string) {
|
||||
|
|
@ -402,3 +407,124 @@ func getLocalStorage() (ret map[string]interface{}) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
var outlineStorageLock = sync.Mutex{}
|
||||
|
||||
func GetOutlineStorage(docID string) (ret map[string]interface{}, err error) {
|
||||
outlineStorageLock.Lock()
|
||||
defer outlineStorageLock.Unlock()
|
||||
|
||||
ret = map[string]interface{}{}
|
||||
outlineDocs, err := getOutlineDocs()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, doc := range outlineDocs {
|
||||
if doc.DocID == docID {
|
||||
ret = doc.Data
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func SetOutlineStorage(docID string, val interface{}) (err error) {
|
||||
outlineStorageLock.Lock()
|
||||
defer outlineStorageLock.Unlock()
|
||||
|
||||
outlineDoc := &OutlineDoc{
|
||||
DocID: docID,
|
||||
Data: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
if valMap, ok := val.(map[string]interface{}); ok {
|
||||
outlineDoc.Data = valMap
|
||||
}
|
||||
|
||||
outlineDocs, err := getOutlineDocs()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果文档已存在,先移除旧的
|
||||
for i, doc := range outlineDocs {
|
||||
if doc.DocID == docID {
|
||||
outlineDocs = append(outlineDocs[:i], outlineDocs[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 将新的文档信息添加到最前面
|
||||
outlineDocs = append([]*OutlineDoc{outlineDoc}, outlineDocs...)
|
||||
|
||||
// 限制为2000个文档
|
||||
if 2000 < len(outlineDocs) {
|
||||
outlineDocs = outlineDocs[:2000]
|
||||
}
|
||||
|
||||
err = setOutlineDocs(outlineDocs)
|
||||
return
|
||||
}
|
||||
|
||||
func RemoveOutlineStorage(docID string) (err error) {
|
||||
outlineStorageLock.Lock()
|
||||
defer outlineStorageLock.Unlock()
|
||||
|
||||
outlineDocs, err := getOutlineDocs()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for i, doc := range outlineDocs {
|
||||
if doc.DocID == docID {
|
||||
outlineDocs = append(outlineDocs[:i], outlineDocs[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
err = setOutlineDocs(outlineDocs)
|
||||
return
|
||||
}
|
||||
|
||||
func setOutlineDocs(outlineDocs []*OutlineDoc) (err error) {
|
||||
dirPath := filepath.Join(util.DataDir, "storage")
|
||||
if err = os.MkdirAll(dirPath, 0755); err != nil {
|
||||
logging.LogErrorf("create storage [outline] dir failed: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
data, err := gulu.JSON.MarshalJSON(outlineDocs)
|
||||
if err != nil {
|
||||
logging.LogErrorf("marshal storage [outline] failed: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
lsPath := filepath.Join(dirPath, "outline.json")
|
||||
err = filelock.WriteFile(lsPath, data)
|
||||
if err != nil {
|
||||
logging.LogErrorf("write storage [outline] failed: %s", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getOutlineDocs() (ret []*OutlineDoc, err error) {
|
||||
ret = []*OutlineDoc{}
|
||||
dataPath := filepath.Join(util.DataDir, "storage/outline.json")
|
||||
if !filelock.IsExist(dataPath) {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := filelock.ReadFile(dataPath)
|
||||
if err != nil {
|
||||
logging.LogErrorf("read storage [outline] failed: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = gulu.JSON.UnmarshalJSON(data, &ret); err != nil {
|
||||
logging.LogErrorf("unmarshal storage [outline] failed: %s", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue