file.go 42 KB

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