file.go 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665
  1. // SiYuan - Build Your Eternal Digital Garden
  2. // Copyright (c) 2020-present, b3log.org
  3. //
  4. // This program is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. package model
  17. import (
  18. "errors"
  19. "fmt"
  20. "math"
  21. "os"
  22. "path"
  23. "path/filepath"
  24. "sort"
  25. "strconv"
  26. "strings"
  27. "time"
  28. "unicode/utf8"
  29. "github.com/88250/gulu"
  30. "github.com/88250/lute/ast"
  31. "github.com/88250/lute/html"
  32. "github.com/88250/lute/parse"
  33. util2 "github.com/88250/lute/util"
  34. "github.com/dustin/go-humanize"
  35. "github.com/facette/natsort"
  36. "github.com/gin-gonic/gin"
  37. jsoniter "github.com/json-iterator/go"
  38. "github.com/siyuan-note/filelock"
  39. "github.com/siyuan-note/logging"
  40. "github.com/siyuan-note/siyuan/kernel/cache"
  41. "github.com/siyuan-note/siyuan/kernel/filesys"
  42. "github.com/siyuan-note/siyuan/kernel/search"
  43. "github.com/siyuan-note/siyuan/kernel/sql"
  44. "github.com/siyuan-note/siyuan/kernel/treenode"
  45. "github.com/siyuan-note/siyuan/kernel/util"
  46. )
  47. type File struct {
  48. Path string `json:"path"`
  49. Name string `json:"name"`
  50. Icon string `json:"icon"`
  51. Name1 string `json:"name1"`
  52. Alias string `json:"alias"`
  53. Memo string `json:"memo"`
  54. Bookmark string `json:"bookmark"`
  55. ID string `json:"id"`
  56. Count int `json:"count"`
  57. Size uint64 `json:"size"`
  58. HSize string `json:"hSize"`
  59. Mtime int64 `json:"mtime"`
  60. CTime int64 `json:"ctime"`
  61. HMtime string `json:"hMtime"`
  62. HCtime string `json:"hCtime"`
  63. Sort int `json:"sort"`
  64. SubFileCount int `json:"subFileCount"`
  65. }
  66. func (box *Box) docFromFileInfo(fileInfo *FileInfo, ial map[string]string) (ret *File) {
  67. ret = &File{}
  68. ret.Path = fileInfo.path
  69. ret.Size = uint64(fileInfo.size)
  70. ret.Name = ial["title"] + ".sy"
  71. ret.Icon = ial["icon"]
  72. ret.ID = ial["id"]
  73. ret.Name1 = ial["name"]
  74. ret.Alias = ial["alias"]
  75. ret.Memo = ial["memo"]
  76. ret.Bookmark = ial["bookmark"]
  77. t, _ := time.ParseInLocation("20060102150405", ret.ID[:14], time.Local)
  78. ret.CTime = t.Unix()
  79. ret.HCtime = t.Format("2006-01-02 15:04:05")
  80. ret.HSize = humanize.Bytes(ret.Size)
  81. mTime := t
  82. if updated := ial["updated"]; "" != updated {
  83. if updatedTime, err := time.ParseInLocation("20060102150405", updated, time.Local); nil == err {
  84. mTime = updatedTime
  85. }
  86. }
  87. ret.Mtime = mTime.Unix()
  88. ret.HMtime = HumanizeTime(mTime)
  89. return
  90. }
  91. func HumanizeTime(then time.Time) string {
  92. labels := timeLangs[Conf.Lang]
  93. defaultMagnitudes := []humanize.RelTimeMagnitude{
  94. {time.Second, labels["now"].(string), time.Second},
  95. {2 * time.Second, labels["1s"].(string), 1},
  96. {time.Minute, labels["xs"].(string), time.Second},
  97. {2 * time.Minute, labels["1m"].(string), 1},
  98. {time.Hour, labels["xm"].(string), time.Minute},
  99. {2 * time.Hour, labels["1h"].(string), 1},
  100. {humanize.Day, labels["xh"].(string), time.Hour},
  101. {2 * humanize.Day, labels["1d"].(string), 1},
  102. {humanize.Week, labels["xd"].(string), humanize.Day},
  103. {2 * humanize.Week, labels["1w"].(string), 1},
  104. {humanize.Month, labels["xw"].(string), humanize.Week},
  105. {2 * humanize.Month, labels["1M"].(string), 1},
  106. {humanize.Year, labels["xM"].(string), humanize.Month},
  107. {18 * humanize.Month, labels["1y"].(string), 1},
  108. {2 * humanize.Year, labels["2y"].(string), 1},
  109. {humanize.LongTime, labels["xy"].(string), humanize.Year},
  110. {math.MaxInt64, labels["max"].(string), 1},
  111. }
  112. return humanize.CustomRelTime(then, time.Now(), labels["albl"].(string), labels["blbl"].(string), defaultMagnitudes)
  113. }
  114. func (box *Box) docIAL(p string) (ret map[string]string) {
  115. name := strings.ToLower(filepath.Base(p))
  116. if !strings.HasSuffix(name, ".sy") {
  117. return nil
  118. }
  119. ret = cache.GetDocIAL(p)
  120. if nil != ret {
  121. return ret
  122. }
  123. filePath := filepath.Join(util.DataDir, box.ID, p)
  124. data, err := filelock.ReadFile(filePath)
  125. if util.IsCorruptedSYData(data) {
  126. box.moveCorruptedData(filePath)
  127. return nil
  128. }
  129. if nil != err {
  130. logging.LogErrorf("read file [%s] failed: %s", p, err)
  131. return nil
  132. }
  133. ret = readDocIAL(data)
  134. if 1 > len(ret) {
  135. logging.LogWarnf("tree [%s] is corrupted", filePath)
  136. box.moveCorruptedData(filePath)
  137. return nil
  138. }
  139. cache.PutDocIAL(p, ret)
  140. return ret
  141. }
  142. func (box *Box) moveCorruptedData(filePath string) {
  143. base := filepath.Base(filePath)
  144. to := filepath.Join(util.WorkspaceDir, "corrupted", time.Now().Format("2006-01-02-150405"), box.ID, base)
  145. if copyErr := filelock.Copy(filePath, to); nil != copyErr {
  146. logging.LogErrorf("copy corrupted data file [%s] failed: %s", filePath, copyErr)
  147. return
  148. }
  149. if removeErr := filelock.Remove(filePath); nil != removeErr {
  150. logging.LogErrorf("remove corrupted data file [%s] failed: %s", filePath, removeErr)
  151. return
  152. }
  153. logging.LogWarnf("moved corrupted data file [%s] to [%s]", filePath, to)
  154. }
  155. func readDocIAL(data []byte) (ret map[string]string) {
  156. ret = map[string]string{}
  157. jsoniter.Get(data, "Properties").ToVal(&ret)
  158. return
  159. }
  160. func SearchDocsByKeyword(keyword string) (ret []map[string]string) {
  161. ret = []map[string]string{}
  162. openedBoxes := Conf.GetOpenedBoxes()
  163. boxes := map[string]*Box{}
  164. for _, box := range openedBoxes {
  165. boxes[box.ID] = box
  166. }
  167. var rootBlocks []*sql.Block
  168. if "" != keyword {
  169. for _, box := range boxes {
  170. if strings.Contains(box.Name, keyword) {
  171. ret = append(ret, map[string]string{"path": "/", "hPath": box.Name + "/", "box": box.ID, "boxIcon": box.Icon})
  172. }
  173. }
  174. condition := "hpath LIKE '%" + keyword + "%'"
  175. if "" != keyword {
  176. namCondition := Conf.Search.NAMFilter(keyword)
  177. if "" != namCondition {
  178. condition += " " + namCondition
  179. }
  180. }
  181. rootBlocks = sql.QueryRootBlockByCondition(condition)
  182. } else {
  183. for _, box := range boxes {
  184. ret = append(ret, map[string]string{"path": "/", "hPath": box.Name + "/", "box": box.ID, "boxIcon": box.Icon})
  185. }
  186. }
  187. for _, block := range rootBlocks {
  188. b := boxes[block.Box]
  189. if nil == b {
  190. continue
  191. }
  192. hPath := b.Name + block.HPath
  193. ret = append(ret, map[string]string{"path": block.Path, "hPath": hPath, "box": block.Box, "boxIcon": b.Icon})
  194. }
  195. sort.Slice(ret, func(i, j int) bool {
  196. return ret[i]["hPath"] < ret[j]["hPath"]
  197. })
  198. return
  199. }
  200. type FileInfo struct {
  201. path string
  202. name string
  203. size int64
  204. isdir bool
  205. }
  206. func ListDocTree(boxID, path string, sortMode int) (ret []*File, totals int, err error) {
  207. //os.MkdirAll("pprof", 0755)
  208. //cpuProfile, _ := os.Create("pprof/cpu_profile_list_doc_tree")
  209. //pprof.StartCPUProfile(cpuProfile)
  210. //defer pprof.StopCPUProfile()
  211. ret = []*File{}
  212. box := Conf.Box(boxID)
  213. if nil == box {
  214. return nil, 0, errors.New(Conf.Language(0))
  215. }
  216. var files []*FileInfo
  217. start := time.Now()
  218. files, totals, err = box.Ls(path)
  219. if nil != err {
  220. return
  221. }
  222. elapsed := time.Now().Sub(start).Milliseconds()
  223. if 100 < elapsed {
  224. logging.LogWarnf("ls elapsed [%dms]", elapsed)
  225. }
  226. start = time.Now()
  227. boxLocalPath := filepath.Join(util.DataDir, box.ID)
  228. var docs []*File
  229. for _, file := range files {
  230. if file.isdir {
  231. if !util.IsIDPattern(file.name) {
  232. continue
  233. }
  234. parentDocPath := strings.TrimSuffix(file.path, "/") + ".sy"
  235. subDocFile := box.Stat(parentDocPath)
  236. if nil == subDocFile {
  237. continue
  238. }
  239. if ial := box.docIAL(parentDocPath); nil != ial {
  240. doc := box.docFromFileInfo(subDocFile, ial)
  241. subFiles, err := os.ReadDir(filepath.Join(boxLocalPath, file.path))
  242. if nil == err {
  243. for _, subFile := range subFiles {
  244. if strings.HasSuffix(subFile.Name(), ".sy") {
  245. doc.SubFileCount++
  246. }
  247. }
  248. }
  249. docs = append(docs, doc)
  250. }
  251. continue
  252. }
  253. subFolder := filepath.Join(boxLocalPath, strings.TrimSuffix(file.path, ".sy"))
  254. if gulu.File.IsDir(subFolder) {
  255. continue
  256. }
  257. if ial := box.docIAL(file.path); nil != ial {
  258. doc := box.docFromFileInfo(file, ial)
  259. docs = append(docs, doc)
  260. continue
  261. }
  262. }
  263. elapsed = time.Now().Sub(start).Milliseconds()
  264. if 500 < elapsed {
  265. logging.LogWarnf("build docs elapsed [%dms]", elapsed)
  266. }
  267. start = time.Now()
  268. refCount := sql.QueryRootBlockRefCount()
  269. for _, doc := range docs {
  270. if count := refCount[doc.ID]; 0 < count {
  271. doc.Count = count
  272. }
  273. }
  274. elapsed = time.Now().Sub(start).Milliseconds()
  275. if 500 < elapsed {
  276. logging.LogWarnf("query root block ref count elapsed [%dms]", elapsed)
  277. }
  278. start = time.Now()
  279. switch sortMode {
  280. case util.SortModeNameASC:
  281. sort.Slice(docs, func(i, j int) bool {
  282. return util.PinYinCompare(util.RemoveEmoji(docs[i].Name), util.RemoveEmoji(docs[j].Name))
  283. })
  284. case util.SortModeNameDESC:
  285. sort.Slice(docs, func(i, j int) bool {
  286. return util.PinYinCompare(util.RemoveEmoji(docs[j].Name), util.RemoveEmoji(docs[i].Name))
  287. })
  288. case util.SortModeUpdatedASC:
  289. sort.Slice(docs, func(i, j int) bool { return docs[i].Mtime < docs[j].Mtime })
  290. case util.SortModeUpdatedDESC:
  291. sort.Slice(docs, func(i, j int) bool { return docs[i].Mtime > docs[j].Mtime })
  292. case util.SortModeAlphanumASC:
  293. sort.Slice(docs, func(i, j int) bool {
  294. return natsort.Compare(util.RemoveEmoji(docs[i].Name), util.RemoveEmoji(docs[j].Name))
  295. })
  296. case util.SortModeAlphanumDESC:
  297. sort.Slice(docs, func(i, j int) bool {
  298. return natsort.Compare(util.RemoveEmoji(docs[j].Name), util.RemoveEmoji(docs[i].Name))
  299. })
  300. case util.SortModeCustom:
  301. fileTreeFiles := docs
  302. box.fillSort(&fileTreeFiles)
  303. sort.Slice(fileTreeFiles, func(i, j int) bool {
  304. if fileTreeFiles[i].Sort == fileTreeFiles[j].Sort {
  305. return util.TimeFromID(fileTreeFiles[i].ID) > util.TimeFromID(fileTreeFiles[j].ID)
  306. }
  307. return fileTreeFiles[i].Sort < fileTreeFiles[j].Sort
  308. })
  309. ret = append(ret, fileTreeFiles...)
  310. if Conf.FileTree.MaxListCount < len(ret) {
  311. ret = ret[:Conf.FileTree.MaxListCount]
  312. }
  313. ret = ret[:]
  314. return
  315. case util.SortModeRefCountASC:
  316. sort.Slice(docs, func(i, j int) bool { return docs[i].Count < docs[j].Count })
  317. case util.SortModeRefCountDESC:
  318. sort.Slice(docs, func(i, j int) bool { return docs[i].Count > docs[j].Count })
  319. case util.SortModeCreatedASC:
  320. sort.Slice(docs, func(i, j int) bool { return docs[i].CTime < docs[j].CTime })
  321. case util.SortModeCreatedDESC:
  322. sort.Slice(docs, func(i, j int) bool { return docs[i].CTime > docs[j].CTime })
  323. case util.SortModeSizeASC:
  324. sort.Slice(docs, func(i, j int) bool { return docs[i].Size < docs[j].Size })
  325. case util.SortModeSizeDESC:
  326. sort.Slice(docs, func(i, j int) bool { return docs[i].Size > docs[j].Size })
  327. case util.SortModeSubDocCountASC:
  328. sort.Slice(docs, func(i, j int) bool { return docs[i].SubFileCount < docs[j].SubFileCount })
  329. case util.SortModeSubDocCountDESC:
  330. sort.Slice(docs, func(i, j int) bool { return docs[i].SubFileCount > docs[j].SubFileCount })
  331. }
  332. if util.SortModeCustom != sortMode {
  333. ret = append(ret, docs...)
  334. }
  335. if Conf.FileTree.MaxListCount < len(ret) {
  336. ret = ret[:Conf.FileTree.MaxListCount]
  337. }
  338. ret = ret[:]
  339. elapsed = time.Now().Sub(start).Milliseconds()
  340. if 200 < elapsed {
  341. logging.LogInfof("sort docs elapsed [%dms]", elapsed)
  342. }
  343. return
  344. }
  345. func ContentStat(content string) (ret *util.BlockStatResult) {
  346. luteEngine := NewLute()
  347. tree := luteEngine.BlockDOM2Tree(content)
  348. runeCnt, wordCnt, linkCnt, imgCnt, refCnt := tree.Root.Stat()
  349. return &util.BlockStatResult{
  350. RuneCount: runeCnt,
  351. WordCount: wordCnt,
  352. LinkCount: linkCnt,
  353. ImageCount: imgCnt,
  354. RefCount: refCnt,
  355. }
  356. }
  357. func BlocksWordCount(ids []string) (ret *util.BlockStatResult) {
  358. ret = &util.BlockStatResult{}
  359. trees := map[string]*parse.Tree{} // 缓存
  360. for _, id := range ids {
  361. bt := treenode.GetBlockTree(id)
  362. if nil == bt {
  363. logging.LogWarnf("block tree not found [%s]", id)
  364. continue
  365. }
  366. tree := trees[bt.RootID]
  367. if nil == tree {
  368. tree, _ = LoadTree(bt.BoxID, bt.Path)
  369. if nil == tree {
  370. continue
  371. }
  372. trees[bt.RootID] = tree
  373. }
  374. node := treenode.GetNodeInTree(tree, id)
  375. runeCnt, wordCnt, linkCnt, imgCnt, refCnt := node.Stat()
  376. ret.RuneCount += runeCnt
  377. ret.WordCount += wordCnt
  378. ret.LinkCount += linkCnt
  379. ret.ImageCount += imgCnt
  380. ret.RefCount += refCnt
  381. }
  382. return
  383. }
  384. func StatTree(id string) (ret *util.BlockStatResult) {
  385. WaitForWritingFiles()
  386. tree, _ := loadTreeByBlockID(id)
  387. if nil == tree {
  388. return
  389. }
  390. runeCnt, wordCnt, linkCnt, imgCnt, refCnt := tree.Root.Stat()
  391. return &util.BlockStatResult{
  392. RuneCount: runeCnt,
  393. WordCount: wordCnt,
  394. LinkCount: linkCnt,
  395. ImageCount: imgCnt,
  396. RefCount: refCnt,
  397. }
  398. }
  399. const (
  400. searchMarkSpanStart = "<span data-type=\"search-mark\">"
  401. searchMarkSpanEnd = "</span>"
  402. virtualBlockRefSpanStart = "<span data-type=\"virtual-block-ref\">"
  403. virtualBlockRefSpanEnd = "</span>"
  404. )
  405. 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) {
  406. WaitForWritingFiles() // 写入数据时阻塞,避免获取到的数据不一致
  407. inputIndex := index
  408. tree, err := loadTreeByBlockID(id)
  409. if nil != err {
  410. if ErrBlockNotFound == err {
  411. if 0 == mode {
  412. err = ErrTreeNotFound // 初始化打开文档时如果找不到则关闭编辑器
  413. }
  414. }
  415. return
  416. }
  417. if nil == tree {
  418. err = ErrBlockNotFound
  419. return
  420. }
  421. luteEngine := NewLute()
  422. node := treenode.GetNodeInTree(tree, id)
  423. if nil == node {
  424. err = ErrBlockNotFound
  425. return
  426. }
  427. located := false
  428. isDoc := ast.NodeDocument == node.Type
  429. isHeading := ast.NodeHeading == node.Type
  430. boxID = node.Box
  431. docPath = node.Path
  432. if isDoc {
  433. if 4 == mode { // 加载文档末尾
  434. node = node.LastChild
  435. located = true
  436. // 重新计算 index
  437. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  438. if !entering {
  439. return ast.WalkContinue
  440. }
  441. index++
  442. return ast.WalkContinue
  443. })
  444. } else {
  445. node = node.FirstChild
  446. }
  447. typ = ast.NodeDocument.String()
  448. idx := 0
  449. if 0 < index {
  450. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  451. if !entering || !n.IsChildBlockOf(tree.Root, 1) {
  452. return ast.WalkContinue
  453. }
  454. idx++
  455. if index == idx {
  456. node = n.DocChild()
  457. if "1" == node.IALAttr("heading-fold") {
  458. // 加载到折叠标题下方块的话需要回溯到上方标题块
  459. for h := node.Previous; nil != h; h = h.Previous {
  460. if "1" == h.IALAttr("fold") {
  461. node = h
  462. break
  463. }
  464. }
  465. }
  466. located = true
  467. return ast.WalkStop
  468. }
  469. return ast.WalkContinue
  470. })
  471. }
  472. } else {
  473. if 0 == index && 0 != mode {
  474. // 非文档且没有指定 index 时需要计算 index
  475. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  476. if !entering {
  477. return ast.WalkContinue
  478. }
  479. index++
  480. if id == n.ID {
  481. node = n.DocChild()
  482. located = true
  483. return ast.WalkStop
  484. }
  485. return ast.WalkContinue
  486. })
  487. }
  488. }
  489. if 1 < index && !located {
  490. count := 0
  491. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  492. if !entering {
  493. return ast.WalkContinue
  494. }
  495. count++
  496. if index == count {
  497. node = n.DocChild()
  498. return ast.WalkStop
  499. }
  500. return ast.WalkContinue
  501. })
  502. }
  503. blockCount = tree.DocBlockCount()
  504. childBlockCount = treenode.CountBlockNodes(tree.Root)
  505. if ast.NodeDocument == node.Type {
  506. parentID = node.ID
  507. parent2ID = parentID
  508. } else {
  509. parentID = node.Parent.ID
  510. parent2ID = parentID
  511. tmp := node
  512. if ast.NodeListItem == node.Type {
  513. // 列表项聚焦返回和面包屑保持一致 https://github.com/siyuan-note/siyuan/issues/4914
  514. tmp = node.Parent
  515. }
  516. if headingParent := treenode.HeadingParent(tmp); nil != headingParent {
  517. parent2ID = headingParent.ID
  518. }
  519. }
  520. rootID = tree.Root.ID
  521. if !isDoc {
  522. typ = node.Type.String()
  523. }
  524. var nodes []*ast.Node
  525. // 如果同时存在 startID 和 endID,则只加载 startID 和 endID 之间的块 [startID, endID]
  526. if "" != startID && "" != endID {
  527. nodes, eof = loadNodesByStartEnd(tree, startID, endID)
  528. if 1 > len(nodes) {
  529. // 按 mode 加载兜底
  530. nodes, eof = loadNodesByMode(node, inputIndex, mode, size, isDoc, isHeading)
  531. }
  532. } else {
  533. nodes, eof = loadNodesByMode(node, inputIndex, mode, size, isDoc, isHeading)
  534. }
  535. refCount := sql.QueryRootChildrenRefCount(rootID)
  536. virtualBlockRefKeywords := getVirtualRefKeywords(tree.Root.IALAttr("title"))
  537. subTree := &parse.Tree{ID: rootID, Root: &ast.Node{Type: ast.NodeDocument}, Marks: tree.Marks}
  538. keyword = strings.Join(strings.Split(keyword, " "), search.TermSep)
  539. keywords := search.SplitKeyword(keyword)
  540. for _, n := range nodes {
  541. var unlinks []*ast.Node
  542. ast.Walk(n, func(n *ast.Node, entering bool) ast.WalkStatus {
  543. if !entering {
  544. return ast.WalkContinue
  545. }
  546. if "1" == n.IALAttr("heading-fold") {
  547. unlinks = append(unlinks, n)
  548. return ast.WalkContinue
  549. }
  550. if "" != n.ID {
  551. // 填充块引计数
  552. if cnt := refCount[n.ID]; 0 < cnt {
  553. n.SetIALAttr("refcount", strconv.Itoa(cnt))
  554. }
  555. }
  556. // 支持代码块搜索定位 https://github.com/siyuan-note/siyuan/issues/5520
  557. if ast.NodeCodeBlockCode == n.Type && 0 < len(keywords) && !treenode.IsChartCodeBlockCode(n) {
  558. text := string(n.Tokens)
  559. text = search.EncloseHighlighting(text, keywords, search.SearchMarkLeft, search.SearchMarkRight, Conf.Search.CaseSensitive)
  560. n.Tokens = gulu.Str.ToBytes(text)
  561. }
  562. if ast.NodeText == n.Type {
  563. if 0 < len(keywords) {
  564. hitBlock := false
  565. for p := n.Parent; nil != p; p = p.Parent {
  566. if p.ID == id {
  567. hitBlock = true
  568. break
  569. }
  570. }
  571. if hitBlock {
  572. if markReplaceSpan(n, &unlinks, string(n.Tokens), keywords, searchMarkSpanStart, searchMarkSpanEnd, luteEngine) {
  573. return ast.WalkContinue
  574. }
  575. }
  576. }
  577. if processVirtualRef(n, &unlinks, virtualBlockRefKeywords, refCount, luteEngine) {
  578. return ast.WalkContinue
  579. }
  580. }
  581. return ast.WalkContinue
  582. })
  583. for _, unlink := range unlinks {
  584. unlink.Unlink()
  585. }
  586. subTree.Root.AppendChild(n)
  587. }
  588. luteEngine.RenderOptions.NodeIndexStart = index
  589. dom = luteEngine.Tree2BlockDOM(subTree, luteEngine.RenderOptions)
  590. return
  591. }
  592. func loadNodesByStartEnd(tree *parse.Tree, startID, endID string) (nodes []*ast.Node, eof bool) {
  593. node := treenode.GetNodeInTree(tree, startID)
  594. if nil == node {
  595. return
  596. }
  597. nodes = append(nodes, node)
  598. for n := node.Next; nil != n; n = n.Next {
  599. if treenode.IsInFoldedHeading(n, nil) {
  600. continue
  601. }
  602. nodes = append(nodes, n)
  603. if n.ID == endID {
  604. if next := n.Next; nil == next {
  605. eof = true
  606. } else {
  607. eof = util2.IsDocIAL(n.Tokens) || util2.IsDocIAL(next.Tokens)
  608. }
  609. break
  610. }
  611. }
  612. return
  613. }
  614. func loadNodesByMode(node *ast.Node, inputIndex, mode, size int, isDoc, isHeading bool) (nodes []*ast.Node, eof bool) {
  615. if 2 == mode /* 向下 */ {
  616. next := node.Next
  617. if ast.NodeHeading == node.Type && "1" == node.IALAttr("fold") {
  618. // 标题展开时进行动态加载导致重复内容 https://github.com/siyuan-note/siyuan/issues/4671
  619. // 这里要考虑折叠标题是最后一个块的情况
  620. if children := treenode.HeadingChildren(node); 0 < len(children) {
  621. next = children[len(children)-1].Next
  622. }
  623. }
  624. if nil == next {
  625. eof = true
  626. } else {
  627. eof = util2.IsDocIAL(node.Tokens) || util2.IsDocIAL(next.Tokens)
  628. }
  629. }
  630. count := 0
  631. switch mode {
  632. case 0: // 仅加载当前 ID
  633. nodes = append(nodes, node)
  634. if isDoc {
  635. for n := node.Next; nil != n; n = n.Next {
  636. if treenode.IsInFoldedHeading(n, nil) {
  637. continue
  638. }
  639. nodes = append(nodes, n)
  640. if 1 > count {
  641. count++
  642. } else {
  643. count += treenode.CountBlockNodes(n)
  644. }
  645. if size < count {
  646. break
  647. }
  648. }
  649. } else if isHeading {
  650. level := node.HeadingLevel
  651. for n := node.Next; nil != n; n = n.Next {
  652. if treenode.IsInFoldedHeading(n, node) {
  653. // 大纲点击折叠标题跳转聚焦 https://github.com/siyuan-note/siyuan/issues/4920
  654. // 多级标题折叠后上级块引浮窗中未折叠 https://github.com/siyuan-note/siyuan/issues/4997
  655. continue
  656. }
  657. if ast.NodeHeading == n.Type {
  658. if n.HeadingLevel <= level {
  659. break
  660. }
  661. } else if ast.NodeSuperBlock == n.Type {
  662. if h := treenode.SuperBlockHeading(n); nil != h {
  663. if level >= h.HeadingLevel {
  664. break
  665. }
  666. }
  667. }
  668. nodes = append(nodes, n)
  669. count++
  670. if size < count {
  671. break
  672. }
  673. }
  674. }
  675. case 4: // Ctrl+End 跳转到末尾后向上加载
  676. for n := node; nil != n; n = n.Previous {
  677. if treenode.IsInFoldedHeading(n, nil) {
  678. continue
  679. }
  680. nodes = append([]*ast.Node{n}, nodes...)
  681. if 1 > count {
  682. count++
  683. } else {
  684. count += treenode.CountBlockNodes(n)
  685. }
  686. if size < count {
  687. break
  688. }
  689. }
  690. eof = true
  691. case 1: // 向上加载
  692. for n := node.Previous; /* 从上一个节点开始加载 */ nil != n; n = n.Previous {
  693. if treenode.IsInFoldedHeading(n, nil) {
  694. continue
  695. }
  696. nodes = append([]*ast.Node{n}, nodes...)
  697. if 1 > count {
  698. count++
  699. } else {
  700. count += treenode.CountBlockNodes(n)
  701. }
  702. if size < count {
  703. break
  704. }
  705. }
  706. eof = nil == node.Previous
  707. case 2: // 向下加载
  708. for n := node.Next; /* 从下一个节点开始加载 */ nil != n; n = n.Next {
  709. if treenode.IsInFoldedHeading(n, node) {
  710. continue
  711. }
  712. nodes = append(nodes, n)
  713. if 1 > count {
  714. count++
  715. } else {
  716. count += treenode.CountBlockNodes(n)
  717. }
  718. if size < count {
  719. break
  720. }
  721. }
  722. case 3: // 上下都加载
  723. for n := node; nil != n; n = n.Previous {
  724. if treenode.IsInFoldedHeading(n, nil) {
  725. continue
  726. }
  727. nodes = append([]*ast.Node{n}, nodes...)
  728. if 1 > count {
  729. count++
  730. } else {
  731. count += treenode.CountBlockNodes(n)
  732. }
  733. if 0 < inputIndex {
  734. if 1 < count {
  735. break // 滑块指示器加载
  736. }
  737. } else {
  738. if size < count {
  739. break
  740. }
  741. }
  742. }
  743. if size/2 < count {
  744. size = size / 2
  745. } else {
  746. size = size - count
  747. }
  748. count = 0
  749. for n := node.Next; nil != n; n = n.Next {
  750. if treenode.IsInFoldedHeading(n, nil) {
  751. continue
  752. }
  753. nodes = append(nodes, n)
  754. if 1 > count {
  755. count++
  756. } else {
  757. count += treenode.CountBlockNodes(n)
  758. }
  759. if 0 < inputIndex {
  760. if size < count {
  761. break
  762. }
  763. } else {
  764. if size < count {
  765. break
  766. }
  767. }
  768. }
  769. }
  770. return
  771. }
  772. func writeJSONQueue(tree *parse.Tree) (err error) {
  773. if err = filesys.WriteTree(tree); nil != err {
  774. return
  775. }
  776. sql.UpsertTreeQueue(tree)
  777. return
  778. }
  779. func writeJSONQueueWithoutChangeTime(tree *parse.Tree) (err error) {
  780. if err = filesys.WriteTreeWithoutChangeTime(tree); nil != err {
  781. return
  782. }
  783. sql.UpsertTreeQueue(tree)
  784. return
  785. }
  786. func indexWriteJSONQueue(tree *parse.Tree) (err error) {
  787. treenode.ReindexBlockTree(tree)
  788. return writeJSONQueue(tree)
  789. }
  790. func indexWriteJSONQueueWithoutChangeTime(tree *parse.Tree) (err error) {
  791. treenode.ReindexBlockTree(tree)
  792. return writeJSONQueueWithoutChangeTime(tree)
  793. }
  794. func renameWriteJSONQueue(tree *parse.Tree, oldHPath string) (err error) {
  795. if err = filesys.WriteTree(tree); nil != err {
  796. return
  797. }
  798. sql.RenameTreeQueue(tree, oldHPath)
  799. treenode.ReindexBlockTree(tree)
  800. return
  801. }
  802. func DuplicateDoc(rootID string) (ret *parse.Tree, err error) {
  803. msgId := util.PushMsg(Conf.Language(116), 30000)
  804. defer util.PushClearMsg(msgId)
  805. WaitForWritingFiles()
  806. ret, err = loadTreeByBlockID(rootID)
  807. if nil != err {
  808. return
  809. }
  810. resetTree(ret, "Duplicated")
  811. createTreeTx(ret)
  812. sql.WaitForWritingDatabase()
  813. return
  814. }
  815. func createTreeTx(tree *parse.Tree) {
  816. transaction := &Transaction{DoOperations: []*Operation{{Action: "create", Data: tree}}}
  817. err := PerformTransactions(&[]*Transaction{transaction})
  818. if nil != err {
  819. tx, txErr := sql.BeginTx()
  820. if nil != txErr {
  821. logging.LogFatalf("transaction failed: %s", txErr)
  822. return
  823. }
  824. sql.ClearBoxHash(tx)
  825. sql.CommitTx(tx)
  826. logging.LogFatalf("transaction failed: %s", err)
  827. return
  828. }
  829. }
  830. func CreateDocByMd(boxID, p, title, md string, sorts []string) (err error) {
  831. WaitForWritingFiles()
  832. box := Conf.Box(boxID)
  833. if nil == box {
  834. return errors.New(Conf.Language(0))
  835. }
  836. luteEngine := NewLute()
  837. dom := luteEngine.Md2BlockDOM(md, false)
  838. err = createDoc(box.ID, p, title, dom)
  839. if nil != err {
  840. return
  841. }
  842. ChangeFileTreeSort(box.ID, sorts)
  843. return
  844. }
  845. func CreateWithMarkdown(boxID, hPath, md string) (id string, err error) {
  846. box := Conf.Box(boxID)
  847. if nil == box {
  848. err = errors.New(Conf.Language(0))
  849. return
  850. }
  851. WaitForWritingFiles()
  852. luteEngine := NewLute()
  853. dom := luteEngine.Md2BlockDOM(md, false)
  854. id, _, err = createDocsByHPath(box.ID, hPath, dom)
  855. return
  856. }
  857. func GetHPathByPath(boxID, p string) (hPath string, err error) {
  858. if "/" == p {
  859. hPath = "/"
  860. return
  861. }
  862. tree, err := LoadTree(boxID, p)
  863. if nil != err {
  864. return
  865. }
  866. hPath = tree.HPath
  867. return
  868. }
  869. func GetHPathsByPaths(paths []string) (hPaths []string, err error) {
  870. pathsBoxes := getBoxesByPaths(paths)
  871. for p, box := range pathsBoxes {
  872. if nil == box {
  873. logging.LogWarnf("box not found by path [%s]", p)
  874. continue
  875. }
  876. bt := treenode.GetBlockTreeByPath(p)
  877. if nil == bt {
  878. logging.LogWarnf("block tree not found by path [%s]", p)
  879. continue
  880. }
  881. hPaths = append(hPaths, box.Name+bt.HPath)
  882. }
  883. return
  884. }
  885. func GetHPathByID(id string) (hPath string, err error) {
  886. tree, err := loadTreeByBlockID(id)
  887. if nil != err {
  888. return
  889. }
  890. hPath = tree.HPath
  891. return
  892. }
  893. func GetFullHPathByID(id string) (hPath string, err error) {
  894. tree, err := loadTreeByBlockID(id)
  895. if nil != err {
  896. return
  897. }
  898. box := Conf.Box(tree.Box)
  899. var boxName string
  900. if nil != box {
  901. boxName = box.Name
  902. }
  903. hPath = boxName + tree.HPath
  904. return
  905. }
  906. func MoveDocs(fromPaths []string, toBoxID, toPath string) (err error) {
  907. toBox := Conf.Box(toBoxID)
  908. if nil == toBox {
  909. err = errors.New(Conf.Language(0))
  910. return
  911. }
  912. fromPaths = util.FilterMoveDocFromPaths(fromPaths, toPath)
  913. if 1 > len(fromPaths) {
  914. return
  915. }
  916. pathsBoxes := getBoxesByPaths(fromPaths)
  917. // 检查路径深度是否超过限制
  918. for fromPath, fromBox := range pathsBoxes {
  919. childDepth := util.GetChildDocDepth(filepath.Join(util.DataDir, fromBox.ID, fromPath))
  920. if depth := strings.Count(toPath, "/") + childDepth; 6 < depth && !Conf.FileTree.AllowCreateDeeper {
  921. err = errors.New(Conf.Language(118))
  922. return
  923. }
  924. }
  925. needShowProgress := 16 < len(fromPaths)
  926. if needShowProgress {
  927. util.PushEndlessProgress(Conf.Language(116))
  928. }
  929. WaitForWritingFiles()
  930. for fromPath, fromBox := range pathsBoxes {
  931. _, err = moveDoc(fromBox, fromPath, toBox, toPath)
  932. if nil != err {
  933. return
  934. }
  935. }
  936. cache.ClearDocsIAL()
  937. IncSync()
  938. if needShowProgress {
  939. util.PushEndlessProgress(Conf.Language(113))
  940. sql.WaitForWritingDatabase()
  941. util.ReloadUI()
  942. }
  943. return
  944. }
  945. func moveDoc(fromBox *Box, fromPath string, toBox *Box, toPath string) (newPath string, err error) {
  946. isSameBox := fromBox.ID == toBox.ID
  947. if isSameBox {
  948. if !fromBox.Exist(toPath) {
  949. err = ErrBlockNotFound
  950. return
  951. }
  952. } else {
  953. if !toBox.Exist(toPath) {
  954. err = ErrBlockNotFound
  955. return
  956. }
  957. }
  958. tree, err := LoadTree(fromBox.ID, fromPath)
  959. if nil != err {
  960. err = ErrBlockNotFound
  961. return
  962. }
  963. moveToRoot := "/" == toPath
  964. toBlockID := tree.ID
  965. fromFolder := path.Join(path.Dir(fromPath), tree.ID)
  966. toFolder := "/"
  967. if !moveToRoot {
  968. var toTree *parse.Tree
  969. if isSameBox {
  970. toTree, err = LoadTree(fromBox.ID, toPath)
  971. } else {
  972. toTree, err = LoadTree(toBox.ID, toPath)
  973. }
  974. if nil != err {
  975. err = ErrBlockNotFound
  976. return
  977. }
  978. toBlockID = toTree.ID
  979. toFolder = path.Join(path.Dir(toPath), toBlockID)
  980. }
  981. if isSameBox {
  982. if err = fromBox.MkdirAll(toFolder); nil != err {
  983. return
  984. }
  985. } else {
  986. if err = toBox.MkdirAll(toFolder); nil != err {
  987. return
  988. }
  989. }
  990. if fromBox.Exist(fromFolder) {
  991. // 移动子文档文件夹
  992. newFolder := path.Join(toFolder, tree.ID)
  993. if isSameBox {
  994. if err = fromBox.Move(fromFolder, newFolder); nil != err {
  995. return
  996. }
  997. } else {
  998. absFromPath := filepath.Join(util.DataDir, fromBox.ID, fromFolder)
  999. absToPath := filepath.Join(util.DataDir, toBox.ID, newFolder)
  1000. if gulu.File.IsExist(absToPath) {
  1001. filelock.Remove(absToPath)
  1002. }
  1003. if err = filelock.Move(absFromPath, absToPath); nil != err {
  1004. msg := fmt.Sprintf(Conf.Language(5), fromBox.Name, fromPath, err)
  1005. logging.LogErrorf("move [path=%s] in box [%s] failed: %s", fromPath, fromBox.ID, err)
  1006. err = errors.New(msg)
  1007. return
  1008. }
  1009. }
  1010. }
  1011. newPath = path.Join(toFolder, tree.ID+".sy")
  1012. if isSameBox {
  1013. if err = fromBox.Move(fromPath, newPath); nil != err {
  1014. return
  1015. }
  1016. tree, err = LoadTree(fromBox.ID, newPath)
  1017. if nil != err {
  1018. return
  1019. }
  1020. moveTree(tree)
  1021. } else {
  1022. absFromPath := filepath.Join(util.DataDir, fromBox.ID, fromPath)
  1023. absToPath := filepath.Join(util.DataDir, toBox.ID, newPath)
  1024. if err = filelock.Move(absFromPath, absToPath); nil != err {
  1025. msg := fmt.Sprintf(Conf.Language(5), fromBox.Name, fromPath, err)
  1026. logging.LogErrorf("move [path=%s] in box [%s] failed: %s", fromPath, fromBox.ID, err)
  1027. err = errors.New(msg)
  1028. return
  1029. }
  1030. tree, err = LoadTree(toBox.ID, newPath)
  1031. if nil != err {
  1032. return
  1033. }
  1034. moveTree(tree)
  1035. moveSorts(tree.ID, fromBox.ID, toBox.ID)
  1036. }
  1037. evt := util.NewCmdResult("moveDoc", 0, util.PushModeBroadcast, util.PushModeNone)
  1038. evt.Data = map[string]interface{}{
  1039. "fromNotebook": fromBox.ID,
  1040. "fromPath": fromPath,
  1041. "toNotebook": toBox.ID,
  1042. "toPath": toPath,
  1043. "newPath": newPath,
  1044. }
  1045. util.PushEvent(evt)
  1046. return
  1047. }
  1048. func RemoveDoc(boxID, p string) (err error) {
  1049. box := Conf.Box(boxID)
  1050. if nil == box {
  1051. err = errors.New(Conf.Language(0))
  1052. return
  1053. }
  1054. WaitForWritingFiles()
  1055. err = removeDoc(box, p)
  1056. if nil != err {
  1057. return
  1058. }
  1059. IncSync()
  1060. return
  1061. }
  1062. func RemoveDocs(paths []string) (err error) {
  1063. util.PushEndlessProgress(Conf.Language(116))
  1064. defer util.PushClearProgress()
  1065. paths = util.FilterSelfChildDocs(paths)
  1066. pathsBoxes := getBoxesByPaths(paths)
  1067. WaitForWritingFiles()
  1068. for p, box := range pathsBoxes {
  1069. err = removeDoc(box, p)
  1070. if nil != err {
  1071. return
  1072. }
  1073. }
  1074. return
  1075. }
  1076. func removeDoc(box *Box, p string) (err error) {
  1077. tree, err := LoadTree(box.ID, p)
  1078. if nil != err {
  1079. return
  1080. }
  1081. historyDir, err := GetHistoryDir(HistoryOpDelete)
  1082. if nil != err {
  1083. logging.LogErrorf("get history dir failed: %s", err)
  1084. return
  1085. }
  1086. historyPath := filepath.Join(historyDir, box.ID, p)
  1087. absPath := filepath.Join(util.DataDir, box.ID, p)
  1088. if err = filelock.Copy(absPath, historyPath); nil != err {
  1089. return errors.New(fmt.Sprintf(Conf.Language(70), box.Name, absPath, err))
  1090. }
  1091. copyDocAssetsToDataAssets(box.ID, p)
  1092. var removeIDs []string
  1093. ids := treenode.RootChildIDs(tree.ID)
  1094. removeIDs = append(removeIDs, ids...)
  1095. dir := path.Dir(p)
  1096. childrenDir := path.Join(dir, tree.ID)
  1097. existChildren := box.Exist(childrenDir)
  1098. if existChildren {
  1099. absChildrenDir := filepath.Join(util.DataDir, tree.Box, childrenDir)
  1100. historyPath = filepath.Join(historyDir, tree.Box, childrenDir)
  1101. if err = filelock.Copy(absChildrenDir, historyPath); nil != err {
  1102. return
  1103. }
  1104. }
  1105. indexHistoryDir(filepath.Base(historyDir), NewLute())
  1106. if existChildren {
  1107. if err = box.Remove(childrenDir); nil != err {
  1108. return
  1109. }
  1110. }
  1111. if err = box.Remove(p); nil != err {
  1112. return
  1113. }
  1114. box.removeSort(removeIDs)
  1115. treenode.RemoveBlockTreesByPathPrefix(childrenDir)
  1116. sql.RemoveTreePathQueue(box.ID, childrenDir)
  1117. if "/" != dir {
  1118. others, err := os.ReadDir(filepath.Join(util.DataDir, box.ID, dir))
  1119. if nil == err && 1 > len(others) {
  1120. box.Remove(dir)
  1121. }
  1122. }
  1123. cache.RemoveDocIAL(p)
  1124. evt := util.NewCmdResult("removeDoc", 0, util.PushModeBroadcast, util.PushModeNone)
  1125. evt.Data = map[string]interface{}{
  1126. "ids": removeIDs,
  1127. }
  1128. util.PushEvent(evt)
  1129. return
  1130. }
  1131. func RenameDoc(boxID, p, title string) (err error) {
  1132. box := Conf.Box(boxID)
  1133. if nil == box {
  1134. err = errors.New(Conf.Language(0))
  1135. return
  1136. }
  1137. WaitForWritingFiles()
  1138. tree, err := LoadTree(box.ID, p)
  1139. if nil != err {
  1140. return
  1141. }
  1142. title = gulu.Str.RemoveInvisible(title)
  1143. if 512 < utf8.RuneCountInString(title) {
  1144. // 限制笔记本名和文档名最大长度为 `512` https://github.com/siyuan-note/siyuan/issues/6299
  1145. return errors.New(Conf.Language(106))
  1146. }
  1147. oldTitle := tree.Root.IALAttr("title")
  1148. if oldTitle == title {
  1149. return
  1150. }
  1151. if "" == title {
  1152. title = "Untitled"
  1153. }
  1154. title = strings.ReplaceAll(title, "/", "")
  1155. oldHPath := tree.HPath
  1156. tree.HPath = path.Join(path.Dir(tree.HPath), title)
  1157. tree.Root.SetIALAttr("title", title)
  1158. tree.Root.SetIALAttr("updated", util.CurrentTimeSecondsStr())
  1159. if err = renameWriteJSONQueue(tree, oldHPath); nil != err {
  1160. return
  1161. }
  1162. refText := getNodeRefText(tree.Root)
  1163. evt := util.NewCmdResult("rename", 0, util.PushModeBroadcast, util.PushModeNone)
  1164. evt.Data = map[string]interface{}{
  1165. "box": boxID,
  1166. "id": tree.Root.ID,
  1167. "path": p,
  1168. "title": title,
  1169. "refText": refText,
  1170. }
  1171. util.PushEvent(evt)
  1172. box.renameSubTrees(tree)
  1173. updateRefTextRenameDoc(tree)
  1174. IncSync()
  1175. return
  1176. }
  1177. func CreateDailyNote(boxID string) (p string, existed bool, err error) {
  1178. box := Conf.Box(boxID)
  1179. if nil == box {
  1180. err = ErrBoxNotFound
  1181. return
  1182. }
  1183. boxConf := box.GetConf()
  1184. if "" == boxConf.DailyNoteSavePath || "/" == boxConf.DailyNoteSavePath {
  1185. err = errors.New(Conf.Language(49))
  1186. return
  1187. }
  1188. hPath, err := RenderCreateDocNameTemplate(boxConf.DailyNoteSavePath)
  1189. if nil != err {
  1190. return
  1191. }
  1192. WaitForWritingFiles()
  1193. existRoot := treenode.GetBlockTreeRootByHPath(box.ID, hPath)
  1194. if nil != existRoot {
  1195. existed = true
  1196. p = existRoot.Path
  1197. return
  1198. }
  1199. id, existed, err := createDocsByHPath(box.ID, hPath, "")
  1200. if nil != err {
  1201. return
  1202. }
  1203. var dom string
  1204. if "" != boxConf.DailyNoteTemplatePath {
  1205. tplPath := filepath.Join(util.DataDir, "templates", boxConf.DailyNoteTemplatePath)
  1206. if !gulu.File.IsExist(tplPath) {
  1207. logging.LogWarnf("not found daily note template [%s]", tplPath)
  1208. } else {
  1209. dom, err = renderTemplate(tplPath, id)
  1210. if nil != err {
  1211. logging.LogWarnf("render daily note template [%s] failed: %s", boxConf.DailyNoteTemplatePath, err)
  1212. }
  1213. }
  1214. }
  1215. if "" != dom {
  1216. var tree *parse.Tree
  1217. tree, err = loadTreeByBlockID(id)
  1218. if nil == err {
  1219. tree.Root.FirstChild.Unlink()
  1220. luteEngine := NewLute()
  1221. newTree := luteEngine.BlockDOM2Tree(dom)
  1222. var children []*ast.Node
  1223. for c := newTree.Root.FirstChild; nil != c; c = c.Next {
  1224. children = append(children, c)
  1225. }
  1226. for _, c := range children {
  1227. tree.Root.AppendChild(c)
  1228. }
  1229. tree.Root.SetIALAttr("updated", util.CurrentTimeSecondsStr())
  1230. if err = indexWriteJSONQueue(tree); nil != err {
  1231. return
  1232. }
  1233. }
  1234. }
  1235. IncSync()
  1236. b := treenode.GetBlockTree(id)
  1237. p = b.Path
  1238. return
  1239. }
  1240. func createDoc(boxID, p, title, dom string) (err error) {
  1241. title = gulu.Str.RemoveInvisible(title)
  1242. if 512 < utf8.RuneCountInString(title) {
  1243. // 限制笔记本名和文档名最大长度为 `512` https://github.com/siyuan-note/siyuan/issues/6299
  1244. return errors.New(Conf.Language(106))
  1245. }
  1246. title = strings.ReplaceAll(title, "/", "")
  1247. baseName := strings.TrimSpace(path.Base(p))
  1248. if "" == strings.TrimSuffix(baseName, ".sy") {
  1249. return errors.New(Conf.Language(16))
  1250. }
  1251. if strings.HasPrefix(baseName, ".") {
  1252. return errors.New(Conf.Language(13))
  1253. }
  1254. box := Conf.Box(boxID)
  1255. if nil == box {
  1256. return errors.New(Conf.Language(0))
  1257. }
  1258. id := strings.TrimSuffix(path.Base(p), ".sy")
  1259. var hPath string
  1260. folder := path.Dir(p)
  1261. if "/" != folder {
  1262. parentID := path.Base(folder)
  1263. parentTree, err := loadTreeByBlockID(parentID)
  1264. if nil != err {
  1265. logging.LogErrorf("get parent tree [id=%s] failed", parentID)
  1266. return ErrBlockNotFound
  1267. }
  1268. hPath = path.Join(parentTree.HPath, title)
  1269. } else {
  1270. hPath = "/" + title
  1271. }
  1272. if depth := strings.Count(p, "/"); 7 < depth && !Conf.FileTree.AllowCreateDeeper {
  1273. err = errors.New(Conf.Language(118))
  1274. return
  1275. }
  1276. if !box.Exist(folder) {
  1277. if err = box.MkdirAll(folder); nil != err {
  1278. return err
  1279. }
  1280. }
  1281. if box.Exist(p) {
  1282. return errors.New(Conf.Language(1))
  1283. }
  1284. var tree *parse.Tree
  1285. luteEngine := NewLute()
  1286. tree = luteEngine.BlockDOM2Tree(dom)
  1287. tree.Box = boxID
  1288. tree.Path = p
  1289. tree.HPath = hPath
  1290. tree.ID = id
  1291. tree.Root.ID = id
  1292. tree.Root.Spec = "1"
  1293. updated := util.TimeFromID(id)
  1294. tree.Root.KramdownIAL = [][]string{{"id", id}, {"title", html.EscapeAttrVal(title)}, {"updated", updated}}
  1295. if nil == tree.Root.FirstChild {
  1296. tree.Root.AppendChild(parse.NewParagraph())
  1297. }
  1298. transaction := &Transaction{DoOperations: []*Operation{{Action: "create", Data: tree}}}
  1299. err = PerformTransactions(&[]*Transaction{transaction})
  1300. if nil != err {
  1301. tx, txErr := sql.BeginTx()
  1302. if nil != txErr {
  1303. logging.LogFatalf("transaction failed: %s", txErr)
  1304. return
  1305. }
  1306. sql.ClearBoxHash(tx)
  1307. sql.CommitTx(tx)
  1308. logging.LogFatalf("transaction failed: %s", err)
  1309. return
  1310. }
  1311. WaitForWritingFiles()
  1312. return
  1313. }
  1314. func moveSorts(rootID, fromBox, toBox string) {
  1315. root := treenode.GetBlockTree(rootID)
  1316. if nil == root {
  1317. return
  1318. }
  1319. fromRootSorts := map[string]int{}
  1320. ids := treenode.RootChildIDs(rootID)
  1321. fromConfPath := filepath.Join(util.DataDir, fromBox, ".siyuan", "sort.json")
  1322. fromFullSortIDs := map[string]int{}
  1323. if gulu.File.IsExist(fromConfPath) {
  1324. data, err := filelock.ReadFile(fromConfPath)
  1325. if nil != err {
  1326. logging.LogErrorf("read sort conf failed: %s", err)
  1327. return
  1328. }
  1329. if err = gulu.JSON.UnmarshalJSON(data, &fromFullSortIDs); nil != err {
  1330. logging.LogErrorf("unmarshal sort conf failed: %s", err)
  1331. }
  1332. }
  1333. for _, id := range ids {
  1334. fromRootSorts[id] = fromFullSortIDs[id]
  1335. }
  1336. toConfPath := filepath.Join(util.DataDir, toBox, ".siyuan", "sort.json")
  1337. toFullSortIDs := map[string]int{}
  1338. if gulu.File.IsExist(toConfPath) {
  1339. data, err := filelock.ReadFile(toConfPath)
  1340. if nil != err {
  1341. logging.LogErrorf("read sort conf failed: %s", err)
  1342. return
  1343. }
  1344. if err = gulu.JSON.UnmarshalJSON(data, &toFullSortIDs); nil != err {
  1345. logging.LogErrorf("unmarshal sort conf failed: %s", err)
  1346. return
  1347. }
  1348. }
  1349. for id, sortVal := range fromRootSorts {
  1350. toFullSortIDs[id] = sortVal
  1351. }
  1352. data, err := gulu.JSON.MarshalIndentJSON(toFullSortIDs, "", " ")
  1353. if nil != err {
  1354. logging.LogErrorf("marshal sort conf failed: %s", err)
  1355. return
  1356. }
  1357. if err = filelock.WriteFile(toConfPath, data); nil != err {
  1358. logging.LogErrorf("write sort conf failed: %s", err)
  1359. return
  1360. }
  1361. }
  1362. func ChangeFileTreeSort(boxID string, paths []string) {
  1363. if 1 > len(paths) {
  1364. return
  1365. }
  1366. WaitForWritingFiles()
  1367. box := Conf.Box(boxID)
  1368. sortIDs := map[string]int{}
  1369. max := 0
  1370. for i, p := range paths {
  1371. id := strings.TrimSuffix(path.Base(p), ".sy")
  1372. sortIDs[id] = i + 1
  1373. if i == len(paths)-1 {
  1374. max = i + 2
  1375. }
  1376. }
  1377. p := paths[0]
  1378. parentPath := path.Dir(p)
  1379. absParentPath := filepath.Join(util.DataDir, boxID, parentPath)
  1380. files, err := os.ReadDir(absParentPath)
  1381. if nil != err {
  1382. logging.LogErrorf("read dir [%s] failed: %s", err)
  1383. }
  1384. sortFolderIDs := map[string]int{}
  1385. for _, f := range files {
  1386. if !strings.HasSuffix(f.Name(), ".sy") {
  1387. continue
  1388. }
  1389. id := strings.TrimSuffix(f.Name(), ".sy")
  1390. val := sortIDs[id]
  1391. if 0 == val {
  1392. val = max
  1393. max++
  1394. }
  1395. sortFolderIDs[id] = val
  1396. }
  1397. confDir := filepath.Join(util.DataDir, box.ID, ".siyuan")
  1398. if err = os.MkdirAll(confDir, 0755); nil != err {
  1399. logging.LogErrorf("create conf dir failed: %s", err)
  1400. return
  1401. }
  1402. confPath := filepath.Join(confDir, "sort.json")
  1403. fullSortIDs := map[string]int{}
  1404. var data []byte
  1405. if gulu.File.IsExist(confPath) {
  1406. data, err = filelock.ReadFile(confPath)
  1407. if nil != err {
  1408. logging.LogErrorf("read sort conf failed: %s", err)
  1409. return
  1410. }
  1411. if err = gulu.JSON.UnmarshalJSON(data, &fullSortIDs); nil != err {
  1412. logging.LogErrorf("unmarshal sort conf failed: %s", err)
  1413. }
  1414. }
  1415. for sortID, sortVal := range sortFolderIDs {
  1416. fullSortIDs[sortID] = sortVal
  1417. }
  1418. data, err = gulu.JSON.MarshalIndentJSON(fullSortIDs, "", " ")
  1419. if nil != err {
  1420. logging.LogErrorf("marshal sort conf failed: %s", err)
  1421. return
  1422. }
  1423. if err = filelock.WriteFile(confPath, data); nil != err {
  1424. logging.LogErrorf("write sort conf failed: %s", err)
  1425. return
  1426. }
  1427. IncSync()
  1428. }
  1429. func (box *Box) fillSort(files *[]*File) {
  1430. confPath := filepath.Join(util.DataDir, box.ID, ".siyuan", "sort.json")
  1431. if !gulu.File.IsExist(confPath) {
  1432. return
  1433. }
  1434. data, err := filelock.ReadFile(confPath)
  1435. if nil != err {
  1436. logging.LogErrorf("read sort conf failed: %s", err)
  1437. return
  1438. }
  1439. fullSortIDs := map[string]int{}
  1440. if err = gulu.JSON.UnmarshalJSON(data, &fullSortIDs); nil != err {
  1441. logging.LogErrorf("unmarshal sort conf failed: %s", err)
  1442. return
  1443. }
  1444. for _, f := range *files {
  1445. id := strings.TrimSuffix(f.ID, ".sy")
  1446. f.Sort = fullSortIDs[id]
  1447. }
  1448. }
  1449. func (box *Box) removeSort(ids []string) {
  1450. confPath := filepath.Join(util.DataDir, box.ID, ".siyuan", "sort.json")
  1451. if !gulu.File.IsExist(confPath) {
  1452. return
  1453. }
  1454. data, err := filelock.ReadFile(confPath)
  1455. if nil != err {
  1456. logging.LogErrorf("read sort conf failed: %s", err)
  1457. return
  1458. }
  1459. fullSortIDs := map[string]int{}
  1460. if err = gulu.JSON.UnmarshalJSON(data, &fullSortIDs); nil != err {
  1461. logging.LogErrorf("unmarshal sort conf failed: %s", err)
  1462. return
  1463. }
  1464. for _, toRemove := range ids {
  1465. delete(fullSortIDs, toRemove)
  1466. }
  1467. data, err = gulu.JSON.MarshalIndentJSON(fullSortIDs, "", " ")
  1468. if nil != err {
  1469. logging.LogErrorf("marshal sort conf failed: %s", err)
  1470. return
  1471. }
  1472. if err = filelock.WriteFile(confPath, data); nil != err {
  1473. logging.LogErrorf("write sort conf failed: %s", err)
  1474. return
  1475. }
  1476. }
  1477. func ServeFile(c *gin.Context, filePath string) (err error) {
  1478. WaitForWritingFiles()
  1479. c.File(filePath)
  1480. return
  1481. }