From 1cb8520700303d99a0ea6b95e5c7fd6fcec630b3 Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Fri, 28 Mar 2025 21:32:09 +0800 Subject: [PATCH] :art: Supports local shorthands on Android https://github.com/siyuan-note/siyuan/issues/14414 --- app/src/layout/dock/Files.ts | 4 +- app/src/mobile/dock/MobileFiles.ts | 10 +- kernel/api/filetree.go | 44 +++---- kernel/model/shortcuts.go | 180 +++++++++++++++++++++-------- 4 files changed, 151 insertions(+), 87 deletions(-) diff --git a/app/src/layout/dock/Files.ts b/app/src/layout/dock/Files.ts index b9a0b20e0..37a82520a 100644 --- a/app/src/layout/dock/Files.ts +++ b/app/src/layout/dock/Files.ts @@ -26,7 +26,6 @@ import {isTouchDevice} from "../../util/functions"; import {App} from "../../index"; import {refreshFileTree} from "../../dialog/processSystem"; import {hideTooltip, showTooltip} from "../../dialog/tooltip"; -import * as dayjs from "dayjs"; export class Files extends Model { public element: HTMLElement; @@ -325,8 +324,7 @@ export class Files extends Model { }); } else if (type === "addLocal") { fetchPost("/api/filetree/moveLocalShorthands", { - "notebook": notebookId, - "path": dayjs().format("YYYY-MM-DD HH:mm:ss") + "notebook": notebookId }); this.element.querySelectorAll('[data-type="addLocal"]').forEach(item => { item.remove(); diff --git a/app/src/mobile/dock/MobileFiles.ts b/app/src/mobile/dock/MobileFiles.ts index 0aca112e1..fcec9da8a 100644 --- a/app/src/mobile/dock/MobileFiles.ts +++ b/app/src/mobile/dock/MobileFiles.ts @@ -1,8 +1,4 @@ -import { - hasClosestByClassName, - hasClosestByTag, - hasTopClosestByTag -} from "../../protyle/util/hasClosest"; +import {hasClosestByClassName, hasClosestByTag, hasTopClosestByTag} from "../../protyle/util/hasClosest"; import {escapeHtml} from "../../util/escape"; import {Model} from "../../layout/Model"; import {Constants} from "../../constants"; @@ -20,7 +16,6 @@ import {MenuItem} from "../../menus/Menu"; import {App} from "../../index"; import {refreshFileTree} from "../../dialog/processSystem"; import {setStorageVal} from "../../protyle/util/compatibility"; -import * as dayjs from "dayjs"; export class MobileFiles extends Model { public element: HTMLElement; @@ -220,8 +215,7 @@ export class MobileFiles extends Model { window.siyuan.menus.menu.fullscreen("bottom"); } else if (type === "addLocal") { fetchPost("/api/filetree/moveLocalShorthands", { - "notebook": notebookId, - "path": dayjs().format("YYYY-MM-DD HH:mm:ss") + "notebook": notebookId }); this.element.querySelectorAll('[data-type="addLocal"]').forEach(item => { item.remove(); diff --git a/kernel/api/filetree.go b/kernel/api/filetree.go index 5187c6c84..436c03af1 100644 --- a/kernel/api/filetree.go +++ b/kernel/api/filetree.go @@ -55,43 +55,33 @@ func moveLocalShorthands(c *gin.Context) { parentID = parentIDArg.(string) } - id := ast.NewNodeID() - idArg := arg["id"] - if nil != idArg { - id = idArg.(string) + var hPath string + hPathArg := arg["path"] + if nil != hPathArg { + hPath = arg["path"].(string) + baseName := path.Base(hPath) + dir := path.Dir(hPath) + r, _ := regexp.Compile("\r\n|\r|\n|\u2028|\u2029|\t|/") + baseName = r.ReplaceAllString(baseName, "") + if 512 < utf8.RuneCountInString(baseName) { + baseName = gulu.Str.SubStr(baseName, 512) + } + hPath = path.Join(dir, baseName) } - hPath := arg["path"].(string) - baseName := path.Base(hPath) - dir := path.Dir(hPath) - r, _ := regexp.Compile("\r\n|\r|\n|\u2028|\u2029|\t|/") - baseName = r.ReplaceAllString(baseName, "") - if 512 < utf8.RuneCountInString(baseName) { - baseName = gulu.Str.SubStr(baseName, 512) - } - hPath = path.Join(dir, baseName) - if !strings.HasPrefix(hPath, "/") { - hPath = "/" + hPath - } - - id, err := model.MoveLocalShorthands(notebook, hPath, parentID, id) + ids, err := model.MoveLocalShorthands(notebook, hPath, parentID) if err != nil { ret.Code = -1 ret.Msg = err.Error() return } - if "" == id { - ret.Code = 1 - ret.Msg = "No local shorthand need to move" - return - } - - ret.Data = id model.FlushTxQueue() box := model.Conf.Box(notebook) - b, _ := model.GetBlock(id, nil) - pushCreate(box, b.Path, arg) + for _, id := range ids { + b, _ := model.GetBlock(id, nil) + pushCreate(box, b.Path, arg) + } } func listDocTree(c *gin.Context) { diff --git a/kernel/model/shortcuts.go b/kernel/model/shortcuts.go index 9bcc81fbe..1d8ed3f09 100644 --- a/kernel/model/shortcuts.go +++ b/kernel/model/shortcuts.go @@ -20,14 +20,20 @@ import ( "bytes" "os" "path/filepath" + "slices" + "strconv" "strings" + "time" "github.com/88250/gulu" + "github.com/88250/lute/ast" + "github.com/88250/lute/parse" "github.com/siyuan-note/logging" + "github.com/siyuan-note/siyuan/kernel/treenode" "github.com/siyuan-note/siyuan/kernel/util" ) -func MoveLocalShorthands(boxID, hPath, parentID, id string) (retID string, err error) { +func MoveLocalShorthands(boxID, hPath, parentID string) (retIDs []string, err error) { shorthandsDir := filepath.Join(util.ShortcutsPath, "shorthands") if !gulu.File.IsDir(shorthandsDir) { return @@ -39,69 +45,145 @@ func MoveLocalShorthands(boxID, hPath, parentID, id string) (retID string, err e return } - buff := bytes.Buffer{} - var toRemoves []string assetsDir := filepath.Join(util.DataDir, "assets") for _, entry := range entries { - if entry.IsDir() { - if "assets" == entry.Name() { - assetsEntries, readErr := os.ReadDir(filepath.Join(shorthandsDir, entry.Name())) - if nil != readErr { - logging.LogErrorf("read dir [%s] failed: %s", shorthandsDir, readErr) + if entry.IsDir() && "assets" == entry.Name() { + assetsEntries, readErr := os.ReadDir(filepath.Join(shorthandsDir, entry.Name())) + if nil != readErr { + logging.LogErrorf("read dir [%s] failed: %s", shorthandsDir, readErr) + continue + } + for _, assetEntry := range assetsEntries { + if assetEntry.IsDir() { continue } - for _, assetEntry := range assetsEntries { - if assetEntry.IsDir() { - continue - } - p := filepath.Join(shorthandsDir, entry.Name(), assetEntry.Name()) - assetWritePath := filepath.Join(assetsDir, assetEntry.Name()) - if renameErr := os.Rename(p, assetWritePath); nil != renameErr { - logging.LogErrorf("rename file [%s] to [%s] failed: %s", p, assetWritePath, renameErr) - continue - } + p := filepath.Join(shorthandsDir, entry.Name(), assetEntry.Name()) + assetWritePath := filepath.Join(assetsDir, assetEntry.Name()) + if renameErr := os.Rename(p, assetWritePath); nil != renameErr { + logging.LogErrorf("rename file [%s] to [%s] failed: %s", p, assetWritePath, renameErr) + continue } } } - - if filepath.Ext(entry.Name()) != ".md" { - continue - } - - p := filepath.Join(shorthandsDir, entry.Name()) - data, readErr := os.ReadFile(p) - if nil != readErr { - logging.LogErrorf("read file [%s] failed: %s", p, readErr) - continue - } - - buff.Write(bytes.TrimSpace(data)) - buff.WriteString("\n\n") - toRemoves = append(toRemoves, p) } - clearShorthand := func(toRemoves []string) { - for _, p := range toRemoves { - if removeErr := os.Remove(p); nil != removeErr { - logging.LogErrorf("remove file [%s] failed: %s", p, removeErr) + var toRemoves []string + box := Conf.Box(boxID) + + if "" == hPath { // hPath 为空的话每一个速记对应创建一个文档记录 + for _, entry := range entries { + if filepath.Ext(entry.Name()) != ".md" { + continue } + + p := filepath.Join(shorthandsDir, entry.Name()) + data, readErr := os.ReadFile(p) + if nil != readErr { + logging.LogErrorf("read file [%s] failed: %s", p, readErr) + continue + } + + content := string(bytes.TrimSpace(data)) + if "" == content { + toRemoves = append(toRemoves, p) + continue + } + + t := strings.TrimSuffix(entry.Name(), ".md") + i, parseErr := strconv.ParseInt(t, 10, 64) + if nil != parseErr { + logging.LogErrorf("parse [%s] to int failed: %s", t, parseErr) + continue + } + hPath = "/" + time.UnixMilli(i).Format("2006-01-02 15:04:05") + var retID string + retID, err = CreateWithMarkdown("", boxID, hPath, content, parentID, "", false, "") + if nil != err { + logging.LogErrorf("create doc failed: %s", err) + return + } + + retIDs = append(retIDs, retID) + toRemoves = append(toRemoves, p) + box.addMinSort("/", retID) + } + } else { // 不为空的话将所有速记合并到指定路径的文档中 + if !strings.HasPrefix(hPath, "/") { + hPath = "/" + hPath + } + + buff := bytes.Buffer{} + for _, entry := range entries { + if filepath.Ext(entry.Name()) != ".md" { + continue + } + + p := filepath.Join(shorthandsDir, entry.Name()) + data, readErr := os.ReadFile(p) + if nil != readErr { + logging.LogErrorf("read file [%s] failed: %s", p, readErr) + continue + } + + content := string(bytes.TrimSpace(data)) + if "" == content { + toRemoves = append(toRemoves, p) + continue + } + + buff.WriteString(content) + buff.WriteString("\n\n") + toRemoves = append(toRemoves, p) + } + + if 0 < buff.Len() { + bt := treenode.GetBlockTreeRootByHPath(boxID, hPath) + if nil == bt { + var retID string + retID, err = CreateWithMarkdown("", boxID, hPath, buff.String(), parentID, "", false, "") + if nil != err { + logging.LogErrorf("create doc failed: %s", err) + return + } + retIDs = append(retIDs, retID) + } else { + var tree *parse.Tree + tree, err = loadTreeByBlockTree(bt) + if nil != err { + logging.LogErrorf("load tree by block tree failed: %s", err) + return + } + var last *ast.Node + for c := tree.Root.FirstChild; nil != c; c = c.Next { + last = c + } + + luteEngine := util.NewStdLute() + inputTree := parse.Parse("", buff.Bytes(), luteEngine.ParseOptions) + + if nil != inputTree { + var nodes []*ast.Node + for c := inputTree.Root.FirstChild; nil != c; c = c.Next { + nodes = append(nodes, c) + } + slices.Reverse(nodes) + for _, node := range nodes { + last.InsertAfter(node) + } + } + + indexWriteTreeIndexQueue(tree) + } + } } - content := strings.TrimSpace(buff.String()) - if 1 > len(content) { - clearShorthand(toRemoves) - return + for _, p := range toRemoves { + if removeErr := os.Remove(p); nil != removeErr { + logging.LogErrorf("remove file [%s] failed: %s", p, removeErr) + } } - - retID, err = CreateWithMarkdown("", boxID, hPath, content, parentID, id, false, "") - if nil != err { - logging.LogErrorf("create doc failed: %s", err) - return - } - - clearShorthand(toRemoves) return }