123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665 |
- // SiYuan - Build Your Eternal Digital Garden
- // 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"
- "math"
- "os"
- "path"
- "path/filepath"
- "sort"
- "strconv"
- "strings"
- "time"
- "unicode/utf8"
- "github.com/88250/gulu"
- "github.com/88250/lute/ast"
- "github.com/88250/lute/html"
- "github.com/88250/lute/parse"
- util2 "github.com/88250/lute/util"
- "github.com/dustin/go-humanize"
- "github.com/facette/natsort"
- "github.com/gin-gonic/gin"
- jsoniter "github.com/json-iterator/go"
- "github.com/siyuan-note/filelock"
- "github.com/siyuan-note/logging"
- "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"
- "github.com/siyuan-note/siyuan/kernel/treenode"
- "github.com/siyuan-note/siyuan/kernel/util"
- )
- type File struct {
- Path string `json:"path"`
- Name string `json:"name"`
- Icon string `json:"icon"`
- Name1 string `json:"name1"`
- 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"`
- }
- 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"
- ret.Icon = ial["icon"]
- 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()
- ret.HCtime = t.Format("2006-01-02 15:04:05")
- ret.HSize = humanize.Bytes(ret.Size)
- mTime := t
- if updated := ial["updated"]; "" != updated {
- if updatedTime, err := time.ParseInLocation("20060102150405", updated, time.Local); nil == err {
- mTime = updatedTime
- }
- }
- ret.Mtime = mTime.Unix()
- ret.HMtime = HumanizeTime(mTime)
- return
- }
- func HumanizeTime(then time.Time) string {
- labels := timeLangs[Conf.Lang]
- defaultMagnitudes := []humanize.RelTimeMagnitude{
- {time.Second, labels["now"].(string), time.Second},
- {2 * time.Second, labels["1s"].(string), 1},
- {time.Minute, labels["xs"].(string), time.Second},
- {2 * time.Minute, labels["1m"].(string), 1},
- {time.Hour, labels["xm"].(string), time.Minute},
- {2 * time.Hour, labels["1h"].(string), 1},
- {humanize.Day, labels["xh"].(string), time.Hour},
- {2 * humanize.Day, labels["1d"].(string), 1},
- {humanize.Week, labels["xd"].(string), humanize.Day},
- {2 * humanize.Week, labels["1w"].(string), 1},
- {humanize.Month, labels["xw"].(string), humanize.Week},
- {2 * humanize.Month, labels["1M"].(string), 1},
- {humanize.Year, labels["xM"].(string), humanize.Month},
- {18 * humanize.Month, labels["1y"].(string), 1},
- {2 * humanize.Year, labels["2y"].(string), 1},
- {humanize.LongTime, labels["xy"].(string), humanize.Year},
- {math.MaxInt64, labels["max"].(string), 1},
- }
- return humanize.CustomRelTime(then, time.Now(), labels["albl"].(string), labels["blbl"].(string), defaultMagnitudes)
- }
- 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)
- data, err := filelock.ReadFile(filePath)
- if util.IsCorruptedSYData(data) {
- box.moveCorruptedData(filePath)
- return nil
- }
- if nil != err {
- logging.LogErrorf("read file [%s] failed: %s", p, err)
- return nil
- }
- ret = readDocIAL(data)
- if 1 > len(ret) {
- logging.LogWarnf("tree [%s] is corrupted", filePath)
- box.moveCorruptedData(filePath)
- return nil
- }
- cache.PutDocIAL(p, ret)
- return ret
- }
- 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)
- if copyErr := filelock.Copy(filePath, to); nil != copyErr {
- logging.LogErrorf("copy corrupted data file [%s] failed: %s", filePath, copyErr)
- return
- }
- if removeErr := filelock.Remove(filePath); nil != removeErr {
- logging.LogErrorf("remove corrupted data file [%s] failed: %s", filePath, removeErr)
- return
- }
- logging.LogWarnf("moved corrupted data file [%s] to [%s]", filePath, to)
- }
- func readDocIAL(data []byte) (ret map[string]string) {
- ret = map[string]string{}
- jsoniter.Get(data, "Properties").ToVal(&ret)
- return
- }
- func SearchDocsByKeyword(keyword string) (ret []map[string]string) {
- ret = []map[string]string{}
- openedBoxes := Conf.GetOpenedBoxes()
- boxes := map[string]*Box{}
- for _, box := range openedBoxes {
- boxes[box.ID] = box
- }
- var rootBlocks []*sql.Block
- if "" != keyword {
- for _, box := range boxes {
- if strings.Contains(box.Name, keyword) {
- ret = append(ret, map[string]string{"path": "/", "hPath": box.Name + "/", "box": box.ID, "boxIcon": box.Icon})
- }
- }
- condition := "hpath LIKE '%" + keyword + "%'"
- if "" != keyword {
- namCondition := Conf.Search.NAMFilter(keyword)
- if "" != namCondition {
- condition += " " + namCondition
- }
- }
- rootBlocks = sql.QueryRootBlockByCondition(condition)
- } else {
- for _, box := range boxes {
- ret = append(ret, map[string]string{"path": "/", "hPath": box.Name + "/", "box": box.ID, "boxIcon": box.Icon})
- }
- }
- for _, block := range rootBlocks {
- b := boxes[block.Box]
- if nil == b {
- continue
- }
- hPath := b.Name + block.HPath
- ret = append(ret, map[string]string{"path": block.Path, "hPath": hPath, "box": block.Box, "boxIcon": b.Icon})
- }
- 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
- }
- func ListDocTree(boxID, path string, sortMode int) (ret []*File, totals int, err error) {
- //os.MkdirAll("pprof", 0755)
- //cpuProfile, _ := os.Create("pprof/cpu_profile_list_doc_tree")
- //pprof.StartCPUProfile(cpuProfile)
- //defer pprof.StopCPUProfile()
- ret = []*File{}
- box := Conf.Box(boxID)
- if nil == box {
- return nil, 0, errors.New(Conf.Language(0))
- }
- var files []*FileInfo
- start := time.Now()
- files, totals, err = box.Ls(path)
- if nil != err {
- return
- }
- elapsed := time.Now().Sub(start).Milliseconds()
- if 100 < elapsed {
- logging.LogWarnf("ls elapsed [%dms]", elapsed)
- }
- start = time.Now()
- boxLocalPath := filepath.Join(util.DataDir, box.ID)
- var docs []*File
- for _, file := range files {
- if file.isdir {
- if !util.IsIDPattern(file.name) {
- continue
- }
- parentDocPath := strings.TrimSuffix(file.path, "/") + ".sy"
- subDocFile := box.Stat(parentDocPath)
- if nil == subDocFile {
- continue
- }
- if ial := box.docIAL(parentDocPath); nil != ial {
- doc := box.docFromFileInfo(subDocFile, ial)
- subFiles, err := os.ReadDir(filepath.Join(boxLocalPath, file.path))
- if nil == err {
- for _, subFile := range subFiles {
- if strings.HasSuffix(subFile.Name(), ".sy") {
- doc.SubFileCount++
- }
- }
- }
- docs = append(docs, doc)
- }
- continue
- }
- subFolder := filepath.Join(boxLocalPath, strings.TrimSuffix(file.path, ".sy"))
- if gulu.File.IsDir(subFolder) {
- continue
- }
- if ial := box.docIAL(file.path); nil != ial {
- doc := box.docFromFileInfo(file, ial)
- docs = append(docs, doc)
- continue
- }
- }
- elapsed = time.Now().Sub(start).Milliseconds()
- if 500 < elapsed {
- logging.LogWarnf("build docs elapsed [%dms]", elapsed)
- }
- 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 {
- logging.LogWarnf("query root block ref count elapsed [%dms]", elapsed)
- }
- start = time.Now()
- switch sortMode {
- case util.SortModeNameASC:
- sort.Slice(docs, func(i, j int) bool {
- return util.PinYinCompare(util.RemoveEmoji(docs[i].Name), util.RemoveEmoji(docs[j].Name))
- })
- case util.SortModeNameDESC:
- sort.Slice(docs, func(i, j int) bool {
- return util.PinYinCompare(util.RemoveEmoji(docs[j].Name), util.RemoveEmoji(docs[i].Name))
- })
- 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 {
- return natsort.Compare(util.RemoveEmoji(docs[i].Name), util.RemoveEmoji(docs[j].Name))
- })
- case util.SortModeAlphanumDESC:
- sort.Slice(docs, func(i, j int) bool {
- return natsort.Compare(util.RemoveEmoji(docs[j].Name), util.RemoveEmoji(docs[i].Name))
- })
- 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...)
- if Conf.FileTree.MaxListCount < len(ret) {
- ret = ret[:Conf.FileTree.MaxListCount]
- }
- 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 })
- 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 })
- 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 })
- }
- if util.SortModeCustom != sortMode {
- ret = append(ret, docs...)
- }
- if Conf.FileTree.MaxListCount < len(ret) {
- ret = ret[:Conf.FileTree.MaxListCount]
- }
- ret = ret[:]
- elapsed = time.Now().Sub(start).Milliseconds()
- if 200 < elapsed {
- logging.LogInfof("sort docs elapsed [%dms]", elapsed)
- }
- return
- }
- func ContentStat(content string) (ret *util.BlockStatResult) {
- luteEngine := NewLute()
- tree := luteEngine.BlockDOM2Tree(content)
- runeCnt, wordCnt, linkCnt, imgCnt, refCnt := tree.Root.Stat()
- return &util.BlockStatResult{
- RuneCount: runeCnt,
- WordCount: wordCnt,
- LinkCount: linkCnt,
- ImageCount: imgCnt,
- RefCount: refCnt,
- }
- }
- func BlocksWordCount(ids []string) (ret *util.BlockStatResult) {
- ret = &util.BlockStatResult{}
- trees := map[string]*parse.Tree{} // 缓存
- for _, id := range ids {
- bt := treenode.GetBlockTree(id)
- if nil == bt {
- logging.LogWarnf("block tree not found [%s]", id)
- continue
- }
- tree := trees[bt.RootID]
- if nil == tree {
- tree, _ = LoadTree(bt.BoxID, bt.Path)
- if nil == tree {
- continue
- }
- trees[bt.RootID] = tree
- }
- node := treenode.GetNodeInTree(tree, id)
- runeCnt, wordCnt, linkCnt, imgCnt, refCnt := node.Stat()
- ret.RuneCount += runeCnt
- ret.WordCount += wordCnt
- ret.LinkCount += linkCnt
- ret.ImageCount += imgCnt
- ret.RefCount += refCnt
- }
- return
- }
- func StatTree(id string) (ret *util.BlockStatResult) {
- WaitForWritingFiles()
- tree, _ := loadTreeByBlockID(id)
- if nil == tree {
- return
- }
- runeCnt, wordCnt, linkCnt, imgCnt, refCnt := tree.Root.Stat()
- return &util.BlockStatResult{
- RuneCount: runeCnt,
- WordCount: wordCnt,
- LinkCount: linkCnt,
- ImageCount: imgCnt,
- RefCount: refCnt,
- }
- }
- const (
- searchMarkSpanStart = "<span data-type=\"search-mark\">"
- searchMarkSpanEnd = "</span>"
- virtualBlockRefSpanStart = "<span data-type=\"virtual-block-ref\">"
- virtualBlockRefSpanEnd = "</span>"
- )
- func GetDoc(startID, endID, id string, index int, keyword string, mode int, size int) (blockCount, childBlockCount int, dom, parentID, parent2ID, rootID, typ string, eof bool, boxID, docPath string, err error) {
- WaitForWritingFiles() // 写入数据时阻塞,避免获取到的数据不一致
- inputIndex := index
- tree, err := loadTreeByBlockID(id)
- if nil != err {
- 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 {
- err = ErrBlockNotFound
- return
- }
- located := false
- isDoc := ast.NodeDocument == node.Type
- isHeading := ast.NodeHeading == node.Type
- 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()
- childBlockCount = treenode.CountBlockNodes(tree.Root)
- 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()
- }
- var nodes []*ast.Node
- // 如果同时存在 startID 和 endID,则只加载 startID 和 endID 之间的块 [startID, endID]
- if "" != startID && "" != endID {
- nodes, eof = loadNodesByStartEnd(tree, startID, endID)
- if 1 > len(nodes) {
- // 按 mode 加载兜底
- nodes, eof = loadNodesByMode(node, inputIndex, mode, size, isDoc, isHeading)
- }
- } else {
- nodes, eof = loadNodesByMode(node, inputIndex, mode, size, isDoc, isHeading)
- }
- refCount := sql.QueryRootChildrenRefCount(rootID)
- virtualBlockRefKeywords := getVirtualRefKeywords(tree.Root.IALAttr("title"))
- subTree := &parse.Tree{ID: rootID, Root: &ast.Node{Type: ast.NodeDocument}, Marks: tree.Marks}
- keyword = strings.Join(strings.Split(keyword, " "), search.TermSep)
- keywords := search.SplitKeyword(keyword)
- 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") {
- unlinks = append(unlinks, n)
- return ast.WalkContinue
- }
- if "" != n.ID {
- // 填充块引计数
- if cnt := refCount[n.ID]; 0 < cnt {
- n.SetIALAttr("refcount", strconv.Itoa(cnt))
- }
- }
- // 支持代码块搜索定位 https://github.com/siyuan-note/siyuan/issues/5520
- if ast.NodeCodeBlockCode == n.Type && 0 < len(keywords) && !treenode.IsChartCodeBlockCode(n) {
- text := string(n.Tokens)
- text = search.EncloseHighlighting(text, keywords, search.SearchMarkLeft, search.SearchMarkRight, Conf.Search.CaseSensitive)
- n.Tokens = gulu.Str.ToBytes(text)
- }
- if ast.NodeText == n.Type {
- if 0 < len(keywords) {
- hitBlock := false
- for p := n.Parent; nil != p; p = p.Parent {
- if p.ID == id {
- hitBlock = true
- break
- }
- }
- if hitBlock {
- if markReplaceSpan(n, &unlinks, string(n.Tokens), keywords, searchMarkSpanStart, searchMarkSpanEnd, luteEngine) {
- return ast.WalkContinue
- }
- }
- }
- if processVirtualRef(n, &unlinks, virtualBlockRefKeywords, refCount, luteEngine) {
- return ast.WalkContinue
- }
- }
- return ast.WalkContinue
- })
- for _, unlink := range unlinks {
- unlink.Unlink()
- }
- subTree.Root.AppendChild(n)
- }
- luteEngine.RenderOptions.NodeIndexStart = index
- dom = luteEngine.Tree2BlockDOM(subTree, luteEngine.RenderOptions)
- return
- }
- 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 {
- if treenode.IsInFoldedHeading(n, nil) {
- continue
- }
- nodes = append(nodes, n)
- if n.ID == endID {
- if next := n.Next; nil == next {
- eof = true
- } else {
- eof = util2.IsDocIAL(n.Tokens) || util2.IsDocIAL(next.Tokens)
- }
- break
- }
- }
- return
- }
- func loadNodesByMode(node *ast.Node, inputIndex, mode, size int, isDoc, isHeading bool) (nodes []*ast.Node, eof bool) {
- if 2 == mode /* 向下 */ {
- next := node.Next
- if ast.NodeHeading == node.Type && "1" == node.IALAttr("fold") {
- // 标题展开时进行动态加载导致重复内容 https://github.com/siyuan-note/siyuan/issues/4671
- // 这里要考虑折叠标题是最后一个块的情况
- if children := treenode.HeadingChildren(node); 0 < len(children) {
- 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 {
- if treenode.IsInFoldedHeading(n, nil) {
- 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 {
- if treenode.IsInFoldedHeading(n, node) {
- // 大纲点击折叠标题跳转聚焦 https://github.com/siyuan-note/siyuan/issues/4920
- // 多级标题折叠后上级块引浮窗中未折叠 https://github.com/siyuan-note/siyuan/issues/4997
- continue
- }
- if ast.NodeHeading == n.Type {
- if n.HeadingLevel <= level {
- break
- }
- } else if ast.NodeSuperBlock == n.Type {
- if h := treenode.SuperBlockHeading(n); nil != h {
- if level >= h.HeadingLevel {
- break
- }
- }
- }
- nodes = append(nodes, n)
- count++
- if size < count {
- break
- }
- }
- }
- case 4: // Ctrl+End 跳转到末尾后向上加载
- for n := node; nil != n; n = n.Previous {
- if treenode.IsInFoldedHeading(n, nil) {
- 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 {
- if treenode.IsInFoldedHeading(n, nil) {
- 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 {
- if treenode.IsInFoldedHeading(n, node) {
- 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 {
- if treenode.IsInFoldedHeading(n, nil) {
- 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 {
- if treenode.IsInFoldedHeading(n, nil) {
- 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
- }
- func writeJSONQueue(tree *parse.Tree) (err error) {
- if err = filesys.WriteTree(tree); nil != err {
- return
- }
- sql.UpsertTreeQueue(tree)
- return
- }
- func writeJSONQueueWithoutChangeTime(tree *parse.Tree) (err error) {
- if err = filesys.WriteTreeWithoutChangeTime(tree); nil != err {
- return
- }
- sql.UpsertTreeQueue(tree)
- return
- }
- func indexWriteJSONQueue(tree *parse.Tree) (err error) {
- treenode.ReindexBlockTree(tree)
- return writeJSONQueue(tree)
- }
- func indexWriteJSONQueueWithoutChangeTime(tree *parse.Tree) (err error) {
- treenode.ReindexBlockTree(tree)
- return writeJSONQueueWithoutChangeTime(tree)
- }
- func renameWriteJSONQueue(tree *parse.Tree, oldHPath string) (err error) {
- if err = filesys.WriteTree(tree); nil != err {
- return
- }
- sql.RenameTreeQueue(tree, oldHPath)
- treenode.ReindexBlockTree(tree)
- return
- }
- func DuplicateDoc(rootID string) (ret *parse.Tree, err error) {
- msgId := util.PushMsg(Conf.Language(116), 30000)
- defer util.PushClearMsg(msgId)
- WaitForWritingFiles()
- ret, err = loadTreeByBlockID(rootID)
- if nil != err {
- return
- }
- resetTree(ret, "Duplicated")
- createTreeTx(ret)
- sql.WaitForWritingDatabase()
- return
- }
- func createTreeTx(tree *parse.Tree) {
- transaction := &Transaction{DoOperations: []*Operation{{Action: "create", Data: tree}}}
- err := PerformTransactions(&[]*Transaction{transaction})
- if nil != err {
- tx, txErr := sql.BeginTx()
- if nil != txErr {
- logging.LogFatalf("transaction failed: %s", txErr)
- return
- }
- sql.ClearBoxHash(tx)
- sql.CommitTx(tx)
- logging.LogFatalf("transaction failed: %s", err)
- return
- }
- }
- func CreateDocByMd(boxID, p, title, md string, sorts []string) (err error) {
- WaitForWritingFiles()
- box := Conf.Box(boxID)
- if nil == box {
- return errors.New(Conf.Language(0))
- }
- luteEngine := NewLute()
- dom := luteEngine.Md2BlockDOM(md, false)
- err = createDoc(box.ID, p, title, dom)
- if nil != err {
- return
- }
- ChangeFileTreeSort(box.ID, sorts)
- return
- }
- func CreateWithMarkdown(boxID, hPath, md string) (id string, err error) {
- box := Conf.Box(boxID)
- if nil == box {
- err = errors.New(Conf.Language(0))
- return
- }
- WaitForWritingFiles()
- luteEngine := NewLute()
- dom := luteEngine.Md2BlockDOM(md, false)
- id, _, err = createDocsByHPath(box.ID, hPath, dom)
- return
- }
- func GetHPathByPath(boxID, p string) (hPath string, err error) {
- if "/" == p {
- hPath = "/"
- return
- }
- tree, err := LoadTree(boxID, p)
- if nil != err {
- return
- }
- hPath = tree.HPath
- return
- }
- 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
- }
- hPaths = append(hPaths, box.Name+bt.HPath)
- }
- return
- }
- func GetHPathByID(id string) (hPath string, err error) {
- tree, err := loadTreeByBlockID(id)
- if nil != err {
- return
- }
- hPath = tree.HPath
- return
- }
- func GetFullHPathByID(id string) (hPath string, err error) {
- tree, err := loadTreeByBlockID(id)
- if nil != err {
- return
- }
- box := Conf.Box(tree.Box)
- var boxName string
- if nil != box {
- boxName = box.Name
- }
- hPath = boxName + tree.HPath
- return
- }
- func MoveDocs(fromPaths []string, toBoxID, toPath string) (err error) {
- toBox := Conf.Box(toBoxID)
- if nil == toBox {
- err = errors.New(Conf.Language(0))
- return
- }
- fromPaths = util.FilterMoveDocFromPaths(fromPaths, toPath)
- if 1 > len(fromPaths) {
- return
- }
- pathsBoxes := getBoxesByPaths(fromPaths)
- // 检查路径深度是否超过限制
- 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
- }
- }
- needShowProgress := 16 < len(fromPaths)
- if needShowProgress {
- util.PushEndlessProgress(Conf.Language(116))
- }
- WaitForWritingFiles()
- for fromPath, fromBox := range pathsBoxes {
- _, err = moveDoc(fromBox, fromPath, toBox, toPath)
- if nil != err {
- return
- }
- }
- cache.ClearDocsIAL()
- IncSync()
- if needShowProgress {
- util.PushEndlessProgress(Conf.Language(113))
- sql.WaitForWritingDatabase()
- util.ReloadUI()
- }
- return
- }
- func moveDoc(fromBox *Box, fromPath string, toBox *Box, toPath string) (newPath string, err error) {
- isSameBox := fromBox.ID == toBox.ID
- if isSameBox {
- if !fromBox.Exist(toPath) {
- err = ErrBlockNotFound
- return
- }
- } else {
- if !toBox.Exist(toPath) {
- err = ErrBlockNotFound
- return
- }
- }
- tree, err := LoadTree(fromBox.ID, fromPath)
- if nil != err {
- err = ErrBlockNotFound
- return
- }
- moveToRoot := "/" == toPath
- toBlockID := tree.ID
- fromFolder := path.Join(path.Dir(fromPath), tree.ID)
- toFolder := "/"
- if !moveToRoot {
- var toTree *parse.Tree
- if isSameBox {
- toTree, err = LoadTree(fromBox.ID, toPath)
- } else {
- toTree, err = LoadTree(toBox.ID, toPath)
- }
- if nil != err {
- err = ErrBlockNotFound
- return
- }
- toBlockID = toTree.ID
- toFolder = path.Join(path.Dir(toPath), toBlockID)
- }
- if isSameBox {
- if err = fromBox.MkdirAll(toFolder); nil != err {
- return
- }
- } else {
- if err = toBox.MkdirAll(toFolder); nil != err {
- return
- }
- }
- if fromBox.Exist(fromFolder) {
- // 移动子文档文件夹
- newFolder := path.Join(toFolder, tree.ID)
- if isSameBox {
- if err = fromBox.Move(fromFolder, newFolder); nil != err {
- return
- }
- } else {
- absFromPath := filepath.Join(util.DataDir, fromBox.ID, fromFolder)
- absToPath := filepath.Join(util.DataDir, toBox.ID, newFolder)
- if gulu.File.IsExist(absToPath) {
- filelock.Remove(absToPath)
- }
- if err = filelock.Move(absFromPath, absToPath); nil != err {
- msg := fmt.Sprintf(Conf.Language(5), fromBox.Name, fromPath, err)
- logging.LogErrorf("move [path=%s] in box [%s] failed: %s", fromPath, fromBox.ID, err)
- err = errors.New(msg)
- return
- }
- }
- }
- newPath = path.Join(toFolder, tree.ID+".sy")
- if isSameBox {
- if err = fromBox.Move(fromPath, newPath); nil != err {
- return
- }
- tree, err = LoadTree(fromBox.ID, newPath)
- if nil != err {
- return
- }
- moveTree(tree)
- } else {
- absFromPath := filepath.Join(util.DataDir, fromBox.ID, fromPath)
- absToPath := filepath.Join(util.DataDir, toBox.ID, newPath)
- if err = filelock.Move(absFromPath, absToPath); nil != err {
- msg := fmt.Sprintf(Conf.Language(5), fromBox.Name, fromPath, err)
- logging.LogErrorf("move [path=%s] in box [%s] failed: %s", fromPath, fromBox.ID, err)
- err = errors.New(msg)
- return
- }
- tree, err = LoadTree(toBox.ID, newPath)
- if nil != err {
- return
- }
- moveTree(tree)
- moveSorts(tree.ID, fromBox.ID, toBox.ID)
- }
- evt := util.NewCmdResult("moveDoc", 0, util.PushModeBroadcast, util.PushModeNone)
- evt.Data = map[string]interface{}{
- "fromNotebook": fromBox.ID,
- "fromPath": fromPath,
- "toNotebook": toBox.ID,
- "toPath": toPath,
- "newPath": newPath,
- }
- util.PushEvent(evt)
- return
- }
- func RemoveDoc(boxID, p string) (err error) {
- box := Conf.Box(boxID)
- if nil == box {
- err = errors.New(Conf.Language(0))
- return
- }
- WaitForWritingFiles()
- err = removeDoc(box, p)
- if nil != err {
- return
- }
- IncSync()
- return
- }
- func RemoveDocs(paths []string) (err error) {
- util.PushEndlessProgress(Conf.Language(116))
- defer util.PushClearProgress()
- paths = util.FilterSelfChildDocs(paths)
- pathsBoxes := getBoxesByPaths(paths)
- WaitForWritingFiles()
- for p, box := range pathsBoxes {
- err = removeDoc(box, p)
- if nil != err {
- return
- }
- }
- return
- }
- func removeDoc(box *Box, p string) (err error) {
- tree, err := LoadTree(box.ID, p)
- if nil != err {
- return
- }
- historyDir, err := GetHistoryDir(HistoryOpDelete)
- if nil != err {
- logging.LogErrorf("get history dir failed: %s", err)
- return
- }
- historyPath := filepath.Join(historyDir, box.ID, p)
- absPath := filepath.Join(util.DataDir, box.ID, p)
- if err = filelock.Copy(absPath, historyPath); nil != err {
- return errors.New(fmt.Sprintf(Conf.Language(70), box.Name, absPath, err))
- }
- copyDocAssetsToDataAssets(box.ID, p)
- var removeIDs []string
- ids := treenode.RootChildIDs(tree.ID)
- removeIDs = append(removeIDs, ids...)
- dir := path.Dir(p)
- childrenDir := path.Join(dir, tree.ID)
- existChildren := box.Exist(childrenDir)
- if existChildren {
- absChildrenDir := filepath.Join(util.DataDir, tree.Box, childrenDir)
- historyPath = filepath.Join(historyDir, tree.Box, childrenDir)
- if err = filelock.Copy(absChildrenDir, historyPath); nil != err {
- return
- }
- }
- indexHistoryDir(filepath.Base(historyDir), NewLute())
- if existChildren {
- if err = box.Remove(childrenDir); nil != err {
- return
- }
- }
- if err = box.Remove(p); nil != err {
- return
- }
- box.removeSort(removeIDs)
- treenode.RemoveBlockTreesByPathPrefix(childrenDir)
- sql.RemoveTreePathQueue(box.ID, childrenDir)
- if "/" != dir {
- others, err := os.ReadDir(filepath.Join(util.DataDir, box.ID, dir))
- if nil == err && 1 > len(others) {
- box.Remove(dir)
- }
- }
- cache.RemoveDocIAL(p)
- evt := util.NewCmdResult("removeDoc", 0, util.PushModeBroadcast, util.PushModeNone)
- evt.Data = map[string]interface{}{
- "ids": removeIDs,
- }
- util.PushEvent(evt)
- return
- }
- func RenameDoc(boxID, p, title string) (err error) {
- box := Conf.Box(boxID)
- if nil == box {
- err = errors.New(Conf.Language(0))
- return
- }
- WaitForWritingFiles()
- tree, err := LoadTree(box.ID, p)
- if nil != err {
- return
- }
- title = gulu.Str.RemoveInvisible(title)
- if 512 < utf8.RuneCountInString(title) {
- // 限制笔记本名和文档名最大长度为 `512` https://github.com/siyuan-note/siyuan/issues/6299
- return errors.New(Conf.Language(106))
- }
- oldTitle := tree.Root.IALAttr("title")
- if oldTitle == title {
- return
- }
- if "" == title {
- title = "Untitled"
- }
- title = strings.ReplaceAll(title, "/", "")
- oldHPath := tree.HPath
- tree.HPath = path.Join(path.Dir(tree.HPath), title)
- tree.Root.SetIALAttr("title", title)
- tree.Root.SetIALAttr("updated", util.CurrentTimeSecondsStr())
- if err = renameWriteJSONQueue(tree, oldHPath); nil != err {
- return
- }
- refText := getNodeRefText(tree.Root)
- evt := util.NewCmdResult("rename", 0, util.PushModeBroadcast, util.PushModeNone)
- evt.Data = map[string]interface{}{
- "box": boxID,
- "id": tree.Root.ID,
- "path": p,
- "title": title,
- "refText": refText,
- }
- util.PushEvent(evt)
- box.renameSubTrees(tree)
- updateRefTextRenameDoc(tree)
- IncSync()
- return
- }
- func CreateDailyNote(boxID string) (p string, existed bool, err error) {
- 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 := RenderCreateDocNameTemplate(boxConf.DailyNoteSavePath)
- if nil != err {
- return
- }
- WaitForWritingFiles()
- existRoot := treenode.GetBlockTreeRootByHPath(box.ID, hPath)
- if nil != existRoot {
- existed = true
- p = existRoot.Path
- return
- }
- id, existed, err := createDocsByHPath(box.ID, hPath, "")
- if nil != err {
- return
- }
- var dom string
- if "" != boxConf.DailyNoteTemplatePath {
- tplPath := filepath.Join(util.DataDir, "templates", boxConf.DailyNoteTemplatePath)
- if !gulu.File.IsExist(tplPath) {
- logging.LogWarnf("not found daily note template [%s]", tplPath)
- } else {
- dom, err = renderTemplate(tplPath, id)
- if nil != err {
- logging.LogWarnf("render daily note template [%s] failed: %s", boxConf.DailyNoteTemplatePath, err)
- }
- }
- }
- if "" != dom {
- var tree *parse.Tree
- tree, err = loadTreeByBlockID(id)
- if nil == err {
- tree.Root.FirstChild.Unlink()
- luteEngine := NewLute()
- newTree := luteEngine.BlockDOM2Tree(dom)
- 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)
- }
- tree.Root.SetIALAttr("updated", util.CurrentTimeSecondsStr())
- if err = indexWriteJSONQueue(tree); nil != err {
- return
- }
- }
- }
- IncSync()
- b := treenode.GetBlockTree(id)
- p = b.Path
- return
- }
- func createDoc(boxID, p, title, dom string) (err error) {
- title = gulu.Str.RemoveInvisible(title)
- if 512 < utf8.RuneCountInString(title) {
- // 限制笔记本名和文档名最大长度为 `512` https://github.com/siyuan-note/siyuan/issues/6299
- return errors.New(Conf.Language(106))
- }
- title = strings.ReplaceAll(title, "/", "")
- baseName := strings.TrimSpace(path.Base(p))
- if "" == strings.TrimSuffix(baseName, ".sy") {
- return errors.New(Conf.Language(16))
- }
- if strings.HasPrefix(baseName, ".") {
- return errors.New(Conf.Language(13))
- }
- box := Conf.Box(boxID)
- if nil == box {
- return errors.New(Conf.Language(0))
- }
- id := strings.TrimSuffix(path.Base(p), ".sy")
- var hPath string
- folder := path.Dir(p)
- if "/" != folder {
- parentID := path.Base(folder)
- parentTree, err := loadTreeByBlockID(parentID)
- if nil != err {
- logging.LogErrorf("get parent tree [id=%s] failed", parentID)
- return ErrBlockNotFound
- }
- 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) {
- if err = box.MkdirAll(folder); nil != err {
- return err
- }
- }
- if box.Exist(p) {
- return errors.New(Conf.Language(1))
- }
- var tree *parse.Tree
- luteEngine := NewLute()
- tree = luteEngine.BlockDOM2Tree(dom)
- tree.Box = boxID
- tree.Path = p
- tree.HPath = hPath
- tree.ID = id
- tree.Root.ID = id
- tree.Root.Spec = "1"
- updated := util.TimeFromID(id)
- tree.Root.KramdownIAL = [][]string{{"id", id}, {"title", html.EscapeAttrVal(title)}, {"updated", updated}}
- if nil == tree.Root.FirstChild {
- tree.Root.AppendChild(parse.NewParagraph())
- }
- transaction := &Transaction{DoOperations: []*Operation{{Action: "create", Data: tree}}}
- err = PerformTransactions(&[]*Transaction{transaction})
- if nil != err {
- tx, txErr := sql.BeginTx()
- if nil != txErr {
- logging.LogFatalf("transaction failed: %s", txErr)
- return
- }
- sql.ClearBoxHash(tx)
- sql.CommitTx(tx)
- logging.LogFatalf("transaction failed: %s", err)
- return
- }
- WaitForWritingFiles()
- return
- }
- func moveSorts(rootID, fromBox, toBox string) {
- root := treenode.GetBlockTree(rootID)
- if nil == root {
- return
- }
- fromRootSorts := map[string]int{}
- ids := treenode.RootChildIDs(rootID)
- fromConfPath := filepath.Join(util.DataDir, fromBox, ".siyuan", "sort.json")
- fromFullSortIDs := map[string]int{}
- if gulu.File.IsExist(fromConfPath) {
- data, err := filelock.ReadFile(fromConfPath)
- if nil != err {
- logging.LogErrorf("read sort conf failed: %s", err)
- return
- }
- if err = gulu.JSON.UnmarshalJSON(data, &fromFullSortIDs); nil != err {
- logging.LogErrorf("unmarshal sort conf failed: %s", err)
- }
- }
- for _, id := range ids {
- fromRootSorts[id] = fromFullSortIDs[id]
- }
- toConfPath := filepath.Join(util.DataDir, toBox, ".siyuan", "sort.json")
- toFullSortIDs := map[string]int{}
- if gulu.File.IsExist(toConfPath) {
- data, err := filelock.ReadFile(toConfPath)
- if nil != err {
- logging.LogErrorf("read sort conf failed: %s", err)
- return
- }
- if err = gulu.JSON.UnmarshalJSON(data, &toFullSortIDs); nil != err {
- logging.LogErrorf("unmarshal sort conf failed: %s", err)
- return
- }
- }
- for id, sortVal := range fromRootSorts {
- toFullSortIDs[id] = sortVal
- }
- data, err := gulu.JSON.MarshalIndentJSON(toFullSortIDs, "", " ")
- if nil != err {
- logging.LogErrorf("marshal sort conf failed: %s", err)
- return
- }
- if err = filelock.WriteFile(toConfPath, data); nil != err {
- logging.LogErrorf("write sort conf failed: %s", err)
- return
- }
- }
- func ChangeFileTreeSort(boxID string, paths []string) {
- if 1 > len(paths) {
- return
- }
- WaitForWritingFiles()
- box := Conf.Box(boxID)
- sortIDs := map[string]int{}
- max := 0
- for i, p := range paths {
- id := strings.TrimSuffix(path.Base(p), ".sy")
- 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)
- if nil != err {
- logging.LogErrorf("read dir [%s] failed: %s", err)
- }
- 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")
- if err = os.MkdirAll(confDir, 0755); nil != err {
- logging.LogErrorf("create conf dir failed: %s", err)
- return
- }
- confPath := filepath.Join(confDir, "sort.json")
- fullSortIDs := map[string]int{}
- var data []byte
- if gulu.File.IsExist(confPath) {
- data, err = filelock.ReadFile(confPath)
- if nil != err {
- logging.LogErrorf("read sort conf failed: %s", err)
- return
- }
- if err = gulu.JSON.UnmarshalJSON(data, &fullSortIDs); nil != err {
- logging.LogErrorf("unmarshal sort conf failed: %s", err)
- }
- }
- for sortID, sortVal := range sortFolderIDs {
- fullSortIDs[sortID] = sortVal
- }
- data, err = gulu.JSON.MarshalIndentJSON(fullSortIDs, "", " ")
- if nil != err {
- logging.LogErrorf("marshal sort conf failed: %s", err)
- return
- }
- if err = filelock.WriteFile(confPath, data); nil != err {
- logging.LogErrorf("write sort conf failed: %s", err)
- return
- }
- IncSync()
- }
- func (box *Box) fillSort(files *[]*File) {
- confPath := filepath.Join(util.DataDir, box.ID, ".siyuan", "sort.json")
- if !gulu.File.IsExist(confPath) {
- return
- }
- data, err := filelock.ReadFile(confPath)
- if nil != err {
- logging.LogErrorf("read sort conf failed: %s", err)
- return
- }
- fullSortIDs := map[string]int{}
- if err = gulu.JSON.UnmarshalJSON(data, &fullSortIDs); nil != err {
- logging.LogErrorf("unmarshal sort conf failed: %s", err)
- return
- }
- for _, f := range *files {
- id := strings.TrimSuffix(f.ID, ".sy")
- f.Sort = fullSortIDs[id]
- }
- }
- func (box *Box) removeSort(ids []string) {
- confPath := filepath.Join(util.DataDir, box.ID, ".siyuan", "sort.json")
- if !gulu.File.IsExist(confPath) {
- return
- }
- data, err := filelock.ReadFile(confPath)
- if nil != err {
- logging.LogErrorf("read sort conf failed: %s", err)
- return
- }
- fullSortIDs := map[string]int{}
- if err = gulu.JSON.UnmarshalJSON(data, &fullSortIDs); nil != err {
- logging.LogErrorf("unmarshal sort conf failed: %s", err)
- return
- }
- for _, toRemove := range ids {
- delete(fullSortIDs, toRemove)
- }
- data, err = gulu.JSON.MarshalIndentJSON(fullSortIDs, "", " ")
- if nil != err {
- logging.LogErrorf("marshal sort conf failed: %s", err)
- return
- }
- if err = filelock.WriteFile(confPath, data); nil != err {
- logging.LogErrorf("write sort conf failed: %s", err)
- return
- }
- }
- func ServeFile(c *gin.Context, filePath string) (err error) {
- WaitForWritingFiles()
- c.File(filePath)
- return
- }
|