mirror of
https://github.com/siyuan-note/siyuan.git
synced 2025-12-16 22:50:13 +01:00
* ✨ 实现大纲持久化 - 添加折叠状态变化的实时保存功能 - 在文档更新时恢复折叠状态 - 保存拖拽前的折叠状态并在拖拽后恢复 - 更新本地存储结构以支持折叠状态 * 🎨 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
238 lines
4.8 KiB
Go
238 lines
4.8 KiB
Go
// SiYuan - Refactor your thinking
|
|
// Copyright (c) 2020-present, b3log.org
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package api
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/88250/gulu"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/siyuan-note/siyuan/kernel/model"
|
|
"github.com/siyuan-note/siyuan/kernel/util"
|
|
)
|
|
|
|
func getRecentDocs(c *gin.Context) {
|
|
ret := gulu.Ret.NewResult()
|
|
defer c.JSON(http.StatusOK, ret)
|
|
|
|
data, err := model.GetRecentDocs()
|
|
if err != nil {
|
|
ret.Code = -1
|
|
ret.Msg = err.Error()
|
|
return
|
|
}
|
|
ret.Data = data
|
|
}
|
|
|
|
func removeCriterion(c *gin.Context) {
|
|
ret := gulu.Ret.NewResult()
|
|
defer c.JSON(http.StatusOK, ret)
|
|
|
|
arg, ok := util.JsonArg(c, ret)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
name := arg["name"].(string)
|
|
err := model.RemoveCriterion(name)
|
|
if err != nil {
|
|
ret.Code = -1
|
|
ret.Msg = err.Error()
|
|
return
|
|
}
|
|
}
|
|
|
|
func setCriterion(c *gin.Context) {
|
|
ret := gulu.Ret.NewResult()
|
|
defer c.JSON(http.StatusOK, ret)
|
|
|
|
arg, ok := util.JsonArg(c, ret)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
param, err := gulu.JSON.MarshalJSON(arg["criterion"])
|
|
if err != nil {
|
|
ret.Code = -1
|
|
ret.Msg = err.Error()
|
|
return
|
|
}
|
|
|
|
criterion := &model.Criterion{}
|
|
if err = gulu.JSON.UnmarshalJSON(param, criterion); err != nil {
|
|
ret.Code = -1
|
|
ret.Msg = err.Error()
|
|
return
|
|
}
|
|
|
|
err = model.SetCriterion(criterion)
|
|
if err != nil {
|
|
ret.Code = -1
|
|
ret.Msg = err.Error()
|
|
return
|
|
}
|
|
}
|
|
|
|
func getCriteria(c *gin.Context) {
|
|
ret := gulu.Ret.NewResult()
|
|
defer c.JSON(http.StatusOK, ret)
|
|
|
|
data := model.GetCriteria()
|
|
ret.Data = data
|
|
}
|
|
|
|
func removeLocalStorageVals(c *gin.Context) {
|
|
ret := gulu.Ret.NewResult()
|
|
defer c.JSON(http.StatusOK, ret)
|
|
|
|
arg, ok := util.JsonArg(c, ret)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var keys []string
|
|
keysArg := arg["keys"].([]interface{})
|
|
for _, key := range keysArg {
|
|
keys = append(keys, key.(string))
|
|
}
|
|
|
|
err := model.RemoveLocalStorageVals(keys)
|
|
if err != nil {
|
|
ret.Code = -1
|
|
ret.Msg = err.Error()
|
|
return
|
|
}
|
|
|
|
app := arg["app"].(string)
|
|
evt := util.NewCmdResult("removeLocalStorageVals", 0, util.PushModeBroadcastMainExcludeSelfApp)
|
|
evt.AppId = app
|
|
evt.Data = map[string]interface{}{"keys": keys}
|
|
util.PushEvent(evt)
|
|
}
|
|
|
|
func setLocalStorageVal(c *gin.Context) {
|
|
ret := gulu.Ret.NewResult()
|
|
defer c.JSON(http.StatusOK, ret)
|
|
|
|
arg, ok := util.JsonArg(c, ret)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
key := arg["key"].(string)
|
|
val := arg["val"].(interface{})
|
|
err := model.SetLocalStorageVal(key, val)
|
|
if err != nil {
|
|
ret.Code = -1
|
|
ret.Msg = err.Error()
|
|
return
|
|
}
|
|
|
|
app := arg["app"].(string)
|
|
evt := util.NewCmdResult("setLocalStorageVal", 0, util.PushModeBroadcastMainExcludeSelfApp)
|
|
evt.AppId = app
|
|
evt.Data = map[string]interface{}{"key": key, "val": val}
|
|
util.PushEvent(evt)
|
|
}
|
|
|
|
func setLocalStorage(c *gin.Context) {
|
|
ret := gulu.Ret.NewResult()
|
|
defer c.JSON(http.StatusOK, ret)
|
|
|
|
arg, ok := util.JsonArg(c, ret)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
val := arg["val"].(interface{})
|
|
err := model.SetLocalStorage(val)
|
|
if err != nil {
|
|
ret.Code = -1
|
|
ret.Msg = err.Error()
|
|
return
|
|
}
|
|
|
|
app := arg["app"].(string)
|
|
evt := util.NewCmdResult("setLocalStorage", 0, util.PushModeBroadcastMainExcludeSelfApp)
|
|
evt.AppId = app
|
|
evt.Data = val
|
|
util.PushEvent(evt)
|
|
}
|
|
|
|
func getLocalStorage(c *gin.Context) {
|
|
ret := gulu.Ret.NewResult()
|
|
defer c.JSON(http.StatusOK, ret)
|
|
|
|
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
|
|
}
|
|
}
|