2023-06-24 20:39:55 +08:00
|
|
|
|
// SiYuan - Refactor your thinking
|
2022-05-26 15:18:53 +08:00
|
|
|
|
// 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 model
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"errors"
|
|
|
|
|
|
"fmt"
|
2024-11-21 10:59:29 +08:00
|
|
|
|
"io/fs"
|
2022-05-26 15:18:53 +08:00
|
|
|
|
"os"
|
|
|
|
|
|
"path"
|
|
|
|
|
|
"path/filepath"
|
|
|
|
|
|
"sort"
|
|
|
|
|
|
"strconv"
|
|
|
|
|
|
"strings"
|
2023-11-14 10:02:03 +08:00
|
|
|
|
"sync"
|
2022-05-26 15:18:53 +08:00
|
|
|
|
"time"
|
2022-10-22 01:25:22 +08:00
|
|
|
|
"unicode/utf8"
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
2024-04-24 19:51:15 +08:00
|
|
|
|
"github.com/88250/go-humanize"
|
2022-05-26 15:18:53 +08:00
|
|
|
|
"github.com/88250/gulu"
|
2023-02-10 14:28:10 +08:00
|
|
|
|
"github.com/88250/lute"
|
2022-05-26 15:18:53 +08:00
|
|
|
|
"github.com/88250/lute/ast"
|
|
|
|
|
|
"github.com/88250/lute/html"
|
|
|
|
|
|
"github.com/88250/lute/parse"
|
|
|
|
|
|
util2 "github.com/88250/lute/util"
|
2022-06-15 23:56:47 +08:00
|
|
|
|
"github.com/siyuan-note/filelock"
|
2022-07-17 12:22:32 +08:00
|
|
|
|
"github.com/siyuan-note/logging"
|
2023-05-09 10:08:16 +08:00
|
|
|
|
"github.com/siyuan-note/riff"
|
2024-03-08 20:38:32 +08:00
|
|
|
|
"github.com/siyuan-note/siyuan/kernel/av"
|
2022-05-26 15:18:53 +08:00
|
|
|
|
"github.com/siyuan-note/siyuan/kernel/cache"
|
|
|
|
|
|
"github.com/siyuan-note/siyuan/kernel/filesys"
|
|
|
|
|
|
"github.com/siyuan-note/siyuan/kernel/search"
|
|
|
|
|
|
"github.com/siyuan-note/siyuan/kernel/sql"
|
2023-02-10 14:28:10 +08:00
|
|
|
|
"github.com/siyuan-note/siyuan/kernel/task"
|
2022-05-26 15:18:53 +08:00
|
|
|
|
"github.com/siyuan-note/siyuan/kernel/treenode"
|
|
|
|
|
|
"github.com/siyuan-note/siyuan/kernel/util"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
type File struct {
|
2023-05-08 22:17:52 +08:00
|
|
|
|
Path string `json:"path"`
|
|
|
|
|
|
Name string `json:"name"` // 标题,即 ial["title"]
|
|
|
|
|
|
Icon string `json:"icon"`
|
|
|
|
|
|
Name1 string `json:"name1"` // 命名,即 ial["name"]
|
|
|
|
|
|
Alias string `json:"alias"`
|
|
|
|
|
|
Memo string `json:"memo"`
|
|
|
|
|
|
Bookmark string `json:"bookmark"`
|
|
|
|
|
|
ID string `json:"id"`
|
|
|
|
|
|
Count int `json:"count"`
|
|
|
|
|
|
Size uint64 `json:"size"`
|
|
|
|
|
|
HSize string `json:"hSize"`
|
|
|
|
|
|
Mtime int64 `json:"mtime"`
|
|
|
|
|
|
CTime int64 `json:"ctime"`
|
|
|
|
|
|
HMtime string `json:"hMtime"`
|
|
|
|
|
|
HCtime string `json:"hCtime"`
|
|
|
|
|
|
Sort int `json:"sort"`
|
|
|
|
|
|
SubFileCount int `json:"subFileCount"`
|
2023-09-05 16:42:15 +08:00
|
|
|
|
Hidden bool `json:"hidden"`
|
2023-05-08 22:17:52 +08:00
|
|
|
|
|
|
|
|
|
|
NewFlashcardCount int `json:"newFlashcardCount"`
|
|
|
|
|
|
DueFlashcardCount int `json:"dueFlashcardCount"`
|
|
|
|
|
|
FlashcardCount int `json:"flashcardCount"`
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (box *Box) docFromFileInfo(fileInfo *FileInfo, ial map[string]string) (ret *File) {
|
|
|
|
|
|
ret = &File{}
|
|
|
|
|
|
ret.Path = fileInfo.path
|
|
|
|
|
|
ret.Size = uint64(fileInfo.size)
|
|
|
|
|
|
ret.Name = ial["title"] + ".sy"
|
2025-06-25 13:15:55 +08:00
|
|
|
|
ret.Icon = ial["icon"]
|
2022-05-26 15:18:53 +08:00
|
|
|
|
ret.ID = ial["id"]
|
|
|
|
|
|
ret.Name1 = ial["name"]
|
|
|
|
|
|
ret.Alias = ial["alias"]
|
|
|
|
|
|
ret.Memo = ial["memo"]
|
|
|
|
|
|
ret.Bookmark = ial["bookmark"]
|
|
|
|
|
|
t, _ := time.ParseInLocation("20060102150405", ret.ID[:14], time.Local)
|
|
|
|
|
|
ret.CTime = t.Unix()
|
2024-05-09 19:53:39 +08:00
|
|
|
|
ret.HCtime = t.Format("2006-01-02 15:04:05") + ", " + util.HumanizeTime(t, Conf.Lang)
|
2024-04-24 20:05:42 +08:00
|
|
|
|
ret.HSize = humanize.BytesCustomCeil(ret.Size, 2)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
|
|
|
|
|
mTime := t
|
|
|
|
|
|
if updated := ial["updated"]; "" != updated {
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if updatedTime, err := time.ParseInLocation("20060102150405", updated, time.Local); err == nil {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
mTime = updatedTime
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ret.Mtime = mTime.Unix()
|
2024-05-09 19:53:39 +08:00
|
|
|
|
ret.HMtime = mTime.Format("2006-01-02 15:04:05") + ", " + util.HumanizeTime(mTime, Conf.Lang)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (box *Box) docIAL(p string) (ret map[string]string) {
|
|
|
|
|
|
name := strings.ToLower(filepath.Base(p))
|
|
|
|
|
|
if !strings.HasSuffix(name, ".sy") {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ret = cache.GetDocIAL(p)
|
|
|
|
|
|
if nil != ret {
|
|
|
|
|
|
return ret
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
filePath := filepath.Join(util.DataDir, box.ID, p)
|
2024-10-31 22:56:48 +08:00
|
|
|
|
ret = filesys.DocIAL(filePath)
|
2022-11-25 22:54:26 +08:00
|
|
|
|
if 1 > len(ret) {
|
2024-10-31 22:56:48 +08:00
|
|
|
|
logging.LogWarnf("properties not found in file [%s]", filePath)
|
2022-09-09 17:11:49 +08:00
|
|
|
|
box.moveCorruptedData(filePath)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
cache.PutDocIAL(p, ret)
|
|
|
|
|
|
return ret
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-09-09 17:11:49 +08:00
|
|
|
|
func (box *Box) moveCorruptedData(filePath string) {
|
|
|
|
|
|
base := filepath.Base(filePath)
|
|
|
|
|
|
to := filepath.Join(util.WorkspaceDir, "corrupted", time.Now().Format("2006-01-02-150405"), box.ID, base)
|
2022-09-29 21:52:01 +08:00
|
|
|
|
if copyErr := filelock.Copy(filePath, to); nil != copyErr {
|
2022-09-09 17:11:49 +08:00
|
|
|
|
logging.LogErrorf("copy corrupted data file [%s] failed: %s", filePath, copyErr)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2022-09-29 21:52:01 +08:00
|
|
|
|
if removeErr := filelock.Remove(filePath); nil != removeErr {
|
2022-09-09 17:11:49 +08:00
|
|
|
|
logging.LogErrorf("remove corrupted data file [%s] failed: %s", filePath, removeErr)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
logging.LogWarnf("moved corrupted data file [%s] to [%s]", filePath, to)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-01 18:58:53 +08:00
|
|
|
|
func SearchDocsByKeyword(keyword string, flashcard bool, excludeIDs []string) (ret []map[string]string) {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
ret = []map[string]string{}
|
|
|
|
|
|
|
2023-05-09 10:08:16 +08:00
|
|
|
|
var deck *riff.Deck
|
|
|
|
|
|
var deckBlockIDs []string
|
2023-04-13 19:07:09 +08:00
|
|
|
|
if flashcard {
|
2023-05-09 10:29:07 +08:00
|
|
|
|
deck = Decks[builtinDeckID]
|
2023-04-14 16:36:04 +08:00
|
|
|
|
if nil == deck {
|
2023-04-13 19:07:09 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2023-05-09 10:08:16 +08:00
|
|
|
|
|
|
|
|
|
|
deckBlockIDs = deck.GetBlockIDs()
|
2023-04-13 19:07:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
openedBoxes := Conf.GetOpenedBoxes()
|
|
|
|
|
|
boxes := map[string]*Box{}
|
|
|
|
|
|
for _, box := range openedBoxes {
|
|
|
|
|
|
boxes[box.ID] = box
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-24 23:01:01 +08:00
|
|
|
|
keywords := strings.Fields(keyword)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
var rootBlocks []*sql.Block
|
2024-09-24 23:01:01 +08:00
|
|
|
|
if 0 < len(keywords) {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
for _, box := range boxes {
|
2024-09-26 16:05:36 +08:00
|
|
|
|
if gulu.Str.Contains(box.Name, keywords) {
|
2023-04-13 19:07:09 +08:00
|
|
|
|
if flashcard {
|
2023-05-09 10:08:16 +08:00
|
|
|
|
newFlashcardCount, dueFlashcardCount, flashcardCount := countBoxFlashcard(box.ID, deck, deckBlockIDs)
|
2023-05-08 22:17:52 +08:00
|
|
|
|
if 0 < flashcardCount {
|
|
|
|
|
|
ret = append(ret, map[string]string{"path": "/", "hPath": box.Name + "/", "box": box.ID, "boxIcon": box.Icon, "newFlashcardCount": strconv.Itoa(newFlashcardCount), "dueFlashcardCount": strconv.Itoa(dueFlashcardCount), "flashcardCount": strconv.Itoa(flashcardCount)})
|
2023-04-13 19:07:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
ret = append(ret, map[string]string{"path": "/", "hPath": box.Name + "/", "box": box.ID, "boxIcon": box.Icon})
|
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-24 23:01:01 +08:00
|
|
|
|
var condition string
|
|
|
|
|
|
for i, k := range keywords {
|
|
|
|
|
|
condition += "(hpath LIKE '%" + k + "%'"
|
|
|
|
|
|
namCondition := Conf.Search.NAMFilter(k)
|
|
|
|
|
|
condition += " " + namCondition
|
|
|
|
|
|
condition += ")"
|
|
|
|
|
|
|
|
|
|
|
|
if i < len(keywords)-1 {
|
|
|
|
|
|
condition += " AND "
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-09-24 23:01:01 +08:00
|
|
|
|
|
2025-12-01 18:58:53 +08:00
|
|
|
|
if 0 < len(excludeIDs) {
|
|
|
|
|
|
condition += fmt.Sprintf(" AND root_id NOT IN ('%s')", strings.Join(excludeIDs, "', '"))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-08 10:56:54 +08:00
|
|
|
|
rootBlocks = sql.QueryRootBlockByCondition(condition, Conf.Search.Limit)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
for _, box := range boxes {
|
2023-04-13 19:07:09 +08:00
|
|
|
|
if flashcard {
|
2023-05-09 10:08:16 +08:00
|
|
|
|
newFlashcardCount, dueFlashcardCount, flashcardCount := countBoxFlashcard(box.ID, deck, deckBlockIDs)
|
2023-05-08 22:17:52 +08:00
|
|
|
|
if 0 < flashcardCount {
|
|
|
|
|
|
ret = append(ret, map[string]string{"path": "/", "hPath": box.Name + "/", "box": box.ID, "boxIcon": box.Icon, "newFlashcardCount": strconv.Itoa(newFlashcardCount), "dueFlashcardCount": strconv.Itoa(dueFlashcardCount), "flashcardCount": strconv.Itoa(flashcardCount)})
|
2023-04-13 19:07:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
ret = append(ret, map[string]string{"path": "/", "hPath": box.Name + "/", "box": box.ID, "boxIcon": box.Icon})
|
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-04-13 19:07:09 +08:00
|
|
|
|
for _, rootBlock := range rootBlocks {
|
|
|
|
|
|
b := boxes[rootBlock.Box]
|
2022-05-26 15:18:53 +08:00
|
|
|
|
if nil == b {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
2023-04-13 19:07:09 +08:00
|
|
|
|
hPath := b.Name + rootBlock.HPath
|
|
|
|
|
|
if flashcard {
|
2023-05-09 10:08:16 +08:00
|
|
|
|
newFlashcardCount, dueFlashcardCount, flashcardCount := countTreeFlashcard(rootBlock.ID, deck, deckBlockIDs)
|
2023-05-08 22:17:52 +08:00
|
|
|
|
if 0 < flashcardCount {
|
|
|
|
|
|
ret = append(ret, map[string]string{"path": rootBlock.Path, "hPath": hPath, "box": rootBlock.Box, "boxIcon": b.Icon, "newFlashcardCount": strconv.Itoa(newFlashcardCount), "dueFlashcardCount": strconv.Itoa(dueFlashcardCount), "flashcardCount": strconv.Itoa(flashcardCount)})
|
2023-04-13 19:07:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
ret = append(ret, map[string]string{"path": rootBlock.Path, "hPath": hPath, "box": rootBlock.Box, "boxIcon": b.Icon})
|
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sort.Slice(ret, func(i, j int) bool {
|
|
|
|
|
|
return ret[i]["hPath"] < ret[j]["hPath"]
|
|
|
|
|
|
})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type FileInfo struct {
|
|
|
|
|
|
path string
|
|
|
|
|
|
name string
|
|
|
|
|
|
size int64
|
|
|
|
|
|
isdir bool
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-09-06 10:04:33 +08:00
|
|
|
|
func ListDocTree(boxID, listPath string, sortMode int, flashcard, showHidden bool, maxListCount int) (ret []*File, totals int, err error) {
|
2022-11-25 22:54:26 +08:00
|
|
|
|
//os.MkdirAll("pprof", 0755)
|
|
|
|
|
|
//cpuProfile, _ := os.Create("pprof/cpu_profile_list_doc_tree")
|
|
|
|
|
|
//pprof.StartCPUProfile(cpuProfile)
|
|
|
|
|
|
//defer pprof.StopCPUProfile()
|
|
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
ret = []*File{}
|
|
|
|
|
|
|
2023-05-09 10:08:16 +08:00
|
|
|
|
var deck *riff.Deck
|
|
|
|
|
|
var deckBlockIDs []string
|
2023-04-13 19:07:09 +08:00
|
|
|
|
if flashcard {
|
2023-05-09 10:23:36 +08:00
|
|
|
|
deck = Decks[builtinDeckID]
|
2023-04-14 16:36:04 +08:00
|
|
|
|
if nil == deck {
|
2023-04-13 19:07:09 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2023-05-09 10:08:16 +08:00
|
|
|
|
|
|
|
|
|
|
deckBlockIDs = deck.GetBlockIDs()
|
2023-04-13 19:07:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
box := Conf.Box(boxID)
|
|
|
|
|
|
if nil == box {
|
|
|
|
|
|
return nil, 0, errors.New(Conf.Language(0))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-02-04 16:31:20 +08:00
|
|
|
|
boxConf := box.GetConf()
|
2023-05-02 23:22:40 +08:00
|
|
|
|
|
|
|
|
|
|
if util.SortModeUnassigned == sortMode {
|
|
|
|
|
|
sortMode = Conf.FileTree.Sort
|
|
|
|
|
|
if util.SortModeFileTree != boxConf.SortMode {
|
|
|
|
|
|
sortMode = boxConf.SortMode
|
|
|
|
|
|
}
|
2023-02-04 16:31:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
var files []*FileInfo
|
|
|
|
|
|
start := time.Now()
|
2023-09-06 10:04:33 +08:00
|
|
|
|
files, totals, err = box.Ls(listPath)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
elapsed := time.Now().Sub(start).Milliseconds()
|
|
|
|
|
|
if 100 < elapsed {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogWarnf("ls elapsed [%dms]", elapsed)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
start = time.Now()
|
|
|
|
|
|
boxLocalPath := filepath.Join(util.DataDir, box.ID)
|
|
|
|
|
|
var docs []*File
|
|
|
|
|
|
for _, file := range files {
|
|
|
|
|
|
if file.isdir {
|
2023-01-17 22:16:14 +08:00
|
|
|
|
if !ast.IsNodeIDPattern(file.name) {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
parentDocPath := strings.TrimSuffix(file.path, "/") + ".sy"
|
2023-09-05 15:50:13 +08:00
|
|
|
|
parentDocFile := box.Stat(parentDocPath)
|
|
|
|
|
|
if nil == parentDocFile {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if ial := box.docIAL(parentDocPath); nil != ial {
|
2023-09-05 16:42:15 +08:00
|
|
|
|
if !showHidden && "true" == ial["custom-hidden"] {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-09-05 15:50:13 +08:00
|
|
|
|
doc := box.docFromFileInfo(parentDocFile, ial)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
subFiles, err := os.ReadDir(filepath.Join(boxLocalPath, file.path))
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err == nil {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
for _, subFile := range subFiles {
|
2023-09-06 10:04:33 +08:00
|
|
|
|
subDocFilePath := path.Join(file.path, subFile.Name())
|
|
|
|
|
|
if subIAL := box.docIAL(subDocFilePath); "true" == subIAL["custom-hidden"] {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
if strings.HasSuffix(subFile.Name(), ".sy") {
|
|
|
|
|
|
doc.SubFileCount++
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2023-04-13 19:07:09 +08:00
|
|
|
|
|
|
|
|
|
|
if flashcard {
|
2024-11-29 08:41:43 +08:00
|
|
|
|
rootID := util.GetTreeID(parentDocPath)
|
2023-05-09 10:08:16 +08:00
|
|
|
|
newFlashcardCount, dueFlashcardCount, flashcardCount := countTreeFlashcard(rootID, deck, deckBlockIDs)
|
2023-05-08 22:17:52 +08:00
|
|
|
|
if 0 < flashcardCount {
|
2023-05-08 21:54:51 +08:00
|
|
|
|
doc.NewFlashcardCount = newFlashcardCount
|
|
|
|
|
|
doc.DueFlashcardCount = dueFlashcardCount
|
2023-05-08 22:17:52 +08:00
|
|
|
|
doc.FlashcardCount = flashcardCount
|
2023-04-13 19:07:09 +08:00
|
|
|
|
docs = append(docs, doc)
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
docs = append(docs, doc)
|
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
2023-09-05 15:50:13 +08:00
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
continue
|
2025-10-14 17:07:56 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
if strings.HasSuffix(file.name, ".sy") && !ast.IsNodeIDPattern(strings.TrimSuffix(file.name, ".sy")) {
|
|
|
|
|
|
// 不以块 ID 命名的 .sy 文件不应该被加载到思源中 https://github.com/siyuan-note/siyuan/issues/16089
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
subFolder := filepath.Join(boxLocalPath, strings.TrimSuffix(file.path, ".sy"))
|
|
|
|
|
|
if gulu.File.IsDir(subFolder) {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ial := box.docIAL(file.path); nil != ial {
|
2023-09-05 16:42:15 +08:00
|
|
|
|
if !showHidden && "true" == ial["custom-hidden"] {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
doc := box.docFromFileInfo(file, ial)
|
2023-04-13 19:07:09 +08:00
|
|
|
|
|
|
|
|
|
|
if flashcard {
|
2024-11-29 08:41:43 +08:00
|
|
|
|
rootID := util.GetTreeID(file.path)
|
2023-05-09 10:08:16 +08:00
|
|
|
|
newFlashcardCount, dueFlashcardCount, flashcardCount := countTreeFlashcard(rootID, deck, deckBlockIDs)
|
2023-05-08 22:17:52 +08:00
|
|
|
|
if 0 < flashcardCount {
|
2023-05-08 21:54:51 +08:00
|
|
|
|
doc.NewFlashcardCount = newFlashcardCount
|
|
|
|
|
|
doc.DueFlashcardCount = dueFlashcardCount
|
2023-05-08 22:17:52 +08:00
|
|
|
|
doc.FlashcardCount = flashcardCount
|
2023-04-13 19:07:09 +08:00
|
|
|
|
docs = append(docs, doc)
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
docs = append(docs, doc)
|
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
elapsed = time.Now().Sub(start).Milliseconds()
|
|
|
|
|
|
if 500 < elapsed {
|
2025-01-16 23:03:36 +08:00
|
|
|
|
logging.LogWarnf("list doc tree [%s] build docs [%d] elapsed [%dms]", listPath, len(docs), elapsed)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
start = time.Now()
|
|
|
|
|
|
refCount := sql.QueryRootBlockRefCount()
|
|
|
|
|
|
for _, doc := range docs {
|
|
|
|
|
|
if count := refCount[doc.ID]; 0 < count {
|
|
|
|
|
|
doc.Count = count
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
elapsed = time.Now().Sub(start).Milliseconds()
|
|
|
|
|
|
if 500 < elapsed {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogWarnf("query root block ref count elapsed [%dms]", elapsed)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
start = time.Now()
|
|
|
|
|
|
switch sortMode {
|
|
|
|
|
|
case util.SortModeNameASC:
|
|
|
|
|
|
sort.Slice(docs, func(i, j int) bool {
|
2025-05-07 17:34:04 +08:00
|
|
|
|
return util.PinYinCompare4FileTree(docs[i].Name, docs[j].Name)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
})
|
|
|
|
|
|
case util.SortModeNameDESC:
|
|
|
|
|
|
sort.Slice(docs, func(i, j int) bool {
|
2025-05-07 17:34:04 +08:00
|
|
|
|
return util.PinYinCompare4FileTree(docs[j].Name, docs[i].Name)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
})
|
|
|
|
|
|
case util.SortModeUpdatedASC:
|
|
|
|
|
|
sort.Slice(docs, func(i, j int) bool { return docs[i].Mtime < docs[j].Mtime })
|
|
|
|
|
|
case util.SortModeUpdatedDESC:
|
|
|
|
|
|
sort.Slice(docs, func(i, j int) bool { return docs[i].Mtime > docs[j].Mtime })
|
|
|
|
|
|
case util.SortModeAlphanumASC:
|
|
|
|
|
|
sort.Slice(docs, func(i, j int) bool {
|
2024-09-14 22:07:09 +08:00
|
|
|
|
return util.NaturalCompare(docs[i].Name, docs[j].Name)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
})
|
|
|
|
|
|
case util.SortModeAlphanumDESC:
|
|
|
|
|
|
sort.Slice(docs, func(i, j int) bool {
|
2024-09-14 22:07:09 +08:00
|
|
|
|
return util.NaturalCompare(docs[j].Name, docs[i].Name)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
})
|
|
|
|
|
|
case util.SortModeCustom:
|
|
|
|
|
|
fileTreeFiles := docs
|
|
|
|
|
|
box.fillSort(&fileTreeFiles)
|
|
|
|
|
|
sort.Slice(fileTreeFiles, func(i, j int) bool {
|
|
|
|
|
|
if fileTreeFiles[i].Sort == fileTreeFiles[j].Sort {
|
|
|
|
|
|
return util.TimeFromID(fileTreeFiles[i].ID) > util.TimeFromID(fileTreeFiles[j].ID)
|
|
|
|
|
|
}
|
|
|
|
|
|
return fileTreeFiles[i].Sort < fileTreeFiles[j].Sort
|
|
|
|
|
|
})
|
|
|
|
|
|
ret = append(ret, fileTreeFiles...)
|
2023-07-29 09:43:21 +08:00
|
|
|
|
totals = len(ret)
|
2023-04-14 12:00:11 +08:00
|
|
|
|
if maxListCount < len(ret) {
|
|
|
|
|
|
ret = ret[:maxListCount]
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
ret = ret[:]
|
|
|
|
|
|
return
|
|
|
|
|
|
case util.SortModeRefCountASC:
|
|
|
|
|
|
sort.Slice(docs, func(i, j int) bool { return docs[i].Count < docs[j].Count })
|
|
|
|
|
|
case util.SortModeRefCountDESC:
|
|
|
|
|
|
sort.Slice(docs, func(i, j int) bool { return docs[i].Count > docs[j].Count })
|
|
|
|
|
|
case util.SortModeCreatedASC:
|
|
|
|
|
|
sort.Slice(docs, func(i, j int) bool { return docs[i].CTime < docs[j].CTime })
|
|
|
|
|
|
case util.SortModeCreatedDESC:
|
|
|
|
|
|
sort.Slice(docs, func(i, j int) bool { return docs[i].CTime > docs[j].CTime })
|
2022-09-30 21:52:01 +08:00
|
|
|
|
case util.SortModeSizeASC:
|
|
|
|
|
|
sort.Slice(docs, func(i, j int) bool { return docs[i].Size < docs[j].Size })
|
|
|
|
|
|
case util.SortModeSizeDESC:
|
|
|
|
|
|
sort.Slice(docs, func(i, j int) bool { return docs[i].Size > docs[j].Size })
|
2022-09-30 22:08:11 +08:00
|
|
|
|
case util.SortModeSubDocCountASC:
|
|
|
|
|
|
sort.Slice(docs, func(i, j int) bool { return docs[i].SubFileCount < docs[j].SubFileCount })
|
|
|
|
|
|
case util.SortModeSubDocCountDESC:
|
|
|
|
|
|
sort.Slice(docs, func(i, j int) bool { return docs[i].SubFileCount > docs[j].SubFileCount })
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if util.SortModeCustom != sortMode {
|
|
|
|
|
|
ret = append(ret, docs...)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-07-29 09:43:21 +08:00
|
|
|
|
totals = len(ret)
|
2023-04-14 12:00:11 +08:00
|
|
|
|
if maxListCount < len(ret) {
|
|
|
|
|
|
ret = ret[:maxListCount]
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
ret = ret[:]
|
|
|
|
|
|
|
|
|
|
|
|
elapsed = time.Now().Sub(start).Milliseconds()
|
|
|
|
|
|
if 200 < elapsed {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogInfof("sort docs elapsed [%dms]", elapsed)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-01-12 01:07:55 +08:00
|
|
|
|
func GetDoc(startID, endID, id string, index int, query string, queryTypes map[string]bool, queryMethod, mode int, size int, isBacklink bool, originalRefBlockIDs map[string]string, highlight bool) (
|
2024-12-07 11:54:30 +08:00
|
|
|
|
blockCount int, dom, parentID, parent2ID, rootID, typ string, eof, scroll bool, boxID, docPath string, isBacklinkExpand bool, keywords []string, err error) {
|
2023-01-27 21:14:09 +08:00
|
|
|
|
//os.MkdirAll("pprof", 0755)
|
|
|
|
|
|
//cpuProfile, _ := os.Create("pprof/GetDoc")
|
|
|
|
|
|
//pprof.StartCPUProfile(cpuProfile)
|
|
|
|
|
|
//defer pprof.StopCPUProfile()
|
|
|
|
|
|
|
2024-10-22 19:20:44 +08:00
|
|
|
|
FlushTxQueue() // 写入数据时阻塞,避免获取到的数据不一致
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
|
|
|
|
|
inputIndex := index
|
2024-03-10 23:27:13 +08:00
|
|
|
|
tree, err := LoadTreeByBlockID(id)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
if ErrBlockNotFound == err {
|
|
|
|
|
|
if 0 == mode {
|
|
|
|
|
|
err = ErrTreeNotFound // 初始化打开文档时如果找不到则关闭编辑器
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if nil == tree {
|
|
|
|
|
|
err = ErrBlockNotFound
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
luteEngine := NewLute()
|
|
|
|
|
|
node := treenode.GetNodeInTree(tree, id)
|
|
|
|
|
|
if nil == node {
|
2023-08-23 10:18:11 +08:00
|
|
|
|
// Unable to open the doc when the block pointed by the scroll position does not exist https://github.com/siyuan-note/siyuan/issues/9030
|
|
|
|
|
|
node = treenode.GetNodeInTree(tree, tree.Root.ID)
|
|
|
|
|
|
if nil == node {
|
|
|
|
|
|
err = ErrBlockNotFound
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
located := false
|
|
|
|
|
|
isDoc := ast.NodeDocument == node.Type
|
|
|
|
|
|
isHeading := ast.NodeHeading == node.Type
|
2022-12-24 12:05:55 +08:00
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
boxID = node.Box
|
|
|
|
|
|
docPath = node.Path
|
|
|
|
|
|
if isDoc {
|
|
|
|
|
|
if 4 == mode { // 加载文档末尾
|
|
|
|
|
|
node = node.LastChild
|
|
|
|
|
|
located = true
|
|
|
|
|
|
// 重新计算 index
|
|
|
|
|
|
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
|
|
|
|
|
|
if !entering {
|
|
|
|
|
|
return ast.WalkContinue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
index++
|
|
|
|
|
|
return ast.WalkContinue
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
node = node.FirstChild
|
|
|
|
|
|
}
|
|
|
|
|
|
typ = ast.NodeDocument.String()
|
|
|
|
|
|
idx := 0
|
|
|
|
|
|
if 0 < index {
|
|
|
|
|
|
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
|
|
|
|
|
|
if !entering || !n.IsChildBlockOf(tree.Root, 1) {
|
|
|
|
|
|
return ast.WalkContinue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
idx++
|
|
|
|
|
|
if index == idx {
|
|
|
|
|
|
node = n.DocChild()
|
|
|
|
|
|
if "1" == node.IALAttr("heading-fold") {
|
|
|
|
|
|
// 加载到折叠标题下方块的话需要回溯到上方标题块
|
|
|
|
|
|
for h := node.Previous; nil != h; h = h.Previous {
|
|
|
|
|
|
if "1" == h.IALAttr("fold") {
|
|
|
|
|
|
node = h
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
located = true
|
|
|
|
|
|
return ast.WalkStop
|
|
|
|
|
|
}
|
|
|
|
|
|
return ast.WalkContinue
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if 0 == index && 0 != mode {
|
|
|
|
|
|
// 非文档且没有指定 index 时需要计算 index
|
|
|
|
|
|
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
|
|
|
|
|
|
if !entering {
|
|
|
|
|
|
return ast.WalkContinue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
index++
|
|
|
|
|
|
if id == n.ID {
|
|
|
|
|
|
node = n.DocChild()
|
|
|
|
|
|
located = true
|
|
|
|
|
|
return ast.WalkStop
|
|
|
|
|
|
}
|
|
|
|
|
|
return ast.WalkContinue
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if 1 < index && !located {
|
|
|
|
|
|
count := 0
|
|
|
|
|
|
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
|
|
|
|
|
|
if !entering {
|
|
|
|
|
|
return ast.WalkContinue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
count++
|
|
|
|
|
|
if index == count {
|
|
|
|
|
|
node = n.DocChild()
|
|
|
|
|
|
return ast.WalkStop
|
|
|
|
|
|
}
|
|
|
|
|
|
return ast.WalkContinue
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
blockCount = tree.DocBlockCount()
|
|
|
|
|
|
if ast.NodeDocument == node.Type {
|
|
|
|
|
|
parentID = node.ID
|
|
|
|
|
|
parent2ID = parentID
|
|
|
|
|
|
} else {
|
|
|
|
|
|
parentID = node.Parent.ID
|
|
|
|
|
|
parent2ID = parentID
|
|
|
|
|
|
tmp := node
|
|
|
|
|
|
if ast.NodeListItem == node.Type {
|
|
|
|
|
|
// 列表项聚焦返回和面包屑保持一致 https://github.com/siyuan-note/siyuan/issues/4914
|
|
|
|
|
|
tmp = node.Parent
|
|
|
|
|
|
}
|
|
|
|
|
|
if headingParent := treenode.HeadingParent(tmp); nil != headingParent {
|
|
|
|
|
|
parent2ID = headingParent.ID
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
rootID = tree.Root.ID
|
|
|
|
|
|
if !isDoc {
|
|
|
|
|
|
typ = node.Type.String()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-03-18 10:38:22 +08:00
|
|
|
|
// 判断是否需要显示动态加载滚动条 https://github.com/siyuan-note/siyuan/issues/7693
|
|
|
|
|
|
childCount := 0
|
|
|
|
|
|
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
|
|
|
|
|
|
if !entering {
|
|
|
|
|
|
return ast.WalkContinue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if 1 > childCount {
|
|
|
|
|
|
childCount = 1
|
|
|
|
|
|
} else {
|
|
|
|
|
|
childCount += treenode.CountBlockNodes(n)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-04-12 23:26:43 +08:00
|
|
|
|
if childCount > Conf.Editor.DynamicLoadBlocks {
|
2023-03-18 10:38:22 +08:00
|
|
|
|
scroll = true
|
|
|
|
|
|
return ast.WalkStop
|
|
|
|
|
|
}
|
|
|
|
|
|
return ast.WalkContinue
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2022-08-06 00:00:23 +08:00
|
|
|
|
var nodes []*ast.Node
|
2022-12-24 12:05:55 +08:00
|
|
|
|
if isBacklink {
|
|
|
|
|
|
// 引用计数浮窗请求,需要按照反链逻辑组装 https://github.com/siyuan-note/siyuan/issues/6853
|
2025-01-11 10:46:35 +08:00
|
|
|
|
nodes, isBacklinkExpand = getBacklinkRenderNodes(node, originalRefBlockIDs)
|
2022-12-24 12:05:55 +08:00
|
|
|
|
} else {
|
2023-05-17 12:13:50 +08:00
|
|
|
|
// 如果同时存在 startID 和 endID,并且是动态加载的情况,则只加载 startID 和 endID 之间的块 [startID, endID]
|
|
|
|
|
|
if "" != startID && "" != endID && scroll {
|
2022-12-24 12:05:55 +08:00
|
|
|
|
nodes, eof = loadNodesByStartEnd(tree, startID, endID)
|
|
|
|
|
|
if 1 > len(nodes) {
|
|
|
|
|
|
// 按 mode 加载兜底
|
|
|
|
|
|
nodes, eof = loadNodesByMode(node, inputIndex, mode, size, isDoc, isHeading)
|
2023-09-18 10:05:15 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// 文档块没有指定 index 时需要计算 index,否则初次打开文档时 node-index 会为 0,导致首次 Ctrl+Home 无法回到顶部
|
|
|
|
|
|
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
|
|
|
|
|
|
if !entering {
|
|
|
|
|
|
return ast.WalkContinue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
index++
|
|
|
|
|
|
if nodes[0].ID == n.ID {
|
|
|
|
|
|
return ast.WalkStop
|
|
|
|
|
|
}
|
|
|
|
|
|
return ast.WalkContinue
|
|
|
|
|
|
})
|
2022-12-24 12:05:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
2022-08-06 00:00:23 +08:00
|
|
|
|
nodes, eof = loadNodesByMode(node, inputIndex, mode, size, isDoc, isHeading)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
2022-10-26 09:51:09 +08:00
|
|
|
|
refCount := sql.QueryRootChildrenRefCount(rootID)
|
2023-02-16 13:14:15 +08:00
|
|
|
|
virtualBlockRefKeywords := getBlockVirtualRefKeywords(tree.Root)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
2022-09-15 15:38:09 +08:00
|
|
|
|
subTree := &parse.Tree{ID: rootID, Root: &ast.Node{Type: ast.NodeDocument}, Marks: tree.Marks}
|
2023-05-19 10:18:25 +08:00
|
|
|
|
|
2024-12-08 11:28:47 +08:00
|
|
|
|
query = filterQueryInvisibleChars(query)
|
2024-09-21 17:29:01 +08:00
|
|
|
|
if "" != query && (0 == queryMethod || 1 == queryMethod || 3 == queryMethod) { // 只有关键字、查询语法和正则表达式搜索支持高亮
|
2023-05-19 10:18:25 +08:00
|
|
|
|
typeFilter := buildTypeFilter(queryTypes)
|
2024-12-11 10:05:14 +08:00
|
|
|
|
switch queryMethod {
|
|
|
|
|
|
case 0:
|
2024-12-31 21:35:30 +08:00
|
|
|
|
query = stringQuery(query)
|
|
|
|
|
|
keywords = highlightByFTS(query, typeFilter, rootID)
|
2024-12-11 10:05:14 +08:00
|
|
|
|
case 1:
|
2024-09-21 17:29:01 +08:00
|
|
|
|
keywords = highlightByFTS(query, typeFilter, rootID)
|
2024-12-11 10:05:14 +08:00
|
|
|
|
case 3:
|
2024-09-21 17:29:01 +08:00
|
|
|
|
keywords = highlightByRegexp(query, typeFilter, rootID)
|
|
|
|
|
|
}
|
2023-05-19 10:18:25 +08:00
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
2024-12-15 11:54:09 +08:00
|
|
|
|
existKeywords := 0 < len(keywords)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
for _, n := range nodes {
|
|
|
|
|
|
var unlinks []*ast.Node
|
|
|
|
|
|
ast.Walk(n, func(n *ast.Node, entering bool) ast.WalkStatus {
|
|
|
|
|
|
if !entering {
|
|
|
|
|
|
return ast.WalkContinue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if "1" == n.IALAttr("heading-fold") {
|
2023-11-10 09:07:21 +08:00
|
|
|
|
// 折叠标题下被引用的块无法悬浮查看
|
|
|
|
|
|
// The referenced block under the folded heading cannot be hovered to view https://github.com/siyuan-note/siyuan/issues/9582
|
2023-12-13 16:24:52 +08:00
|
|
|
|
if (0 != mode && id != n.ID) || isDoc {
|
2023-11-10 09:07:21 +08:00
|
|
|
|
unlinks = append(unlinks, n)
|
|
|
|
|
|
return ast.WalkContinue
|
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
2024-03-08 20:38:32 +08:00
|
|
|
|
|
|
|
|
|
|
if avs := n.IALAttr(av.NodeAttrNameAvs); "" != avs {
|
2024-03-08 21:17:38 +08:00
|
|
|
|
// 填充属性视图角标 Display the database title on the block superscript https://github.com/siyuan-note/siyuan/issues/10545
|
2024-03-08 23:12:33 +08:00
|
|
|
|
avNames := getAvNames(n.IALAttr(av.NodeAttrNameAvs))
|
|
|
|
|
|
if "" != avNames {
|
2024-03-10 23:00:45 +08:00
|
|
|
|
n.SetIALAttr(av.NodeAttrViewNames, avNames)
|
2024-03-08 20:38:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
|
|
|
|
|
if "" != n.ID {
|
|
|
|
|
|
// 填充块引计数
|
|
|
|
|
|
if cnt := refCount[n.ID]; 0 < cnt {
|
|
|
|
|
|
n.SetIALAttr("refcount", strconv.Itoa(cnt))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-12-15 11:54:09 +08:00
|
|
|
|
if highlight && existKeywords {
|
2022-12-25 11:06:23 +08:00
|
|
|
|
hitBlock := false
|
|
|
|
|
|
for p := n.Parent; nil != p; p = p.Parent {
|
|
|
|
|
|
if p.ID == id {
|
|
|
|
|
|
hitBlock = true
|
|
|
|
|
|
break
|
2022-08-14 11:46:50 +08:00
|
|
|
|
}
|
2022-12-25 11:06:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
if hitBlock {
|
2023-08-03 12:43:59 +08:00
|
|
|
|
if ast.NodeCodeBlockCode == n.Type && !treenode.IsChartCodeBlockCode(n) {
|
|
|
|
|
|
// 支持代码块搜索定位 https://github.com/siyuan-note/siyuan/issues/5520
|
|
|
|
|
|
code := string(n.Tokens)
|
|
|
|
|
|
markedCode := search.EncloseHighlighting(code, keywords, search.SearchMarkLeft, search.SearchMarkRight, Conf.Search.CaseSensitive, false)
|
|
|
|
|
|
if code != markedCode {
|
|
|
|
|
|
n.Tokens = gulu.Str.ToBytes(markedCode)
|
|
|
|
|
|
return ast.WalkContinue
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if markReplaceSpan(n, &unlinks, keywords, search.MarkDataType, luteEngine) {
|
2022-12-25 11:06:23 +08:00
|
|
|
|
return ast.WalkContinue
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-12-25 11:06:23 +08:00
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
2024-12-15 11:54:09 +08:00
|
|
|
|
if existKeywords && id == n.ID {
|
|
|
|
|
|
inlines := n.ChildrenByType(ast.NodeTextMark)
|
|
|
|
|
|
for _, inline := range inlines {
|
|
|
|
|
|
if inline.IsTextMarkType("inline-memo") && util.ContainsSubStr(inline.TextMarkInlineMemoContent, keywords) {
|
|
|
|
|
|
// 支持行级备注搜索定位 https://github.com/siyuan-note/siyuan/issues/13465
|
|
|
|
|
|
keywords = append(keywords, inline.TextMarkTextContent)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-12-25 11:06:23 +08:00
|
|
|
|
if processVirtualRef(n, &unlinks, virtualBlockRefKeywords, refCount, luteEngine) {
|
|
|
|
|
|
return ast.WalkContinue
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
return ast.WalkContinue
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
for _, unlink := range unlinks {
|
|
|
|
|
|
unlink.Unlink()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
subTree.Root.AppendChild(n)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
luteEngine.RenderOptions.NodeIndexStart = index
|
|
|
|
|
|
dom = luteEngine.Tree2BlockDOM(subTree, luteEngine.RenderOptions)
|
2022-12-10 22:46:24 +08:00
|
|
|
|
|
2024-12-08 11:28:47 +08:00
|
|
|
|
if 1 > len(keywords) {
|
|
|
|
|
|
keywords = []string{}
|
|
|
|
|
|
}
|
|
|
|
|
|
for i, keyword := range keywords {
|
|
|
|
|
|
keyword = strings.TrimPrefix(keyword, "#")
|
|
|
|
|
|
keyword = strings.TrimSuffix(keyword, "#")
|
|
|
|
|
|
keywords[i] = keyword
|
|
|
|
|
|
}
|
2024-12-15 11:54:09 +08:00
|
|
|
|
keywords = gulu.Str.RemoveDuplicatedElem(keywords)
|
2024-12-08 11:28:47 +08:00
|
|
|
|
|
2024-06-23 21:52:04 +08:00
|
|
|
|
go setRecentDocByTree(tree)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-08-06 00:00:23 +08:00
|
|
|
|
func loadNodesByStartEnd(tree *parse.Tree, startID, endID string) (nodes []*ast.Node, eof bool) {
|
|
|
|
|
|
node := treenode.GetNodeInTree(tree, startID)
|
|
|
|
|
|
if nil == node {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
nodes = append(nodes, node)
|
|
|
|
|
|
for n := node.Next; nil != n; n = n.Next {
|
2022-08-15 10:07:08 +08:00
|
|
|
|
if treenode.IsInFoldedHeading(n, nil) {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
2022-08-12 00:06:25 +08:00
|
|
|
|
nodes = append(nodes, n)
|
|
|
|
|
|
|
2022-08-06 00:00:23 +08:00
|
|
|
|
if n.ID == endID {
|
2022-08-12 00:06:25 +08:00
|
|
|
|
if next := n.Next; nil == next {
|
2022-08-06 00:00:23 +08:00
|
|
|
|
eof = true
|
|
|
|
|
|
} else {
|
|
|
|
|
|
eof = util2.IsDocIAL(n.Tokens) || util2.IsDocIAL(next.Tokens)
|
|
|
|
|
|
}
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
2025-07-26 21:24:28 +08:00
|
|
|
|
|
|
|
|
|
|
if len(nodes) >= Conf.Editor.DynamicLoadBlocks {
|
|
|
|
|
|
// 如果加载到指定数量的块则停止加载
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
2022-08-06 00:00:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
func loadNodesByMode(node *ast.Node, inputIndex, mode, size int, isDoc, isHeading bool) (nodes []*ast.Node, eof bool) {
|
2022-06-27 16:16:47 +08:00
|
|
|
|
if 2 == mode /* 向下 */ {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
next := node.Next
|
2022-06-21 11:19:45 +08:00
|
|
|
|
if ast.NodeHeading == node.Type && "1" == node.IALAttr("fold") {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
// 标题展开时进行动态加载导致重复内容 https://github.com/siyuan-note/siyuan/issues/4671
|
|
|
|
|
|
// 这里要考虑折叠标题是最后一个块的情况
|
2022-06-19 17:01:21 +08:00
|
|
|
|
if children := treenode.HeadingChildren(node); 0 < len(children) {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
next = children[len(children)-1].Next
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if nil == next {
|
|
|
|
|
|
eof = true
|
|
|
|
|
|
} else {
|
|
|
|
|
|
eof = util2.IsDocIAL(node.Tokens) || util2.IsDocIAL(next.Tokens)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
count := 0
|
|
|
|
|
|
switch mode {
|
|
|
|
|
|
case 0: // 仅加载当前 ID
|
|
|
|
|
|
nodes = append(nodes, node)
|
|
|
|
|
|
if isDoc {
|
|
|
|
|
|
for n := node.Next; nil != n; n = n.Next {
|
2022-06-19 17:01:21 +08:00
|
|
|
|
if treenode.IsInFoldedHeading(n, nil) {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
nodes = append(nodes, n)
|
|
|
|
|
|
if 1 > count {
|
|
|
|
|
|
count++
|
|
|
|
|
|
} else {
|
|
|
|
|
|
count += treenode.CountBlockNodes(n)
|
|
|
|
|
|
}
|
|
|
|
|
|
if size < count {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if isHeading {
|
|
|
|
|
|
level := node.HeadingLevel
|
|
|
|
|
|
for n := node.Next; nil != n; n = n.Next {
|
2022-06-19 17:01:21 +08:00
|
|
|
|
if treenode.IsInFoldedHeading(n, node) {
|
2022-06-07 19:41:25 +08:00
|
|
|
|
// 大纲点击折叠标题跳转聚焦 https://github.com/siyuan-note/siyuan/issues/4920
|
|
|
|
|
|
// 多级标题折叠后上级块引浮窗中未折叠 https://github.com/siyuan-note/siyuan/issues/4997
|
2022-05-26 15:18:53 +08:00
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if ast.NodeHeading == n.Type {
|
|
|
|
|
|
if n.HeadingLevel <= level {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
nodes = append(nodes, n)
|
|
|
|
|
|
count++
|
|
|
|
|
|
if size < count {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
case 4: // Ctrl+End 跳转到末尾后向上加载
|
|
|
|
|
|
for n := node; nil != n; n = n.Previous {
|
2022-06-19 17:01:21 +08:00
|
|
|
|
if treenode.IsInFoldedHeading(n, nil) {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
nodes = append([]*ast.Node{n}, nodes...)
|
|
|
|
|
|
if 1 > count {
|
|
|
|
|
|
count++
|
|
|
|
|
|
} else {
|
|
|
|
|
|
count += treenode.CountBlockNodes(n)
|
|
|
|
|
|
}
|
|
|
|
|
|
if size < count {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
eof = true
|
|
|
|
|
|
case 1: // 向上加载
|
|
|
|
|
|
for n := node.Previous; /* 从上一个节点开始加载 */ nil != n; n = n.Previous {
|
2022-06-19 17:01:21 +08:00
|
|
|
|
if treenode.IsInFoldedHeading(n, nil) {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
nodes = append([]*ast.Node{n}, nodes...)
|
|
|
|
|
|
if 1 > count {
|
|
|
|
|
|
count++
|
|
|
|
|
|
} else {
|
|
|
|
|
|
count += treenode.CountBlockNodes(n)
|
|
|
|
|
|
}
|
|
|
|
|
|
if size < count {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
eof = nil == node.Previous
|
|
|
|
|
|
case 2: // 向下加载
|
|
|
|
|
|
for n := node.Next; /* 从下一个节点开始加载 */ nil != n; n = n.Next {
|
2022-06-19 17:01:21 +08:00
|
|
|
|
if treenode.IsInFoldedHeading(n, node) {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
nodes = append(nodes, n)
|
|
|
|
|
|
if 1 > count {
|
|
|
|
|
|
count++
|
|
|
|
|
|
} else {
|
|
|
|
|
|
count += treenode.CountBlockNodes(n)
|
|
|
|
|
|
}
|
|
|
|
|
|
if size < count {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
case 3: // 上下都加载
|
|
|
|
|
|
for n := node; nil != n; n = n.Previous {
|
2022-06-19 17:01:21 +08:00
|
|
|
|
if treenode.IsInFoldedHeading(n, nil) {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
nodes = append([]*ast.Node{n}, nodes...)
|
|
|
|
|
|
if 1 > count {
|
|
|
|
|
|
count++
|
|
|
|
|
|
} else {
|
|
|
|
|
|
count += treenode.CountBlockNodes(n)
|
|
|
|
|
|
}
|
|
|
|
|
|
if 0 < inputIndex {
|
|
|
|
|
|
if 1 < count {
|
|
|
|
|
|
break // 滑块指示器加载
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if size < count {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if size/2 < count {
|
|
|
|
|
|
size = size / 2
|
|
|
|
|
|
} else {
|
|
|
|
|
|
size = size - count
|
|
|
|
|
|
}
|
|
|
|
|
|
count = 0
|
|
|
|
|
|
for n := node.Next; nil != n; n = n.Next {
|
2022-06-19 17:01:21 +08:00
|
|
|
|
if treenode.IsInFoldedHeading(n, nil) {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
nodes = append(nodes, n)
|
|
|
|
|
|
if 1 > count {
|
|
|
|
|
|
count++
|
|
|
|
|
|
} else {
|
|
|
|
|
|
count += treenode.CountBlockNodes(n)
|
|
|
|
|
|
}
|
|
|
|
|
|
if 0 < inputIndex {
|
|
|
|
|
|
if size < count {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if size < count {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-04-11 21:54:34 +08:00
|
|
|
|
func writeTreeUpsertQueue(tree *parse.Tree) (err error) {
|
2024-09-28 17:38:50 +08:00
|
|
|
|
size, err := filesys.WriteTree(tree)
|
|
|
|
|
|
if err != nil {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
sql.UpsertTreeQueue(tree)
|
2025-09-10 21:39:14 +08:00
|
|
|
|
refreshDocInfoWithSize(tree, size)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-12-13 20:04:16 +08:00
|
|
|
|
func indexWriteTreeIndexQueue(tree *parse.Tree) (err error) {
|
|
|
|
|
|
treenode.IndexBlockTree(tree)
|
|
|
|
|
|
_, err = filesys.WriteTree(tree)
|
2024-09-28 17:38:50 +08:00
|
|
|
|
if err != nil {
|
2024-04-11 21:54:34 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
sql.IndexTreeQueue(tree)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func indexWriteTreeUpsertQueue(tree *parse.Tree) (err error) {
|
2024-06-20 22:53:27 +08:00
|
|
|
|
treenode.UpsertBlockTree(tree)
|
2024-04-11 21:54:34 +08:00
|
|
|
|
return writeTreeUpsertQueue(tree)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-02-21 13:00:27 +08:00
|
|
|
|
func renameWriteJSONQueue(tree *parse.Tree) (err error) {
|
2024-09-28 17:38:50 +08:00
|
|
|
|
size, err := filesys.WriteTree(tree)
|
|
|
|
|
|
if err != nil {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2023-02-21 13:00:27 +08:00
|
|
|
|
sql.RenameTreeQueue(tree)
|
2024-06-20 22:53:27 +08:00
|
|
|
|
treenode.UpsertBlockTree(tree)
|
2025-09-10 21:39:14 +08:00
|
|
|
|
refreshDocInfoWithSize(tree, size)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-01-27 18:07:06 +08:00
|
|
|
|
func DuplicateDoc(tree *parse.Tree) {
|
2022-06-05 11:20:22 +08:00
|
|
|
|
msgId := util.PushMsg(Conf.Language(116), 30000)
|
|
|
|
|
|
defer util.PushClearMsg(msgId)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
2025-08-05 16:55:48 +08:00
|
|
|
|
previousPath := tree.Path
|
2024-07-24 11:23:21 +08:00
|
|
|
|
resetTree(tree, "Duplicated", false)
|
2023-01-27 18:07:06 +08:00
|
|
|
|
createTreeTx(tree)
|
2025-08-05 16:55:48 +08:00
|
|
|
|
box := Conf.Box(tree.Box)
|
|
|
|
|
|
if nil != box {
|
|
|
|
|
|
box.addSort(previousPath, tree.ID)
|
|
|
|
|
|
}
|
2024-10-22 19:20:44 +08:00
|
|
|
|
FlushTxQueue()
|
2024-07-24 11:23:21 +08:00
|
|
|
|
|
2025-11-22 18:28:00 +08:00
|
|
|
|
// 复制为副本时移除数据库绑定状态 https://github.com/siyuan-note/siyuan/issues/12294
|
2024-08-03 21:55:10 +08:00
|
|
|
|
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
|
|
|
|
|
|
if !entering || !n.IsBlock() {
|
|
|
|
|
|
return ast.WalkContinue
|
2024-07-24 11:23:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-22 18:28:00 +08:00
|
|
|
|
n.RemoveIALAttr(av.NodeAttrNameAvs)
|
2025-11-26 20:45:37 +08:00
|
|
|
|
n.RemoveIALAttr(av.NodeAttrViewNames)
|
2025-11-22 18:46:53 +08:00
|
|
|
|
n.RemoveIALAttrsByPrefix(av.NodeAttrViewStaticText)
|
2024-08-03 21:55:10 +08:00
|
|
|
|
return ast.WalkContinue
|
|
|
|
|
|
})
|
2022-08-21 13:01:54 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2022-08-19 12:12:39 +08:00
|
|
|
|
|
2022-08-21 13:01:54 +08:00
|
|
|
|
func createTreeTx(tree *parse.Tree) {
|
|
|
|
|
|
transaction := &Transaction{DoOperations: []*Operation{{Action: "create", Data: tree}}}
|
2023-03-19 00:12:28 +08:00
|
|
|
|
PerformTransactions(&[]*Transaction{transaction})
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-11-14 10:02:03 +08:00
|
|
|
|
var createDocLock = sync.Mutex{}
|
|
|
|
|
|
|
2023-01-27 16:48:42 +08:00
|
|
|
|
func CreateDocByMd(boxID, p, title, md string, sorts []string) (tree *parse.Tree, err error) {
|
2023-11-14 10:02:03 +08:00
|
|
|
|
createDocLock.Lock()
|
|
|
|
|
|
defer createDocLock.Unlock()
|
|
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
box := Conf.Box(boxID)
|
|
|
|
|
|
if nil == box {
|
2023-01-27 16:48:42 +08:00
|
|
|
|
err = errors.New(Conf.Language(0))
|
|
|
|
|
|
return
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-02-10 14:28:10 +08:00
|
|
|
|
luteEngine := util.NewLute()
|
2024-01-12 19:58:44 +08:00
|
|
|
|
dom := luteEngine.Md2BlockDOM(md, false)
|
2023-09-27 16:11:18 +08:00
|
|
|
|
tree, err = createDoc(box.ID, p, title, dom)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2022-07-01 22:41:28 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-22 19:20:44 +08:00
|
|
|
|
FlushTxQueue()
|
2025-08-29 16:11:43 +08:00
|
|
|
|
if 0 < len(sorts) {
|
|
|
|
|
|
ChangeFileTreeSort(box.ID, sorts)
|
|
|
|
|
|
} else {
|
2025-11-11 10:54:54 +08:00
|
|
|
|
box.setSortByConf(path.Dir(tree.Path), tree.ID)
|
2025-08-29 16:11:43 +08:00
|
|
|
|
}
|
2022-07-01 22:41:28 +08:00
|
|
|
|
return
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-12 10:12:32 +08:00
|
|
|
|
func CreateWithMarkdown(tags, boxID, hPath, md, parentID, id string, withMath bool, clippingHref string) (retID string, err error) {
|
2023-11-14 10:02:03 +08:00
|
|
|
|
createDocLock.Lock()
|
|
|
|
|
|
defer createDocLock.Unlock()
|
|
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
box := Conf.Box(boxID)
|
|
|
|
|
|
if nil == box {
|
|
|
|
|
|
err = errors.New(Conf.Language(0))
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-22 19:20:44 +08:00
|
|
|
|
FlushTxQueue()
|
2023-02-10 14:28:10 +08:00
|
|
|
|
luteEngine := util.NewLute()
|
2024-05-21 00:04:56 +08:00
|
|
|
|
if withMath {
|
|
|
|
|
|
luteEngine.SetInlineMath(true)
|
|
|
|
|
|
}
|
2024-08-12 18:03:10 +08:00
|
|
|
|
luteEngine.SetHTMLTag2TextMark(true)
|
2024-11-12 10:12:32 +08:00
|
|
|
|
if strings.HasPrefix(clippingHref, "https://ld246.com/article/") || strings.HasPrefix(clippingHref, "https://liuyun.io/article/") {
|
|
|
|
|
|
// 改进链滴剪藏 https://github.com/siyuan-note/siyuan/issues/13117
|
2025-02-09 11:19:26 +08:00
|
|
|
|
enableLuteInlineSyntax(luteEngine)
|
2024-11-12 10:12:32 +08:00
|
|
|
|
}
|
2024-01-12 19:58:44 +08:00
|
|
|
|
dom := luteEngine.Md2BlockDOM(md, false)
|
2023-09-30 17:57:23 +08:00
|
|
|
|
retID, err = createDocsByHPath(box.ID, hPath, dom, parentID, id)
|
2024-09-20 11:49:07 +08:00
|
|
|
|
|
|
|
|
|
|
nameValues := map[string]string{}
|
|
|
|
|
|
tags = strings.TrimSpace(tags)
|
|
|
|
|
|
tags = strings.ReplaceAll(tags, ",", ",")
|
|
|
|
|
|
tagArray := strings.Split(tags, ",")
|
|
|
|
|
|
var tmp []string
|
|
|
|
|
|
for _, tag := range tagArray {
|
|
|
|
|
|
tmp = append(tmp, strings.TrimSpace(tag))
|
|
|
|
|
|
}
|
|
|
|
|
|
tags = strings.Join(tmp, ",")
|
|
|
|
|
|
nameValues["tags"] = tags
|
|
|
|
|
|
SetBlockAttrs(retID, nameValues)
|
|
|
|
|
|
|
2024-10-22 19:20:44 +08:00
|
|
|
|
FlushTxQueue()
|
2025-11-03 11:14:58 +08:00
|
|
|
|
|
|
|
|
|
|
bt := treenode.GetBlockTree(retID)
|
|
|
|
|
|
if nil == bt {
|
|
|
|
|
|
logging.LogWarnf("get block tree by id [%s] failed after create", retID)
|
2025-11-03 11:02:38 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-11-11 10:54:54 +08:00
|
|
|
|
box.setSortByConf(path.Dir(bt.Path), retID)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-29 00:01:30 +08:00
|
|
|
|
const DailyNoteAttrPrefix = "custom-dailynote-"
|
|
|
|
|
|
|
2023-11-14 10:02:03 +08:00
|
|
|
|
func CreateDailyNote(boxID string) (p string, existed bool, err error) {
|
|
|
|
|
|
createDocLock.Lock()
|
|
|
|
|
|
defer createDocLock.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
box := Conf.Box(boxID)
|
|
|
|
|
|
if nil == box {
|
|
|
|
|
|
err = ErrBoxNotFound
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
boxConf := box.GetConf()
|
|
|
|
|
|
if "" == boxConf.DailyNoteSavePath || "/" == boxConf.DailyNoteSavePath {
|
|
|
|
|
|
err = errors.New(Conf.Language(49))
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
hPath, err := RenderGoTemplate(boxConf.DailyNoteSavePath)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2023-11-14 10:02:03 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-22 19:20:44 +08:00
|
|
|
|
FlushTxQueue()
|
2023-11-14 10:02:03 +08:00
|
|
|
|
|
2025-04-01 01:19:39 +08:00
|
|
|
|
hPath = util.TrimSpaceInPath(hPath)
|
2023-11-14 10:02:03 +08:00
|
|
|
|
existRoot := treenode.GetBlockTreeRootByHPath(box.ID, hPath)
|
|
|
|
|
|
if nil != existRoot {
|
|
|
|
|
|
existed = true
|
|
|
|
|
|
p = existRoot.Path
|
2023-12-04 17:53:04 +08:00
|
|
|
|
|
2024-03-10 23:27:13 +08:00
|
|
|
|
tree, loadErr := LoadTreeByBlockID(existRoot.RootID)
|
2023-12-04 17:53:04 +08:00
|
|
|
|
if nil != loadErr {
|
|
|
|
|
|
logging.LogWarnf("load tree by block id [%s] failed: %v", existRoot.RootID, loadErr)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
p = tree.Path
|
|
|
|
|
|
date := time.Now().Format("20060102")
|
2025-11-29 00:01:30 +08:00
|
|
|
|
if tree.Root.IALAttr(DailyNoteAttrPrefix+date) == "" {
|
|
|
|
|
|
tree.Root.SetIALAttr(DailyNoteAttrPrefix+date, date)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = indexWriteTreeUpsertQueue(tree); err != nil {
|
2023-12-04 17:53:04 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2023-11-14 10:02:03 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
id, err := createDocsByHPath(box.ID, hPath, "", "", "")
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2023-11-14 10:02:03 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-22 19:46:48 +08:00
|
|
|
|
var templateTree *parse.Tree
|
|
|
|
|
|
var templateDom string
|
2023-11-14 10:02:03 +08:00
|
|
|
|
if "" != boxConf.DailyNoteTemplatePath {
|
|
|
|
|
|
tplPath := filepath.Join(util.DataDir, "templates", boxConf.DailyNoteTemplatePath)
|
|
|
|
|
|
if !filelock.IsExist(tplPath) {
|
|
|
|
|
|
logging.LogWarnf("not found daily note template [%s]", tplPath)
|
|
|
|
|
|
} else {
|
2024-03-22 19:46:48 +08:00
|
|
|
|
var renderErr error
|
|
|
|
|
|
templateTree, templateDom, renderErr = RenderTemplate(tplPath, id, false)
|
|
|
|
|
|
if nil != renderErr {
|
2023-11-14 10:02:03 +08:00
|
|
|
|
logging.LogWarnf("render daily note template [%s] failed: %s", boxConf.DailyNoteTemplatePath, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-03-22 19:46:48 +08:00
|
|
|
|
if "" != templateDom {
|
2023-11-14 10:02:03 +08:00
|
|
|
|
var tree *parse.Tree
|
2024-03-10 23:27:13 +08:00
|
|
|
|
tree, err = LoadTreeByBlockID(id)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err == nil {
|
2023-11-14 10:02:03 +08:00
|
|
|
|
tree.Root.FirstChild.Unlink()
|
|
|
|
|
|
|
|
|
|
|
|
luteEngine := util.NewLute()
|
2024-03-22 19:46:48 +08:00
|
|
|
|
newTree := luteEngine.BlockDOM2Tree(templateDom)
|
2023-11-14 10:02:03 +08:00
|
|
|
|
var children []*ast.Node
|
|
|
|
|
|
for c := newTree.Root.FirstChild; nil != c; c = c.Next {
|
|
|
|
|
|
children = append(children, c)
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, c := range children {
|
|
|
|
|
|
tree.Root.AppendChild(c)
|
|
|
|
|
|
}
|
2024-03-22 19:47:03 +08:00
|
|
|
|
|
|
|
|
|
|
// Creating a dailynote template supports doc attributes https://github.com/siyuan-note/siyuan/issues/10698
|
2024-03-22 19:46:48 +08:00
|
|
|
|
templateIALs := parse.IAL2Map(templateTree.Root.KramdownIAL)
|
|
|
|
|
|
for k, v := range templateIALs {
|
2024-09-08 12:19:13 +08:00
|
|
|
|
if "name" == k || "alias" == k || "bookmark" == k || "memo" == k || "icon" == k || strings.HasPrefix(k, "custom-") {
|
2024-03-22 19:46:48 +08:00
|
|
|
|
tree.Root.SetIALAttr(k, v)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-03-22 19:47:03 +08:00
|
|
|
|
|
2023-11-14 10:02:03 +08:00
|
|
|
|
tree.Root.SetIALAttr("updated", util.CurrentTimeSecondsStr())
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = indexWriteTreeUpsertQueue(tree); err != nil {
|
2023-11-14 10:02:03 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
IncSync()
|
|
|
|
|
|
|
2024-10-22 19:20:44 +08:00
|
|
|
|
FlushTxQueue()
|
2023-12-04 16:05:43 +08:00
|
|
|
|
|
2024-03-10 23:27:13 +08:00
|
|
|
|
tree, err := LoadTreeByBlockID(id)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2023-12-04 16:05:43 +08:00
|
|
|
|
logging.LogErrorf("load tree by block id [%s] failed: %v", id, err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
p = tree.Path
|
2023-12-04 17:53:04 +08:00
|
|
|
|
date := time.Now().Format("20060102")
|
2025-11-29 00:01:30 +08:00
|
|
|
|
tree.Root.SetIALAttr(DailyNoteAttrPrefix+date, date)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = indexWriteTreeUpsertQueue(tree); err != nil {
|
2023-12-04 16:05:43 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-11-14 10:02:03 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
func GetHPathByPath(boxID, p string) (hPath string, err error) {
|
|
|
|
|
|
if "/" == p {
|
|
|
|
|
|
hPath = "/"
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-02-10 14:28:10 +08:00
|
|
|
|
luteEngine := util.NewLute()
|
|
|
|
|
|
tree, err := filesys.LoadTree(boxID, p, luteEngine)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
hPath = tree.HPath
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-11-04 21:37:10 +08:00
|
|
|
|
func GetHPathsByPaths(paths []string) (hPaths []string, err error) {
|
|
|
|
|
|
pathsBoxes := getBoxesByPaths(paths)
|
|
|
|
|
|
for p, box := range pathsBoxes {
|
|
|
|
|
|
if nil == box {
|
|
|
|
|
|
logging.LogWarnf("box not found by path [%s]", p)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bt := treenode.GetBlockTreeByPath(p)
|
|
|
|
|
|
if nil == bt {
|
|
|
|
|
|
logging.LogWarnf("block tree not found by path [%s]", p)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-09-12 18:00:50 +08:00
|
|
|
|
hpath := html.UnescapeString(bt.HPath)
|
2023-09-13 10:17:22 +08:00
|
|
|
|
hPaths = append(hPaths, box.Name+hpath)
|
2022-11-04 21:37:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
func GetHPathByID(id string) (hPath string, err error) {
|
2024-03-10 23:27:13 +08:00
|
|
|
|
tree, err := LoadTreeByBlockID(id)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
hPath = tree.HPath
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-04 20:22:48 +08:00
|
|
|
|
func GetPathByID(id string) (path, boxID string, err error) {
|
2024-08-28 17:45:09 +08:00
|
|
|
|
tree, err := LoadTreeByBlockID(id)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2024-08-28 17:45:09 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2024-08-28 17:47:50 +08:00
|
|
|
|
|
|
|
|
|
|
path = tree.Path
|
2025-03-04 20:22:48 +08:00
|
|
|
|
boxID = tree.Box
|
2024-08-28 17:45:09 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
func GetFullHPathByID(id string) (hPath string, err error) {
|
2024-03-10 23:27:13 +08:00
|
|
|
|
tree, err := LoadTreeByBlockID(id)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
box := Conf.Box(tree.Box)
|
2024-06-28 11:41:57 +08:00
|
|
|
|
if nil == box {
|
|
|
|
|
|
err = ErrBoxNotFound
|
|
|
|
|
|
return
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
2024-06-28 11:41:57 +08:00
|
|
|
|
hPath = box.Name + tree.HPath
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-11-15 09:08:41 +08:00
|
|
|
|
func GetIDsByHPath(hpath, boxID string) (ret []string, err error) {
|
2024-01-12 09:41:08 +08:00
|
|
|
|
ret = []string{}
|
2023-11-15 09:08:41 +08:00
|
|
|
|
roots := treenode.GetBlockTreeRootsByHPath(boxID, hpath)
|
|
|
|
|
|
if 1 > len(roots) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for _, root := range roots {
|
|
|
|
|
|
ret = append(ret, root.ID)
|
|
|
|
|
|
}
|
|
|
|
|
|
ret = gulu.Str.RemoveDuplicatedElem(ret)
|
2024-01-12 09:41:08 +08:00
|
|
|
|
if 1 > len(ret) {
|
|
|
|
|
|
ret = []string{}
|
|
|
|
|
|
}
|
2023-11-15 09:08:41 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-03-27 15:22:33 +08:00
|
|
|
|
func MoveDocs(fromPaths []string, toBoxID, toPath string, callback interface{}) (err error) {
|
2022-11-04 19:25:25 +08:00
|
|
|
|
toBox := Conf.Box(toBoxID)
|
2022-11-03 14:34:08 +08:00
|
|
|
|
if nil == toBox {
|
|
|
|
|
|
err = errors.New(Conf.Language(0))
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-11-05 22:59:24 +08:00
|
|
|
|
fromPaths = util.FilterMoveDocFromPaths(fromPaths, toPath)
|
|
|
|
|
|
if 1 > len(fromPaths) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-11-04 15:35:22 +08:00
|
|
|
|
pathsBoxes := getBoxesByPaths(fromPaths)
|
2022-11-04 21:44:09 +08:00
|
|
|
|
|
2024-03-15 23:11:46 +08:00
|
|
|
|
if 1 == len(fromPaths) {
|
|
|
|
|
|
// 移动到自己的父文档下的情况相当于不移动,直接返回
|
|
|
|
|
|
if fromBox := pathsBoxes[fromPaths[0]]; nil != fromBox && fromBox.ID == toBoxID {
|
|
|
|
|
|
parentDir := path.Dir(fromPaths[0])
|
|
|
|
|
|
if ("/" == toPath && "/" == parentDir) || (parentDir+".sy" == toPath) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-11-04 21:44:09 +08:00
|
|
|
|
// 检查路径深度是否超过限制
|
|
|
|
|
|
for fromPath, fromBox := range pathsBoxes {
|
|
|
|
|
|
childDepth := util.GetChildDocDepth(filepath.Join(util.DataDir, fromBox.ID, fromPath))
|
|
|
|
|
|
if depth := strings.Count(toPath, "/") + childDepth; 6 < depth && !Conf.FileTree.AllowCreateDeeper {
|
|
|
|
|
|
err = errors.New(Conf.Language(118))
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-15 23:36:46 +08:00
|
|
|
|
// A progress layer appears when moving more than 64 documents at once https://github.com/siyuan-note/siyuan/issues/9356
|
|
|
|
|
|
subDocsCount := 0
|
|
|
|
|
|
for fromPath, fromBox := range pathsBoxes {
|
|
|
|
|
|
subDocsCount += countSubDocs(fromBox.ID, fromPath)
|
|
|
|
|
|
}
|
|
|
|
|
|
needShowProgress := 64 < subDocsCount
|
2022-11-04 15:35:22 +08:00
|
|
|
|
if needShowProgress {
|
2023-10-05 21:06:30 +08:00
|
|
|
|
defer util.PushClearProgress()
|
2022-11-04 15:35:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-22 19:20:44 +08:00
|
|
|
|
FlushTxQueue()
|
2023-02-10 14:28:10 +08:00
|
|
|
|
luteEngine := util.NewLute()
|
2023-10-05 21:06:30 +08:00
|
|
|
|
count := 0
|
2022-11-03 14:34:08 +08:00
|
|
|
|
for fromPath, fromBox := range pathsBoxes {
|
2023-10-05 21:06:30 +08:00
|
|
|
|
count++
|
|
|
|
|
|
if needShowProgress {
|
|
|
|
|
|
util.PushEndlessProgress(fmt.Sprintf(Conf.Language(70), fmt.Sprintf("%d/%d", count, len(fromPaths))))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-03-27 15:22:33 +08:00
|
|
|
|
_, err = moveDoc(fromBox, fromPath, toBox, toPath, luteEngine, callback)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2022-11-03 14:34:08 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
cache.ClearDocsIAL()
|
|
|
|
|
|
IncSync()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-15 23:36:46 +08:00
|
|
|
|
func countSubDocs(box, p string) (ret int) {
|
|
|
|
|
|
p = strings.TrimSuffix(p, ".sy")
|
2024-11-21 10:59:29 +08:00
|
|
|
|
_ = filelock.Walk(filepath.Join(util.DataDir, box, p), func(path string, d fs.DirEntry, err error) error {
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2024-03-15 23:36:46 +08:00
|
|
|
|
return err
|
|
|
|
|
|
}
|
2024-11-21 10:59:29 +08:00
|
|
|
|
if d.IsDir() {
|
2024-03-15 23:36:46 +08:00
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
if strings.HasSuffix(path, ".sy") {
|
|
|
|
|
|
ret++
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-03-27 15:22:33 +08:00
|
|
|
|
func moveDoc(fromBox *Box, fromPath string, toBox *Box, toPath string, luteEngine *lute.Lute, callback interface{}) (newPath string, err error) {
|
2022-11-03 14:27:17 +08:00
|
|
|
|
isSameBox := fromBox.ID == toBox.ID
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
|
|
|
|
|
if isSameBox {
|
|
|
|
|
|
if !fromBox.Exist(toPath) {
|
|
|
|
|
|
err = ErrBlockNotFound
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if !toBox.Exist(toPath) {
|
|
|
|
|
|
err = ErrBlockNotFound
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-02-10 14:28:10 +08:00
|
|
|
|
tree, err := filesys.LoadTree(fromBox.ID, fromPath, luteEngine)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2022-11-03 14:27:17 +08:00
|
|
|
|
err = ErrBlockNotFound
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-10 21:39:14 +08:00
|
|
|
|
fromParentTree := loadParentTree(tree)
|
|
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
moveToRoot := "/" == toPath
|
|
|
|
|
|
toBlockID := tree.ID
|
|
|
|
|
|
fromFolder := path.Join(path.Dir(fromPath), tree.ID)
|
|
|
|
|
|
toFolder := "/"
|
|
|
|
|
|
if !moveToRoot {
|
|
|
|
|
|
var toTree *parse.Tree
|
|
|
|
|
|
if isSameBox {
|
2023-02-10 14:28:10 +08:00
|
|
|
|
toTree, err = filesys.LoadTree(fromBox.ID, toPath, luteEngine)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
} else {
|
2023-02-10 14:28:10 +08:00
|
|
|
|
toTree, err = filesys.LoadTree(toBox.ID, toPath, luteEngine)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
err = ErrBlockNotFound
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
toBlockID = toTree.ID
|
|
|
|
|
|
toFolder = path.Join(path.Dir(toPath), toBlockID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if isSameBox {
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = fromBox.MkdirAll(toFolder); err != nil {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = toBox.MkdirAll(toFolder); err != nil {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-06-12 18:04:45 +08:00
|
|
|
|
needMoveSubDocs := fromBox.Exist(fromFolder)
|
|
|
|
|
|
if needMoveSubDocs {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
// 移动子文档文件夹
|
|
|
|
|
|
|
|
|
|
|
|
newFolder := path.Join(toFolder, tree.ID)
|
|
|
|
|
|
if isSameBox {
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = fromBox.Move(fromFolder, newFolder); err != nil {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
2022-11-03 14:27:17 +08:00
|
|
|
|
absFromPath := filepath.Join(util.DataDir, fromBox.ID, fromFolder)
|
|
|
|
|
|
absToPath := filepath.Join(util.DataDir, toBox.ID, newFolder)
|
2023-11-06 22:13:04 +08:00
|
|
|
|
if filelock.IsExist(absToPath) {
|
2022-09-29 21:52:01 +08:00
|
|
|
|
filelock.Remove(absToPath)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = filelock.Rename(absFromPath, absToPath); err != nil {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
msg := fmt.Sprintf(Conf.Language(5), fromBox.Name, fromPath, err)
|
2022-11-03 14:27:17 +08:00
|
|
|
|
logging.LogErrorf("move [path=%s] in box [%s] failed: %s", fromPath, fromBox.ID, err)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
err = errors.New(msg)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
newPath = path.Join(toFolder, tree.ID+".sy")
|
|
|
|
|
|
|
|
|
|
|
|
if isSameBox {
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = fromBox.Move(fromPath, newPath); err != nil {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-02-10 14:28:10 +08:00
|
|
|
|
tree, err = filesys.LoadTree(fromBox.ID, newPath, luteEngine)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
moveTree(tree)
|
|
|
|
|
|
} else {
|
2022-11-03 14:27:17 +08:00
|
|
|
|
absFromPath := filepath.Join(util.DataDir, fromBox.ID, fromPath)
|
|
|
|
|
|
absToPath := filepath.Join(util.DataDir, toBox.ID, newPath)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = filelock.Rename(absFromPath, absToPath); err != nil {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
msg := fmt.Sprintf(Conf.Language(5), fromBox.Name, fromPath, err)
|
2022-11-03 14:27:17 +08:00
|
|
|
|
logging.LogErrorf("move [path=%s] in box [%s] failed: %s", fromPath, fromBox.ID, err)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
err = errors.New(msg)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-02-10 14:28:10 +08:00
|
|
|
|
tree, err = filesys.LoadTree(toBox.ID, newPath, luteEngine)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
moveTree(tree)
|
2022-11-03 14:27:17 +08:00
|
|
|
|
moveSorts(tree.ID, fromBox.ID, toBox.ID)
|
|
|
|
|
|
}
|
2022-11-04 15:35:22 +08:00
|
|
|
|
|
2024-06-12 18:04:45 +08:00
|
|
|
|
if needMoveSubDocs {
|
|
|
|
|
|
// 将其所有子文档的移动事件推送到前端 https://github.com/siyuan-note/siyuan/issues/11661
|
|
|
|
|
|
subDocsFolder := path.Join(toFolder, tree.ID)
|
|
|
|
|
|
syFiles := listSyFiles(path.Join(toBox.ID, subDocsFolder))
|
|
|
|
|
|
for _, syFile := range syFiles {
|
|
|
|
|
|
relPath := strings.TrimPrefix(syFile, "/"+path.Join(toBox.ID, toFolder))
|
|
|
|
|
|
subFromPath := path.Join(path.Dir(fromPath), relPath)
|
|
|
|
|
|
subToPath := path.Join(toFolder, relPath)
|
|
|
|
|
|
|
|
|
|
|
|
evt := util.NewCmdResult("moveDoc", 0, util.PushModeBroadcast)
|
|
|
|
|
|
evt.Data = map[string]interface{}{
|
|
|
|
|
|
"fromNotebook": fromBox.ID,
|
|
|
|
|
|
"fromPath": subFromPath,
|
|
|
|
|
|
"toNotebook": toBox.ID,
|
|
|
|
|
|
"toPath": path.Dir(subToPath) + ".sy",
|
|
|
|
|
|
"newPath": subToPath,
|
|
|
|
|
|
}
|
|
|
|
|
|
evt.Callback = callback
|
|
|
|
|
|
util.PushEvent(evt)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-01-01 14:09:36 +08:00
|
|
|
|
evt := util.NewCmdResult("moveDoc", 0, util.PushModeBroadcast)
|
2022-11-04 15:35:22 +08:00
|
|
|
|
evt.Data = map[string]interface{}{
|
|
|
|
|
|
"fromNotebook": fromBox.ID,
|
|
|
|
|
|
"fromPath": fromPath,
|
|
|
|
|
|
"toNotebook": toBox.ID,
|
|
|
|
|
|
"toPath": toPath,
|
|
|
|
|
|
"newPath": newPath,
|
|
|
|
|
|
}
|
2023-03-27 15:22:33 +08:00
|
|
|
|
evt.Callback = callback
|
2022-11-04 15:35:22 +08:00
|
|
|
|
util.PushEvent(evt)
|
2025-09-10 21:39:14 +08:00
|
|
|
|
|
|
|
|
|
|
refreshDocInfo(fromParentTree)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-01-19 21:36:56 +08:00
|
|
|
|
func RemoveDoc(boxID, p string) {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
box := Conf.Box(boxID)
|
|
|
|
|
|
if nil == box {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-22 19:20:44 +08:00
|
|
|
|
FlushTxQueue()
|
2023-02-10 14:28:10 +08:00
|
|
|
|
luteEngine := util.NewLute()
|
|
|
|
|
|
removeDoc(box, p, luteEngine)
|
2022-11-03 14:27:17 +08:00
|
|
|
|
IncSync()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-01-19 21:36:56 +08:00
|
|
|
|
func RemoveDocs(paths []string) {
|
2022-11-03 14:27:17 +08:00
|
|
|
|
util.PushEndlessProgress(Conf.Language(116))
|
2022-11-03 23:02:12 +08:00
|
|
|
|
defer util.PushClearProgress()
|
2022-11-03 14:27:17 +08:00
|
|
|
|
|
2022-11-03 18:48:11 +08:00
|
|
|
|
paths = util.FilterSelfChildDocs(paths)
|
2022-11-03 14:54:06 +08:00
|
|
|
|
pathsBoxes := getBoxesByPaths(paths)
|
2024-10-22 19:20:44 +08:00
|
|
|
|
FlushTxQueue()
|
2023-02-10 14:28:10 +08:00
|
|
|
|
luteEngine := util.NewLute()
|
2022-11-03 14:34:08 +08:00
|
|
|
|
for p, box := range pathsBoxes {
|
2023-02-10 14:28:10 +08:00
|
|
|
|
removeDoc(box, p, luteEngine)
|
2022-11-03 14:27:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-02-10 14:28:10 +08:00
|
|
|
|
func removeDoc(box *Box, p string, luteEngine *lute.Lute) {
|
|
|
|
|
|
tree, _ := filesys.LoadTree(box.ID, p, luteEngine)
|
2023-01-19 21:36:56 +08:00
|
|
|
|
if nil == tree {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-08-23 11:30:51 +08:00
|
|
|
|
historyDir, err := GetHistoryDir(HistoryOpDelete)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogErrorf("get history dir failed: %s", err)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-11-03 14:27:17 +08:00
|
|
|
|
historyPath := filepath.Join(historyDir, box.ID, p)
|
|
|
|
|
|
absPath := filepath.Join(util.DataDir, box.ID, p)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = filelock.Copy(absPath, historyPath); err != nil {
|
2023-01-19 21:36:56 +08:00
|
|
|
|
logging.LogErrorf("backup [path=%s] to history [%s] failed: %s", absPath, historyPath, err)
|
|
|
|
|
|
return
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-15 10:50:49 +08:00
|
|
|
|
generateAvHistory(tree, historyDir)
|
2022-11-03 14:27:17 +08:00
|
|
|
|
copyDocAssetsToDataAssets(box.ID, p)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
2023-01-19 21:36:56 +08:00
|
|
|
|
removeIDs := treenode.RootChildIDs(tree.ID)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
dir := path.Dir(p)
|
2022-11-03 20:23:24 +08:00
|
|
|
|
childrenDir := path.Join(dir, tree.ID)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
existChildren := box.Exist(childrenDir)
|
|
|
|
|
|
if existChildren {
|
|
|
|
|
|
absChildrenDir := filepath.Join(util.DataDir, tree.Box, childrenDir)
|
|
|
|
|
|
historyPath = filepath.Join(historyDir, tree.Box, childrenDir)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = filelock.Copy(absChildrenDir, historyPath); err != nil {
|
2023-01-19 21:36:56 +08:00
|
|
|
|
logging.LogErrorf("backup [path=%s] to history [%s] failed: %s", absChildrenDir, historyPath, err)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2023-02-10 14:28:10 +08:00
|
|
|
|
indexHistoryDir(filepath.Base(historyDir), util.NewLute())
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
2024-06-15 21:41:04 +08:00
|
|
|
|
allRemoveRootIDs := []string{tree.ID}
|
|
|
|
|
|
allRemoveRootIDs = append(allRemoveRootIDs, removeIDs...)
|
2024-08-04 11:24:04 +08:00
|
|
|
|
allRemoveRootIDs = gulu.Str.RemoveDuplicatedElem(allRemoveRootIDs)
|
2024-06-15 21:41:04 +08:00
|
|
|
|
for _, rootID := range allRemoveRootIDs {
|
2024-06-16 11:08:21 +08:00
|
|
|
|
removeTree, _ := LoadTreeByBlockID(rootID)
|
|
|
|
|
|
if nil == removeTree {
|
|
|
|
|
|
continue
|
2024-06-15 21:41:04 +08:00
|
|
|
|
}
|
2024-06-16 11:08:21 +08:00
|
|
|
|
|
2024-09-26 09:00:35 +08:00
|
|
|
|
syncDelete2AvBlock(removeTree.Root, removeTree, nil)
|
2024-06-15 21:41:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
if existChildren {
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = box.Remove(childrenDir); err != nil {
|
2024-01-11 22:20:45 +08:00
|
|
|
|
logging.LogErrorf("remove children dir [%s%s] failed: %s", box.ID, childrenDir, err)
|
2022-11-03 20:10:42 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2024-01-11 22:20:45 +08:00
|
|
|
|
logging.LogInfof("removed children dir [%s%s]", box.ID, childrenDir)
|
2022-11-03 20:10:42 +08:00
|
|
|
|
}
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = box.Remove(p); err != nil {
|
2024-01-11 22:20:45 +08:00
|
|
|
|
logging.LogErrorf("remove [%s%s] failed: %s", box.ID, p, err)
|
2022-11-03 20:10:42 +08:00
|
|
|
|
return
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
2024-01-11 22:20:45 +08:00
|
|
|
|
logging.LogInfof("removed doc [%s%s]", box.ID, p)
|
|
|
|
|
|
|
2022-11-03 20:10:42 +08:00
|
|
|
|
box.removeSort(removeIDs)
|
2022-12-10 22:17:17 +08:00
|
|
|
|
RemoveRecentDoc(removeIDs)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
if "/" != dir {
|
2022-11-03 14:27:17 +08:00
|
|
|
|
others, err := os.ReadDir(filepath.Join(util.DataDir, box.ID, dir))
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err == nil && 1 > len(others) {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
box.Remove(dir)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-01-01 14:09:36 +08:00
|
|
|
|
evt := util.NewCmdResult("removeDoc", 0, util.PushModeBroadcast)
|
2022-11-03 19:55:48 +08:00
|
|
|
|
evt.Data = map[string]interface{}{
|
|
|
|
|
|
"ids": removeIDs,
|
|
|
|
|
|
}
|
|
|
|
|
|
util.PushEvent(evt)
|
2023-01-19 21:36:56 +08:00
|
|
|
|
|
2024-09-28 23:41:09 +08:00
|
|
|
|
refreshParentDocInfo(tree)
|
2024-10-03 17:40:33 +08:00
|
|
|
|
task.AppendTask(task.DatabaseIndex, removeDoc0, tree, childrenDir)
|
2023-01-19 21:36:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-03 17:40:33 +08:00
|
|
|
|
func removeDoc0(tree *parse.Tree, childrenDir string) {
|
|
|
|
|
|
// 收集引用的定义块 ID
|
|
|
|
|
|
refDefIDs := getRefDefIDs(tree.Root)
|
|
|
|
|
|
// 推送定义节点引用计数
|
|
|
|
|
|
for _, defID := range refDefIDs {
|
2025-07-17 16:33:22 +08:00
|
|
|
|
task.AppendAsyncTaskWithDelay(task.SetDefRefCount, util.SQLFlushInterval, refreshRefCount, defID)
|
2024-10-03 17:40:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-01-19 21:36:56 +08:00
|
|
|
|
treenode.RemoveBlockTreesByPathPrefix(childrenDir)
|
2024-10-03 17:40:33 +08:00
|
|
|
|
sql.RemoveTreePathQueue(tree.Box, childrenDir)
|
|
|
|
|
|
cache.RemoveDocIAL(tree.Path)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func RenameDoc(boxID, p, title string) (err error) {
|
|
|
|
|
|
box := Conf.Box(boxID)
|
|
|
|
|
|
if nil == box {
|
|
|
|
|
|
err = errors.New(Conf.Language(0))
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-22 19:20:44 +08:00
|
|
|
|
FlushTxQueue()
|
2023-02-10 14:28:10 +08:00
|
|
|
|
luteEngine := util.NewLute()
|
|
|
|
|
|
tree, err := filesys.LoadTree(box.ID, p, luteEngine)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-20 23:21:59 +08:00
|
|
|
|
title = removeInvisibleCharsInTitle(title)
|
2022-10-22 01:25:22 +08:00
|
|
|
|
if 512 < utf8.RuneCountInString(title) {
|
2022-10-22 17:09:59 +08:00
|
|
|
|
// 限制笔记本名和文档名最大长度为 `512` https://github.com/siyuan-note/siyuan/issues/6299
|
2022-10-22 01:25:22 +08:00
|
|
|
|
return errors.New(Conf.Language(106))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
oldTitle := tree.Root.IALAttr("title")
|
|
|
|
|
|
if oldTitle == title {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if "" == title {
|
2024-09-28 17:04:00 +08:00
|
|
|
|
title = Conf.language(16)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
2022-11-15 10:59:05 +08:00
|
|
|
|
title = strings.ReplaceAll(title, "/", "")
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
2022-10-24 17:43:21 +08:00
|
|
|
|
tree.HPath = path.Join(path.Dir(tree.HPath), title)
|
|
|
|
|
|
tree.Root.SetIALAttr("title", title)
|
|
|
|
|
|
tree.Root.SetIALAttr("updated", util.CurrentTimeSecondsStr())
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = renameWriteJSONQueue(tree); err != nil {
|
2022-10-24 17:43:21 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-10-19 09:44:56 +08:00
|
|
|
|
refText := getNodeRefText(tree.Root)
|
2023-01-01 14:09:36 +08:00
|
|
|
|
evt := util.NewCmdResult("rename", 0, util.PushModeBroadcast)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
evt.Data = map[string]interface{}{
|
2022-10-19 09:44:56 +08:00
|
|
|
|
"box": boxID,
|
|
|
|
|
|
"id": tree.Root.ID,
|
|
|
|
|
|
"path": p,
|
|
|
|
|
|
"title": title,
|
|
|
|
|
|
"refText": refText,
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
util.PushEvent(evt)
|
|
|
|
|
|
|
|
|
|
|
|
box.renameSubTrees(tree)
|
2022-10-29 23:51:34 +08:00
|
|
|
|
updateRefTextRenameDoc(tree)
|
2022-07-14 21:50:46 +08:00
|
|
|
|
IncSync()
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-09-27 16:11:18 +08:00
|
|
|
|
func createDoc(boxID, p, title, dom string) (tree *parse.Tree, err error) {
|
2024-05-20 23:21:59 +08:00
|
|
|
|
title = removeInvisibleCharsInTitle(title)
|
2022-10-22 01:25:22 +08:00
|
|
|
|
if 512 < utf8.RuneCountInString(title) {
|
2022-10-22 17:09:59 +08:00
|
|
|
|
// 限制笔记本名和文档名最大长度为 `512` https://github.com/siyuan-note/siyuan/issues/6299
|
2023-01-27 16:48:42 +08:00
|
|
|
|
err = errors.New(Conf.Language(106))
|
|
|
|
|
|
return
|
2022-10-22 01:25:22 +08:00
|
|
|
|
}
|
2022-11-15 10:59:05 +08:00
|
|
|
|
title = strings.ReplaceAll(title, "/", "")
|
2024-03-24 22:15:19 +08:00
|
|
|
|
title = strings.TrimSpace(title)
|
|
|
|
|
|
if "" == title {
|
2024-09-28 17:04:00 +08:00
|
|
|
|
title = Conf.Language(16)
|
2024-03-24 22:15:19 +08:00
|
|
|
|
}
|
2022-10-22 01:25:22 +08:00
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
baseName := strings.TrimSpace(path.Base(p))
|
2024-11-29 08:41:43 +08:00
|
|
|
|
if "" == util.GetTreeID(baseName) {
|
2023-01-27 16:48:42 +08:00
|
|
|
|
err = errors.New(Conf.Language(16))
|
|
|
|
|
|
return
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if strings.HasPrefix(baseName, ".") {
|
2023-01-27 16:48:42 +08:00
|
|
|
|
err = errors.New(Conf.Language(13))
|
|
|
|
|
|
return
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
box := Conf.Box(boxID)
|
|
|
|
|
|
if nil == box {
|
2023-01-27 16:48:42 +08:00
|
|
|
|
err = errors.New(Conf.Language(0))
|
|
|
|
|
|
return
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-29 08:41:43 +08:00
|
|
|
|
id := util.GetTreeID(p)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
var hPath string
|
|
|
|
|
|
folder := path.Dir(p)
|
|
|
|
|
|
if "/" != folder {
|
|
|
|
|
|
parentID := path.Base(folder)
|
2024-03-10 23:27:13 +08:00
|
|
|
|
parentTree, loadErr := LoadTreeByBlockID(parentID)
|
2023-01-27 16:48:42 +08:00
|
|
|
|
if nil != loadErr {
|
2023-01-28 10:24:51 +08:00
|
|
|
|
logging.LogErrorf("get parent tree [%s] failed", parentID)
|
2023-01-27 16:48:42 +08:00
|
|
|
|
err = ErrBlockNotFound
|
|
|
|
|
|
return
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
hPath = path.Join(parentTree.HPath, title)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
hPath = "/" + title
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if depth := strings.Count(p, "/"); 7 < depth && !Conf.FileTree.AllowCreateDeeper {
|
|
|
|
|
|
err = errors.New(Conf.Language(118))
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if !box.Exist(folder) {
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = box.MkdirAll(folder); err != nil {
|
2023-01-27 16:48:42 +08:00
|
|
|
|
return
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if box.Exist(p) {
|
2023-01-27 16:48:42 +08:00
|
|
|
|
err = errors.New(Conf.Language(1))
|
|
|
|
|
|
return
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-02-10 14:28:10 +08:00
|
|
|
|
luteEngine := util.NewLute()
|
2022-05-26 15:18:53 +08:00
|
|
|
|
tree = luteEngine.BlockDOM2Tree(dom)
|
|
|
|
|
|
tree.Box = boxID
|
|
|
|
|
|
tree.Path = p
|
|
|
|
|
|
tree.HPath = hPath
|
|
|
|
|
|
tree.ID = id
|
|
|
|
|
|
tree.Root.ID = id
|
2022-09-15 19:54:23 +08:00
|
|
|
|
tree.Root.Spec = "1"
|
2022-05-26 15:18:53 +08:00
|
|
|
|
updated := util.TimeFromID(id)
|
|
|
|
|
|
tree.Root.KramdownIAL = [][]string{{"id", id}, {"title", html.EscapeAttrVal(title)}, {"updated", updated}}
|
|
|
|
|
|
if nil == tree.Root.FirstChild {
|
2024-11-10 12:04:56 +08:00
|
|
|
|
tree.Root.AppendChild(treenode.NewParagraph(""))
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-11-29 16:28:54 +08:00
|
|
|
|
// 如果段落块中仅包含一个 mp3/mp4 超链接,则将其转换为音视频块
|
|
|
|
|
|
// Convert mp3 and mp4 hyperlinks to audio and video when moving cloud inbox to docs https://github.com/siyuan-note/siyuan/issues/9778
|
|
|
|
|
|
var unlinks []*ast.Node
|
|
|
|
|
|
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
|
|
|
|
|
|
if !entering {
|
|
|
|
|
|
return ast.WalkContinue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ast.NodeParagraph == n.Type {
|
|
|
|
|
|
link := n.FirstChild
|
|
|
|
|
|
if nil != link && link.IsTextMarkType("a") {
|
|
|
|
|
|
if strings.HasSuffix(link.TextMarkAHref, ".mp3") {
|
|
|
|
|
|
unlinks = append(unlinks, n)
|
|
|
|
|
|
audio := &ast.Node{ID: n.ID, Type: ast.NodeAudio, Tokens: []byte("<audio controls=\"controls\" src=\"" + link.TextMarkAHref + "\" data-src=\"" + link.TextMarkAHref + "\"></audio>")}
|
|
|
|
|
|
audio.SetIALAttr("id", n.ID)
|
|
|
|
|
|
audio.SetIALAttr("updated", util.TimeFromID(n.ID))
|
|
|
|
|
|
n.InsertBefore(audio)
|
|
|
|
|
|
} else if strings.HasSuffix(link.TextMarkAHref, ".mp4") {
|
|
|
|
|
|
unlinks = append(unlinks, n)
|
|
|
|
|
|
video := &ast.Node{ID: n.ID, Type: ast.NodeVideo, Tokens: []byte("<video controls=\"controls\" src=\"" + link.TextMarkAHref + "\" data-src=\"" + link.TextMarkAHref + "\"></video>")}
|
|
|
|
|
|
video.SetIALAttr("id", n.ID)
|
|
|
|
|
|
video.SetIALAttr("updated", util.TimeFromID(n.ID))
|
|
|
|
|
|
n.InsertBefore(video)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return ast.WalkContinue
|
|
|
|
|
|
})
|
|
|
|
|
|
for _, unlink := range unlinks {
|
|
|
|
|
|
unlink.Unlink()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
transaction := &Transaction{DoOperations: []*Operation{{Action: "create", Data: tree}}}
|
2023-03-19 00:12:28 +08:00
|
|
|
|
PerformTransactions(&[]*Transaction{transaction})
|
2024-10-22 19:20:44 +08:00
|
|
|
|
FlushTxQueue()
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-20 23:21:59 +08:00
|
|
|
|
func removeInvisibleCharsInTitle(title string) string {
|
|
|
|
|
|
// 不要踢掉 零宽连字符,否则有的 Emoji 会变形 https://github.com/siyuan-note/siyuan/issues/11480
|
|
|
|
|
|
title = strings.ReplaceAll(title, string(gulu.ZWJ), "__@ZWJ@__")
|
2024-11-27 20:13:22 +08:00
|
|
|
|
title = util.RemoveInvalid(title)
|
2024-05-20 23:21:59 +08:00
|
|
|
|
title = strings.ReplaceAll(title, "__@ZWJ@__", string(gulu.ZWJ))
|
2025-03-31 15:39:15 +08:00
|
|
|
|
title = strings.TrimSpace(title)
|
2024-05-20 23:21:59 +08:00
|
|
|
|
return title
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
func moveSorts(rootID, fromBox, toBox string) {
|
|
|
|
|
|
root := treenode.GetBlockTree(rootID)
|
|
|
|
|
|
if nil == root {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fromRootSorts := map[string]int{}
|
2022-11-03 20:25:56 +08:00
|
|
|
|
ids := treenode.RootChildIDs(rootID)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
fromConfPath := filepath.Join(util.DataDir, fromBox, ".siyuan", "sort.json")
|
|
|
|
|
|
fromFullSortIDs := map[string]int{}
|
2023-11-06 22:13:04 +08:00
|
|
|
|
if filelock.IsExist(fromConfPath) {
|
2022-09-29 21:52:01 +08:00
|
|
|
|
data, err := filelock.ReadFile(fromConfPath)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogErrorf("read sort conf failed: %s", err)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = gulu.JSON.UnmarshalJSON(data, &fromFullSortIDs); err != nil {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogErrorf("unmarshal sort conf failed: %s", err)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, id := range ids {
|
|
|
|
|
|
fromRootSorts[id] = fromFullSortIDs[id]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
toConfPath := filepath.Join(util.DataDir, toBox, ".siyuan", "sort.json")
|
|
|
|
|
|
toFullSortIDs := map[string]int{}
|
2023-11-06 22:13:04 +08:00
|
|
|
|
if filelock.IsExist(toConfPath) {
|
2022-09-29 21:52:01 +08:00
|
|
|
|
data, err := filelock.ReadFile(toConfPath)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogErrorf("read sort conf failed: %s", err)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = gulu.JSON.UnmarshalJSON(data, &toFullSortIDs); err != nil {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogErrorf("unmarshal sort conf failed: %s", err)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for id, sortVal := range fromRootSorts {
|
|
|
|
|
|
toFullSortIDs[id] = sortVal
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-07-15 23:01:47 +08:00
|
|
|
|
data, err := gulu.JSON.MarshalJSON(toFullSortIDs)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogErrorf("marshal sort conf failed: %s", err)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = filelock.WriteFile(toConfPath, data); err != nil {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogErrorf("write sort conf failed: %s", err)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-11-22 21:19:47 +08:00
|
|
|
|
|
|
|
|
|
|
sortIDs := map[string]int{}
|
|
|
|
|
|
bt := treenode.GetBlockTree(rootID)
|
|
|
|
|
|
if nil != bt {
|
|
|
|
|
|
parentPath := path.Dir(bt.Path)
|
|
|
|
|
|
docs, _, listErr := ListDocTree(toBox, parentPath, util.SortModeUnassigned, false, false, 102400)
|
|
|
|
|
|
if listErr != nil {
|
|
|
|
|
|
logging.LogErrorf("list doc tree failed: %s", err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for _, doc := range docs {
|
|
|
|
|
|
sortIDs[doc.ID] = doc.Sort
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pushFiletreeSortChanged(sortIDs)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func ChangeFileTreeSort(boxID string, paths []string) {
|
2022-07-01 23:08:56 +08:00
|
|
|
|
if 1 > len(paths) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-22 19:20:44 +08:00
|
|
|
|
FlushTxQueue()
|
2022-05-26 15:18:53 +08:00
|
|
|
|
box := Conf.Box(boxID)
|
|
|
|
|
|
sortIDs := map[string]int{}
|
|
|
|
|
|
max := 0
|
|
|
|
|
|
for i, p := range paths {
|
2024-11-29 08:41:43 +08:00
|
|
|
|
id := util.GetTreeID(p)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
sortIDs[id] = i + 1
|
|
|
|
|
|
if i == len(paths)-1 {
|
|
|
|
|
|
max = i + 2
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
p := paths[0]
|
|
|
|
|
|
parentPath := path.Dir(p)
|
|
|
|
|
|
absParentPath := filepath.Join(util.DataDir, boxID, parentPath)
|
|
|
|
|
|
files, err := os.ReadDir(absParentPath)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2023-08-15 09:31:19 +02:00
|
|
|
|
logging.LogErrorf("read dir [%s] failed: %s", absParentPath, err)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sortFolderIDs := map[string]int{}
|
|
|
|
|
|
for _, f := range files {
|
|
|
|
|
|
if !strings.HasSuffix(f.Name(), ".sy") {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
id := strings.TrimSuffix(f.Name(), ".sy")
|
|
|
|
|
|
val := sortIDs[id]
|
|
|
|
|
|
if 0 == val {
|
|
|
|
|
|
val = max
|
|
|
|
|
|
max++
|
|
|
|
|
|
}
|
|
|
|
|
|
sortFolderIDs[id] = val
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
confDir := filepath.Join(util.DataDir, box.ID, ".siyuan")
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = os.MkdirAll(confDir, 0755); err != nil {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogErrorf("create conf dir failed: %s", err)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
confPath := filepath.Join(confDir, "sort.json")
|
|
|
|
|
|
fullSortIDs := map[string]int{}
|
|
|
|
|
|
var data []byte
|
2023-11-06 22:13:04 +08:00
|
|
|
|
if filelock.IsExist(confPath) {
|
2022-09-29 21:52:01 +08:00
|
|
|
|
data, err = filelock.ReadFile(confPath)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogErrorf("read sort conf failed: %s", err)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = gulu.JSON.UnmarshalJSON(data, &fullSortIDs); err != nil {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogErrorf("unmarshal sort conf failed: %s", err)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for sortID, sortVal := range sortFolderIDs {
|
|
|
|
|
|
fullSortIDs[sortID] = sortVal
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-07-15 23:01:47 +08:00
|
|
|
|
data, err = gulu.JSON.MarshalJSON(fullSortIDs)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogErrorf("marshal sort conf failed: %s", err)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = filelock.WriteFile(confPath, data); err != nil {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogErrorf("write sort conf failed: %s", err)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-14 21:50:46 +08:00
|
|
|
|
IncSync()
|
2025-11-22 21:19:47 +08:00
|
|
|
|
|
|
|
|
|
|
pushFiletreeSortChanged(sortFolderIDs)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (box *Box) fillSort(files *[]*File) {
|
|
|
|
|
|
confPath := filepath.Join(util.DataDir, box.ID, ".siyuan", "sort.json")
|
2023-11-06 22:13:04 +08:00
|
|
|
|
if !filelock.IsExist(confPath) {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-09-29 21:52:01 +08:00
|
|
|
|
data, err := filelock.ReadFile(confPath)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogErrorf("read sort conf failed: %s", err)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fullSortIDs := map[string]int{}
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = gulu.JSON.UnmarshalJSON(data, &fullSortIDs); err != nil {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogErrorf("unmarshal sort conf failed: %s", err)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for _, f := range *files {
|
|
|
|
|
|
id := strings.TrimSuffix(f.ID, ".sy")
|
|
|
|
|
|
f.Sort = fullSortIDs[id]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-11-03 20:10:42 +08:00
|
|
|
|
func (box *Box) removeSort(ids []string) {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
confPath := filepath.Join(util.DataDir, box.ID, ".siyuan", "sort.json")
|
2023-11-06 22:13:04 +08:00
|
|
|
|
if !filelock.IsExist(confPath) {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-09-29 21:52:01 +08:00
|
|
|
|
data, err := filelock.ReadFile(confPath)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogErrorf("read sort conf failed: %s", err)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fullSortIDs := map[string]int{}
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = gulu.JSON.UnmarshalJSON(data, &fullSortIDs); err != nil {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogErrorf("unmarshal sort conf failed: %s", err)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-11-03 20:10:42 +08:00
|
|
|
|
for _, toRemove := range ids {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
delete(fullSortIDs, toRemove)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-07-15 23:01:47 +08:00
|
|
|
|
data, err = gulu.JSON.MarshalJSON(fullSortIDs)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogErrorf("marshal sort conf failed: %s", err)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = filelock.WriteFile(confPath, data); err != nil {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogErrorf("write sort conf failed: %s", err)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2023-11-17 17:26:28 +08:00
|
|
|
|
|
2025-11-11 10:54:54 +08:00
|
|
|
|
func (box *Box) setSortByConf(parentPath, id string) {
|
|
|
|
|
|
if *Conf.FileTree.CreateDocAtTop {
|
|
|
|
|
|
box.addMinSort(parentPath, id)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
box.addMaxSort(parentPath, id)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (box *Box) addMaxSort(parentPath, id string) {
|
|
|
|
|
|
docs, _, err := ListDocTree(box.ID, parentPath, util.SortModeUnassigned, false, false, 102400)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
logging.LogErrorf("list doc tree failed: %s", err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sortVal := 0
|
|
|
|
|
|
if 0 < len(docs) {
|
|
|
|
|
|
sortVal = docs[len(docs)-1].Sort + 1
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
box.setSortVal(id, sortVal)
|
2025-11-22 21:19:47 +08:00
|
|
|
|
|
|
|
|
|
|
sortIDs := map[string]int{}
|
|
|
|
|
|
for _, doc := range docs {
|
|
|
|
|
|
sortIDs[doc.ID] = doc.Sort
|
|
|
|
|
|
}
|
|
|
|
|
|
sortIDs[id] = sortVal
|
|
|
|
|
|
pushFiletreeSortChanged(sortIDs)
|
2025-11-11 10:54:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-11-17 17:26:28 +08:00
|
|
|
|
func (box *Box) addMinSort(parentPath, id string) {
|
|
|
|
|
|
docs, _, err := ListDocTree(box.ID, parentPath, util.SortModeUnassigned, false, false, 1)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2023-11-17 17:26:28 +08:00
|
|
|
|
logging.LogErrorf("list doc tree failed: %s", err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sortVal := 0
|
|
|
|
|
|
if 0 < len(docs) {
|
|
|
|
|
|
sortVal = docs[0].Sort - 1
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-11 10:54:54 +08:00
|
|
|
|
box.setSortVal(id, sortVal)
|
2025-11-22 21:19:47 +08:00
|
|
|
|
|
|
|
|
|
|
sortIDs := map[string]int{}
|
|
|
|
|
|
for _, doc := range docs {
|
|
|
|
|
|
sortIDs[doc.ID] = doc.Sort
|
|
|
|
|
|
}
|
|
|
|
|
|
sortIDs[id] = sortVal
|
|
|
|
|
|
pushFiletreeSortChanged(sortIDs)
|
2025-11-11 10:54:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (box *Box) setSortVal(id string, sortVal int) {
|
|
|
|
|
|
var err error
|
2023-11-17 17:26:28 +08:00
|
|
|
|
confDir := filepath.Join(util.DataDir, box.ID, ".siyuan")
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = os.MkdirAll(confDir, 0755); err != nil {
|
2023-11-17 17:26:28 +08:00
|
|
|
|
logging.LogErrorf("create conf dir failed: %s", err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
confPath := filepath.Join(confDir, "sort.json")
|
|
|
|
|
|
fullSortIDs := map[string]int{}
|
|
|
|
|
|
var data []byte
|
|
|
|
|
|
if filelock.IsExist(confPath) {
|
|
|
|
|
|
data, err = filelock.ReadFile(confPath)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2023-11-17 17:26:28 +08:00
|
|
|
|
logging.LogErrorf("read sort conf failed: %s", err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = gulu.JSON.UnmarshalJSON(data, &fullSortIDs); err != nil {
|
2023-11-17 17:26:28 +08:00
|
|
|
|
logging.LogErrorf("unmarshal sort conf failed: %s", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fullSortIDs[id] = sortVal
|
|
|
|
|
|
data, err = gulu.JSON.MarshalJSON(fullSortIDs)
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err != nil {
|
2023-11-17 17:26:28 +08:00
|
|
|
|
logging.LogErrorf("marshal sort conf failed: %s", err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2024-09-04 04:40:50 +03:00
|
|
|
|
if err = filelock.WriteFile(confPath, data); err != nil {
|
2023-11-17 17:26:28 +08:00
|
|
|
|
logging.LogErrorf("write sort conf failed: %s", err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-11-11 10:54:54 +08:00
|
|
|
|
return
|
2023-11-17 17:26:28 +08:00
|
|
|
|
}
|
2024-11-22 22:36:19 +08:00
|
|
|
|
|
2024-11-29 09:07:17 +08:00
|
|
|
|
func (box *Box) addSort(previousPath, id string) {
|
2024-11-22 22:36:19 +08:00
|
|
|
|
confDir := filepath.Join(util.DataDir, box.ID, ".siyuan")
|
|
|
|
|
|
if err := os.MkdirAll(confDir, 0755); err != nil {
|
|
|
|
|
|
logging.LogErrorf("create conf dir failed: %s", err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
confPath := filepath.Join(confDir, "sort.json")
|
|
|
|
|
|
fullSortIDs := map[string]int{}
|
|
|
|
|
|
var data []byte
|
|
|
|
|
|
if filelock.IsExist(confPath) {
|
|
|
|
|
|
data, err := filelock.ReadFile(confPath)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
logging.LogErrorf("read sort conf failed: %s", err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if err = gulu.JSON.UnmarshalJSON(data, &fullSortIDs); err != nil {
|
|
|
|
|
|
logging.LogErrorf("unmarshal sort conf failed: %s", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-29 09:07:17 +08:00
|
|
|
|
parentPath := path.Dir(previousPath)
|
|
|
|
|
|
docs, _, err := ListDocTree(box.ID, parentPath, util.SortModeUnassigned, false, false, Conf.FileTree.MaxListCount)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
logging.LogErrorf("list doc tree failed: %s", err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-22 21:19:47 +08:00
|
|
|
|
sortIDs := map[string]int{}
|
2024-11-29 09:07:17 +08:00
|
|
|
|
previousID := util.GetTreeID(previousPath)
|
2024-11-22 22:36:19 +08:00
|
|
|
|
sortVal := 0
|
2024-11-29 09:07:17 +08:00
|
|
|
|
for _, doc := range docs {
|
|
|
|
|
|
fullSortIDs[doc.ID] = sortVal
|
|
|
|
|
|
if doc.ID == previousID {
|
|
|
|
|
|
sortVal++
|
|
|
|
|
|
fullSortIDs[id] = sortVal
|
|
|
|
|
|
}
|
2024-11-22 22:36:19 +08:00
|
|
|
|
sortVal++
|
2025-11-22 21:19:47 +08:00
|
|
|
|
sortIDs[doc.ID] = sortVal
|
2024-11-22 22:36:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-29 09:07:17 +08:00
|
|
|
|
data, err = gulu.JSON.MarshalJSON(fullSortIDs)
|
2024-11-22 22:36:19 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
logging.LogErrorf("marshal sort conf failed: %s", err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if err = filelock.WriteFile(confPath, data); err != nil {
|
|
|
|
|
|
logging.LogErrorf("write sort conf failed: %s", err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-11-22 21:19:47 +08:00
|
|
|
|
|
|
|
|
|
|
pushFiletreeSortChanged(sortIDs)
|
2024-11-22 22:36:19 +08:00
|
|
|
|
}
|
2024-12-13 19:58:13 +08:00
|
|
|
|
|
|
|
|
|
|
func (box *Box) setSort(sortIDVals map[string]int) {
|
|
|
|
|
|
confPath := filepath.Join(util.DataDir, box.ID, ".siyuan", "sort.json")
|
|
|
|
|
|
if !filelock.IsExist(confPath) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
data, err := filelock.ReadFile(confPath)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
logging.LogErrorf("read sort conf failed: %s", err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fullSortIDs := map[string]int{}
|
|
|
|
|
|
if err = gulu.JSON.UnmarshalJSON(data, &fullSortIDs); err != nil {
|
|
|
|
|
|
logging.LogErrorf("unmarshal sort conf failed: %s", err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for sortID := range sortIDVals {
|
|
|
|
|
|
fullSortIDs[sortID] = sortIDVals[sortID]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
data, err = gulu.JSON.MarshalJSON(fullSortIDs)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
logging.LogErrorf("marshal sort conf failed: %s", err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if err = filelock.WriteFile(confPath, data); err != nil {
|
|
|
|
|
|
logging.LogErrorf("write sort conf failed: %s", err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-11-22 21:19:47 +08:00
|
|
|
|
|
|
|
|
|
|
pushFiletreeSortChanged(sortIDVals)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func pushFiletreeSortChanged(sortIDs map[string]int) {
|
2025-11-26 16:43:08 +08:00
|
|
|
|
if 1 > len(sortIDs) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-22 21:19:47 +08:00
|
|
|
|
var childIDs []string
|
|
|
|
|
|
for sortID := range sortIDs {
|
|
|
|
|
|
childIDs = append(childIDs, sortID)
|
|
|
|
|
|
}
|
|
|
|
|
|
sort.Slice(childIDs, func(i, j int) bool {
|
|
|
|
|
|
return sortIDs[childIDs[i]] < sortIDs[childIDs[j]]
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
firstID := childIDs[0]
|
|
|
|
|
|
bt := treenode.GetBlockTree(firstID)
|
|
|
|
|
|
if nil == bt {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
parentPath := path.Dir(bt.Path)
|
|
|
|
|
|
util.BroadcastByType("main", "filetreeSortChanged", 0, "", map[string]any{
|
|
|
|
|
|
"parentPath": parentPath,
|
|
|
|
|
|
"childIDs": childIDs,
|
|
|
|
|
|
})
|
2024-12-13 19:58:13 +08:00
|
|
|
|
}
|