file.go 41 KB

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