filetree.go 22 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003
  1. // SiYuan - Refactor your thinking
  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 api
  17. import (
  18. "fmt"
  19. "math"
  20. "net/http"
  21. "os"
  22. "path"
  23. "path/filepath"
  24. "regexp"
  25. "strings"
  26. "unicode/utf8"
  27. "github.com/88250/gulu"
  28. "github.com/88250/lute/ast"
  29. "github.com/gin-gonic/gin"
  30. "github.com/siyuan-note/siyuan/kernel/filesys"
  31. "github.com/siyuan-note/siyuan/kernel/model"
  32. "github.com/siyuan-note/siyuan/kernel/util"
  33. )
  34. func listDocTree(c *gin.Context) {
  35. // Add kernel API `/api/filetree/listDocTree` https://github.com/siyuan-note/siyuan/issues/10482
  36. ret := gulu.Ret.NewResult()
  37. defer c.JSON(http.StatusOK, ret)
  38. arg, ok := util.JsonArg(c, ret)
  39. if !ok {
  40. return
  41. }
  42. notebook := arg["notebook"].(string)
  43. if util.InvalidIDPattern(notebook, ret) {
  44. return
  45. }
  46. p := arg["path"].(string)
  47. var doctree []*DocFile
  48. root := filepath.Join(util.WorkspaceDir, "data", notebook, p)
  49. dir, err := os.ReadDir(root)
  50. if nil != err {
  51. ret.Code = -1
  52. ret.Msg = err.Error()
  53. return
  54. }
  55. ids := map[string]bool{}
  56. for _, entry := range dir {
  57. if entry.IsDir() {
  58. if strings.HasPrefix(entry.Name(), ".") {
  59. continue
  60. }
  61. if !ast.IsNodeIDPattern(entry.Name()) {
  62. continue
  63. }
  64. parent := &DocFile{ID: entry.Name()}
  65. ids[parent.ID] = true
  66. doctree = append(doctree, parent)
  67. subPath := filepath.Join(root, entry.Name())
  68. if err = walkDocTree(subPath, parent, &ids); nil != err {
  69. ret.Code = -1
  70. ret.Msg = err.Error()
  71. return
  72. }
  73. } else {
  74. doc := &DocFile{ID: strings.TrimSuffix(entry.Name(), ".sy")}
  75. if !ids[doc.ID] {
  76. doctree = append(doctree, doc)
  77. }
  78. ids[doc.ID] = true
  79. }
  80. }
  81. ret.Data = map[string]interface{}{
  82. "tree": doctree,
  83. }
  84. }
  85. type DocFile struct {
  86. ID string `json:"id"`
  87. Children []*DocFile `json:"children,omitempty"`
  88. }
  89. func walkDocTree(p string, docFile *DocFile, ids *map[string]bool) (err error) {
  90. dir, err := os.ReadDir(p)
  91. if nil != err {
  92. return
  93. }
  94. for _, entry := range dir {
  95. if entry.IsDir() {
  96. if strings.HasPrefix(entry.Name(), ".") {
  97. continue
  98. }
  99. if !ast.IsNodeIDPattern(entry.Name()) {
  100. continue
  101. }
  102. parent := &DocFile{ID: entry.Name()}
  103. (*ids)[parent.ID] = true
  104. docFile.Children = append(docFile.Children, parent)
  105. subPath := filepath.Join(p, entry.Name())
  106. if err = walkDocTree(subPath, parent, ids); nil != err {
  107. return
  108. }
  109. } else {
  110. doc := &DocFile{ID: strings.TrimSuffix(entry.Name(), ".sy")}
  111. if !(*ids)[doc.ID] {
  112. docFile.Children = append(docFile.Children, doc)
  113. }
  114. (*ids)[doc.ID] = true
  115. }
  116. }
  117. return
  118. }
  119. func upsertIndexes(c *gin.Context) {
  120. ret := gulu.Ret.NewResult()
  121. defer c.JSON(http.StatusOK, ret)
  122. arg, ok := util.JsonArg(c, ret)
  123. if !ok {
  124. return
  125. }
  126. pathsArg := arg["paths"].([]interface{})
  127. var paths []string
  128. for _, p := range pathsArg {
  129. paths = append(paths, p.(string))
  130. }
  131. model.UpsertIndexes(paths)
  132. }
  133. func removeIndexes(c *gin.Context) {
  134. ret := gulu.Ret.NewResult()
  135. defer c.JSON(http.StatusOK, ret)
  136. arg, ok := util.JsonArg(c, ret)
  137. if !ok {
  138. return
  139. }
  140. pathsArg := arg["paths"].([]interface{})
  141. var paths []string
  142. for _, p := range pathsArg {
  143. paths = append(paths, p.(string))
  144. }
  145. model.RemoveIndexes(paths)
  146. }
  147. func refreshFiletree(c *gin.Context) {
  148. ret := gulu.Ret.NewResult()
  149. defer c.JSON(http.StatusOK, ret)
  150. model.FullReindex()
  151. }
  152. func doc2Heading(c *gin.Context) {
  153. ret := gulu.Ret.NewResult()
  154. defer c.JSON(http.StatusOK, ret)
  155. arg, ok := util.JsonArg(c, ret)
  156. if !ok {
  157. return
  158. }
  159. srcID := arg["srcID"].(string)
  160. targetID := arg["targetID"].(string)
  161. after := arg["after"].(bool)
  162. srcTreeBox, srcTreePath, err := model.Doc2Heading(srcID, targetID, after)
  163. if nil != err {
  164. ret.Code = -1
  165. ret.Msg = err.Error()
  166. ret.Data = map[string]interface{}{"closeTimeout": 5000}
  167. return
  168. }
  169. ret.Data = map[string]interface{}{
  170. "srcTreeBox": srcTreeBox,
  171. "srcTreePath": srcTreePath,
  172. }
  173. }
  174. func heading2Doc(c *gin.Context) {
  175. ret := gulu.Ret.NewResult()
  176. defer c.JSON(http.StatusOK, ret)
  177. arg, ok := util.JsonArg(c, ret)
  178. if !ok {
  179. return
  180. }
  181. srcHeadingID := arg["srcHeadingID"].(string)
  182. targetNotebook := arg["targetNoteBook"].(string)
  183. targetPath := arg["targetPath"].(string)
  184. srcRootBlockID, targetPath, err := model.Heading2Doc(srcHeadingID, targetNotebook, targetPath)
  185. if nil != err {
  186. ret.Code = -1
  187. ret.Msg = err.Error()
  188. ret.Data = map[string]interface{}{"closeTimeout": 5000}
  189. return
  190. }
  191. model.WaitForWritingFiles()
  192. luteEngine := util.NewLute()
  193. tree, err := filesys.LoadTree(targetNotebook, targetPath, luteEngine)
  194. if nil != err {
  195. ret.Code = -1
  196. ret.Msg = err.Error()
  197. return
  198. }
  199. name := path.Base(targetPath)
  200. box := model.Conf.Box(targetNotebook)
  201. files, _, _ := model.ListDocTree(targetNotebook, path.Dir(targetPath), util.SortModeUnassigned, false, false, model.Conf.FileTree.MaxListCount)
  202. evt := util.NewCmdResult("heading2doc", 0, util.PushModeBroadcast)
  203. evt.Data = map[string]interface{}{
  204. "box": box,
  205. "path": targetPath,
  206. "files": files,
  207. "name": name,
  208. "id": tree.Root.ID,
  209. "srcRootBlockID": srcRootBlockID,
  210. }
  211. evt.Callback = arg["callback"]
  212. util.PushEvent(evt)
  213. }
  214. func li2Doc(c *gin.Context) {
  215. ret := gulu.Ret.NewResult()
  216. defer c.JSON(http.StatusOK, ret)
  217. arg, ok := util.JsonArg(c, ret)
  218. if !ok {
  219. return
  220. }
  221. srcListItemID := arg["srcListItemID"].(string)
  222. targetNotebook := arg["targetNoteBook"].(string)
  223. targetPath := arg["targetPath"].(string)
  224. srcRootBlockID, targetPath, err := model.ListItem2Doc(srcListItemID, targetNotebook, targetPath)
  225. if nil != err {
  226. ret.Code = -1
  227. ret.Msg = err.Error()
  228. ret.Data = map[string]interface{}{"closeTimeout": 5000}
  229. return
  230. }
  231. model.WaitForWritingFiles()
  232. luteEngine := util.NewLute()
  233. tree, err := filesys.LoadTree(targetNotebook, targetPath, luteEngine)
  234. if nil != err {
  235. ret.Code = -1
  236. ret.Msg = err.Error()
  237. return
  238. }
  239. name := path.Base(targetPath)
  240. box := model.Conf.Box(targetNotebook)
  241. files, _, _ := model.ListDocTree(targetNotebook, path.Dir(targetPath), util.SortModeUnassigned, false, false, model.Conf.FileTree.MaxListCount)
  242. evt := util.NewCmdResult("li2doc", 0, util.PushModeBroadcast)
  243. evt.Data = map[string]interface{}{
  244. "box": box,
  245. "path": targetPath,
  246. "files": files,
  247. "name": name,
  248. "id": tree.Root.ID,
  249. "srcRootBlockID": srcRootBlockID,
  250. }
  251. evt.Callback = arg["callback"]
  252. util.PushEvent(evt)
  253. }
  254. func getHPathByPath(c *gin.Context) {
  255. ret := gulu.Ret.NewResult()
  256. defer c.JSON(http.StatusOK, ret)
  257. arg, ok := util.JsonArg(c, ret)
  258. if !ok {
  259. return
  260. }
  261. notebook := arg["notebook"].(string)
  262. if util.InvalidIDPattern(notebook, ret) {
  263. return
  264. }
  265. p := arg["path"].(string)
  266. hPath, err := model.GetHPathByPath(notebook, p)
  267. if nil != err {
  268. ret.Code = -1
  269. ret.Msg = err.Error()
  270. return
  271. }
  272. ret.Data = hPath
  273. }
  274. func getHPathsByPaths(c *gin.Context) {
  275. ret := gulu.Ret.NewResult()
  276. defer c.JSON(http.StatusOK, ret)
  277. arg, ok := util.JsonArg(c, ret)
  278. if !ok {
  279. return
  280. }
  281. pathsArg := arg["paths"].([]interface{})
  282. var paths []string
  283. for _, p := range pathsArg {
  284. paths = append(paths, p.(string))
  285. }
  286. hPath, err := model.GetHPathsByPaths(paths)
  287. if nil != err {
  288. ret.Code = -1
  289. ret.Msg = err.Error()
  290. return
  291. }
  292. ret.Data = hPath
  293. }
  294. func getHPathByID(c *gin.Context) {
  295. ret := gulu.Ret.NewResult()
  296. defer c.JSON(http.StatusOK, ret)
  297. arg, ok := util.JsonArg(c, ret)
  298. if !ok {
  299. return
  300. }
  301. id := arg["id"].(string)
  302. if util.InvalidIDPattern(id, ret) {
  303. return
  304. }
  305. hPath, err := model.GetHPathByID(id)
  306. if nil != err {
  307. ret.Code = -1
  308. ret.Msg = err.Error()
  309. return
  310. }
  311. ret.Data = hPath
  312. }
  313. func getFullHPathByID(c *gin.Context) {
  314. ret := gulu.Ret.NewResult()
  315. defer c.JSON(http.StatusOK, ret)
  316. arg, ok := util.JsonArg(c, ret)
  317. if !ok {
  318. return
  319. }
  320. if nil == arg["id"] {
  321. return
  322. }
  323. id := arg["id"].(string)
  324. hPath, err := model.GetFullHPathByID(id)
  325. if nil != err {
  326. ret.Code = -1
  327. ret.Msg = err.Error()
  328. return
  329. }
  330. ret.Data = hPath
  331. }
  332. func getIDsByHPath(c *gin.Context) {
  333. ret := gulu.Ret.NewResult()
  334. defer c.JSON(http.StatusOK, ret)
  335. arg, ok := util.JsonArg(c, ret)
  336. if !ok {
  337. return
  338. }
  339. if nil == arg["path"] {
  340. return
  341. }
  342. if nil == arg["notebook"] {
  343. return
  344. }
  345. notebook := arg["notebook"].(string)
  346. if util.InvalidIDPattern(notebook, ret) {
  347. return
  348. }
  349. p := arg["path"].(string)
  350. ids, err := model.GetIDsByHPath(p, notebook)
  351. if nil != err {
  352. ret.Code = -1
  353. ret.Msg = err.Error()
  354. return
  355. }
  356. ret.Data = ids
  357. }
  358. func moveDocs(c *gin.Context) {
  359. ret := gulu.Ret.NewResult()
  360. defer c.JSON(http.StatusOK, ret)
  361. arg, ok := util.JsonArg(c, ret)
  362. if !ok {
  363. return
  364. }
  365. var fromPaths []string
  366. fromPathsArg := arg["fromPaths"].([]interface{})
  367. for _, fromPath := range fromPathsArg {
  368. fromPaths = append(fromPaths, fromPath.(string))
  369. }
  370. toPath := arg["toPath"].(string)
  371. toNotebook := arg["toNotebook"].(string)
  372. if util.InvalidIDPattern(toNotebook, ret) {
  373. return
  374. }
  375. callback := arg["callback"]
  376. err := model.MoveDocs(fromPaths, toNotebook, toPath, callback)
  377. if nil != err {
  378. ret.Code = -1
  379. ret.Msg = err.Error()
  380. ret.Data = map[string]interface{}{"closeTimeout": 7000}
  381. return
  382. }
  383. }
  384. func removeDoc(c *gin.Context) {
  385. ret := gulu.Ret.NewResult()
  386. defer c.JSON(http.StatusOK, ret)
  387. arg, ok := util.JsonArg(c, ret)
  388. if !ok {
  389. return
  390. }
  391. notebook := arg["notebook"].(string)
  392. if util.InvalidIDPattern(notebook, ret) {
  393. return
  394. }
  395. p := arg["path"].(string)
  396. model.RemoveDoc(notebook, p)
  397. }
  398. func removeDocs(c *gin.Context) {
  399. ret := gulu.Ret.NewResult()
  400. defer c.JSON(http.StatusOK, ret)
  401. arg, ok := util.JsonArg(c, ret)
  402. if !ok {
  403. return
  404. }
  405. pathsArg := arg["paths"].([]interface{})
  406. var paths []string
  407. for _, path := range pathsArg {
  408. paths = append(paths, path.(string))
  409. }
  410. model.RemoveDocs(paths)
  411. }
  412. func renameDoc(c *gin.Context) {
  413. ret := gulu.Ret.NewResult()
  414. defer c.JSON(http.StatusOK, ret)
  415. arg, ok := util.JsonArg(c, ret)
  416. if !ok {
  417. return
  418. }
  419. notebook := arg["notebook"].(string)
  420. if util.InvalidIDPattern(notebook, ret) {
  421. return
  422. }
  423. p := arg["path"].(string)
  424. title := arg["title"].(string)
  425. err := model.RenameDoc(notebook, p, title)
  426. if nil != err {
  427. ret.Code = -1
  428. ret.Msg = err.Error()
  429. return
  430. }
  431. return
  432. }
  433. func duplicateDoc(c *gin.Context) {
  434. ret := gulu.Ret.NewResult()
  435. defer c.JSON(http.StatusOK, ret)
  436. arg, ok := util.JsonArg(c, ret)
  437. if !ok {
  438. return
  439. }
  440. id := arg["id"].(string)
  441. tree, err := model.LoadTreeByBlockID(id)
  442. if nil != err {
  443. ret.Code = -1
  444. ret.Msg = err.Error()
  445. ret.Data = map[string]interface{}{"closeTimeout": 7000}
  446. return
  447. }
  448. notebook := tree.Box
  449. box := model.Conf.Box(notebook)
  450. model.DuplicateDoc(tree)
  451. pushCreate(box, tree.Path, tree.ID, arg)
  452. ret.Data = map[string]interface{}{
  453. "id": tree.Root.ID,
  454. "notebook": notebook,
  455. "path": tree.Path,
  456. "hPath": tree.HPath,
  457. }
  458. }
  459. func createDoc(c *gin.Context) {
  460. ret := gulu.Ret.NewResult()
  461. defer c.JSON(http.StatusOK, ret)
  462. arg, ok := util.JsonArg(c, ret)
  463. if !ok {
  464. return
  465. }
  466. notebook := arg["notebook"].(string)
  467. p := arg["path"].(string)
  468. title := arg["title"].(string)
  469. md := arg["md"].(string)
  470. sortsArg := arg["sorts"]
  471. var sorts []string
  472. if nil != sortsArg {
  473. for _, sort := range sortsArg.([]interface{}) {
  474. sorts = append(sorts, sort.(string))
  475. }
  476. }
  477. tree, err := model.CreateDocByMd(notebook, p, title, md, sorts)
  478. if nil != err {
  479. ret.Code = -1
  480. ret.Msg = err.Error()
  481. ret.Data = map[string]interface{}{"closeTimeout": 7000}
  482. return
  483. }
  484. model.WaitForWritingFiles()
  485. box := model.Conf.Box(notebook)
  486. pushCreate(box, p, tree.Root.ID, arg)
  487. ret.Data = map[string]interface{}{
  488. "id": tree.Root.ID,
  489. }
  490. }
  491. func createDailyNote(c *gin.Context) {
  492. ret := gulu.Ret.NewResult()
  493. defer c.JSON(http.StatusOK, ret)
  494. arg, ok := util.JsonArg(c, ret)
  495. if !ok {
  496. return
  497. }
  498. notebook := arg["notebook"].(string)
  499. p, existed, err := model.CreateDailyNote(notebook)
  500. if nil != err {
  501. if model.ErrBoxNotFound == err {
  502. ret.Code = 1
  503. } else {
  504. ret.Code = -1
  505. }
  506. ret.Msg = err.Error()
  507. return
  508. }
  509. model.WaitForWritingFiles()
  510. box := model.Conf.Box(notebook)
  511. luteEngine := util.NewLute()
  512. tree, err := filesys.LoadTree(box.ID, p, luteEngine)
  513. if nil != err {
  514. ret.Code = -1
  515. ret.Msg = err.Error()
  516. return
  517. }
  518. if !existed {
  519. // 只有创建的情况才推送,已经存在的情况不推送
  520. // Creating a dailynote existed no longer expands the doc tree https://github.com/siyuan-note/siyuan/issues/9959
  521. appArg := arg["app"]
  522. app := ""
  523. if nil != appArg {
  524. app = appArg.(string)
  525. }
  526. evt := util.NewCmdResult("createdailynote", 0, util.PushModeBroadcast)
  527. evt.AppId = app
  528. name := path.Base(p)
  529. files, _, _ := model.ListDocTree(box.ID, path.Dir(p), util.SortModeUnassigned, false, false, model.Conf.FileTree.MaxListCount)
  530. evt.Data = map[string]interface{}{
  531. "box": box,
  532. "path": p,
  533. "files": files,
  534. "name": name,
  535. "id": tree.Root.ID,
  536. }
  537. evt.Callback = arg["callback"]
  538. util.PushEvent(evt)
  539. }
  540. ret.Data = map[string]interface{}{
  541. "id": tree.Root.ID,
  542. }
  543. }
  544. func createDocWithMd(c *gin.Context) {
  545. ret := gulu.Ret.NewResult()
  546. defer c.JSON(http.StatusOK, ret)
  547. arg, ok := util.JsonArg(c, ret)
  548. if !ok {
  549. return
  550. }
  551. notebook := arg["notebook"].(string)
  552. if util.InvalidIDPattern(notebook, ret) {
  553. return
  554. }
  555. var parentID string
  556. parentIDArg := arg["parentID"]
  557. if nil != parentIDArg {
  558. parentID = parentIDArg.(string)
  559. }
  560. id := ast.NewNodeID()
  561. idArg := arg["id"]
  562. if nil != idArg {
  563. id = idArg.(string)
  564. }
  565. hPath := arg["path"].(string)
  566. markdown := arg["markdown"].(string)
  567. baseName := path.Base(hPath)
  568. dir := path.Dir(hPath)
  569. r, _ := regexp.Compile("\r\n|\r|\n|\u2028|\u2029|\t|/")
  570. baseName = r.ReplaceAllString(baseName, "")
  571. if 512 < utf8.RuneCountInString(baseName) {
  572. baseName = gulu.Str.SubStr(baseName, 512)
  573. }
  574. hPath = path.Join(dir, baseName)
  575. if !strings.HasPrefix(hPath, "/") {
  576. hPath = "/" + hPath
  577. }
  578. withMath := false
  579. withMathArg := arg["withMath"]
  580. if nil != withMathArg {
  581. withMath = withMathArg.(bool)
  582. }
  583. id, err := model.CreateWithMarkdown(notebook, hPath, markdown, parentID, id, withMath)
  584. if nil != err {
  585. ret.Code = -1
  586. ret.Msg = err.Error()
  587. return
  588. }
  589. ret.Data = id
  590. model.WaitForWritingFiles()
  591. box := model.Conf.Box(notebook)
  592. b, _ := model.GetBlock(id, nil)
  593. p := b.Path
  594. pushCreate(box, p, id, arg)
  595. }
  596. func getDocCreateSavePath(c *gin.Context) {
  597. ret := gulu.Ret.NewResult()
  598. defer c.JSON(http.StatusOK, ret)
  599. arg, ok := util.JsonArg(c, ret)
  600. if !ok {
  601. return
  602. }
  603. notebook := arg["notebook"].(string)
  604. box := model.Conf.Box(notebook)
  605. var docCreateSaveBox string
  606. docCreateSavePathTpl := model.Conf.FileTree.DocCreateSavePath
  607. if nil != box {
  608. boxConf := box.GetConf()
  609. docCreateSaveBox = boxConf.DocCreateSaveBox
  610. docCreateSavePathTpl = boxConf.DocCreateSavePath
  611. }
  612. if "" == docCreateSaveBox && "" == docCreateSavePathTpl {
  613. docCreateSaveBox = model.Conf.FileTree.DocCreateSaveBox
  614. }
  615. if "" != docCreateSaveBox {
  616. if nil == model.Conf.Box(docCreateSaveBox) {
  617. // 如果配置的笔记本未打开或者不存在,则使用当前笔记本
  618. docCreateSaveBox = notebook
  619. }
  620. }
  621. if "" == docCreateSaveBox {
  622. docCreateSaveBox = notebook
  623. }
  624. if "" == docCreateSavePathTpl {
  625. docCreateSavePathTpl = model.Conf.FileTree.DocCreateSavePath
  626. }
  627. docCreateSavePathTpl = strings.TrimSpace(docCreateSavePathTpl)
  628. if docCreateSaveBox != notebook {
  629. if "" != docCreateSavePathTpl && !strings.HasPrefix(docCreateSavePathTpl, "/") {
  630. // 如果配置的笔记本不是当前笔记本,则将相对路径转换为绝对路径
  631. docCreateSavePathTpl = "/" + docCreateSavePathTpl
  632. }
  633. }
  634. docCreateSavePath, err := model.RenderGoTemplate(docCreateSavePathTpl)
  635. if nil != err {
  636. ret.Code = -1
  637. ret.Msg = err.Error()
  638. return
  639. }
  640. ret.Data = map[string]interface{}{
  641. "box": docCreateSaveBox,
  642. "path": docCreateSavePath,
  643. }
  644. }
  645. func getRefCreateSavePath(c *gin.Context) {
  646. ret := gulu.Ret.NewResult()
  647. defer c.JSON(http.StatusOK, ret)
  648. arg, ok := util.JsonArg(c, ret)
  649. if !ok {
  650. return
  651. }
  652. notebook := arg["notebook"].(string)
  653. box := model.Conf.Box(notebook)
  654. var refCreateSaveBox string
  655. refCreateSavePathTpl := model.Conf.FileTree.RefCreateSavePath
  656. if nil != box {
  657. boxConf := box.GetConf()
  658. refCreateSaveBox = boxConf.RefCreateSaveBox
  659. refCreateSavePathTpl = boxConf.RefCreateSavePath
  660. }
  661. if "" == refCreateSaveBox && "" == refCreateSavePathTpl {
  662. refCreateSaveBox = model.Conf.FileTree.RefCreateSaveBox
  663. }
  664. if "" != refCreateSaveBox {
  665. if nil == model.Conf.Box(refCreateSaveBox) {
  666. // 如果配置的笔记本未打开或者不存在,则使用当前笔记本
  667. refCreateSaveBox = notebook
  668. }
  669. }
  670. if "" == refCreateSaveBox {
  671. refCreateSaveBox = notebook
  672. }
  673. if "" == refCreateSavePathTpl {
  674. refCreateSavePathTpl = model.Conf.FileTree.RefCreateSavePath
  675. }
  676. if refCreateSaveBox != notebook {
  677. if "" != refCreateSavePathTpl && !strings.HasPrefix(refCreateSavePathTpl, "/") {
  678. // 如果配置的笔记本不是当前笔记本,则将相对路径转换为绝对路径
  679. refCreateSavePathTpl = "/" + refCreateSavePathTpl
  680. }
  681. }
  682. refCreateSavePath, err := model.RenderGoTemplate(refCreateSavePathTpl)
  683. if nil != err {
  684. ret.Code = -1
  685. ret.Msg = err.Error()
  686. return
  687. }
  688. ret.Data = map[string]interface{}{
  689. "box": refCreateSaveBox,
  690. "path": refCreateSavePath,
  691. }
  692. }
  693. func changeSort(c *gin.Context) {
  694. ret := gulu.Ret.NewResult()
  695. defer c.JSON(http.StatusOK, ret)
  696. arg, ok := util.JsonArg(c, ret)
  697. if !ok {
  698. return
  699. }
  700. notebook := arg["notebook"].(string)
  701. pathsArg := arg["paths"].([]interface{})
  702. var paths []string
  703. for _, p := range pathsArg {
  704. paths = append(paths, p.(string))
  705. }
  706. model.ChangeFileTreeSort(notebook, paths)
  707. }
  708. func searchDocs(c *gin.Context) {
  709. ret := gulu.Ret.NewResult()
  710. defer c.JSON(http.StatusOK, ret)
  711. arg, ok := util.JsonArg(c, ret)
  712. if !ok {
  713. return
  714. }
  715. flashcard := false
  716. if arg["flashcard"] != nil {
  717. flashcard = arg["flashcard"].(bool)
  718. }
  719. k := arg["k"].(string)
  720. ret.Data = model.SearchDocsByKeyword(k, flashcard)
  721. }
  722. func listDocsByPath(c *gin.Context) {
  723. ret := gulu.Ret.NewResult()
  724. defer c.JSON(http.StatusOK, ret)
  725. arg, ok := util.JsonArg(c, ret)
  726. if !ok {
  727. return
  728. }
  729. notebook := arg["notebook"].(string)
  730. p := arg["path"].(string)
  731. sortParam := arg["sort"]
  732. sortMode := util.SortModeUnassigned
  733. if nil != sortParam {
  734. sortMode = int(sortParam.(float64))
  735. }
  736. flashcard := false
  737. if arg["flashcard"] != nil {
  738. flashcard = arg["flashcard"].(bool)
  739. }
  740. maxListCount := model.Conf.FileTree.MaxListCount
  741. if arg["maxListCount"] != nil {
  742. // API `listDocsByPath` add an optional parameter `maxListCount` https://github.com/siyuan-note/siyuan/issues/7993
  743. maxListCount = int(arg["maxListCount"].(float64))
  744. if 0 >= maxListCount {
  745. maxListCount = math.MaxInt
  746. }
  747. }
  748. showHidden := false
  749. if arg["showHidden"] != nil {
  750. showHidden = arg["showHidden"].(bool)
  751. }
  752. files, totals, err := model.ListDocTree(notebook, p, sortMode, flashcard, showHidden, maxListCount)
  753. if nil != err {
  754. ret.Code = -1
  755. ret.Msg = err.Error()
  756. return
  757. }
  758. if maxListCount < totals {
  759. // API `listDocsByPath` add an optional parameter `ignoreMaxListHint` https://github.com/siyuan-note/siyuan/issues/10290
  760. ignoreMaxListHintArg := arg["ignoreMaxListHint"]
  761. if nil == ignoreMaxListHintArg || !ignoreMaxListHintArg.(bool) {
  762. util.PushMsg(fmt.Sprintf(model.Conf.Language(48), len(files)), 7000)
  763. }
  764. }
  765. ret.Data = map[string]interface{}{
  766. "box": notebook,
  767. "path": p,
  768. "files": files,
  769. }
  770. }
  771. func getDoc(c *gin.Context) {
  772. ret := gulu.Ret.NewResult()
  773. defer c.JSON(http.StatusOK, ret)
  774. arg, ok := util.JsonArg(c, ret)
  775. if !ok {
  776. return
  777. }
  778. id := arg["id"].(string)
  779. idx := arg["index"]
  780. index := 0
  781. if nil != idx {
  782. index = int(idx.(float64))
  783. }
  784. var query string
  785. if queryArg := arg["query"]; nil != queryArg {
  786. query = queryArg.(string)
  787. }
  788. var queryMethod int
  789. if queryMethodArg := arg["queryMethod"]; nil != queryMethodArg {
  790. queryMethod = int(queryMethodArg.(float64))
  791. }
  792. var queryTypes map[string]bool
  793. if queryTypesArg := arg["queryTypes"]; nil != queryTypesArg {
  794. typesArg := queryTypesArg.(map[string]interface{})
  795. queryTypes = map[string]bool{}
  796. for t, b := range typesArg {
  797. queryTypes[t] = b.(bool)
  798. }
  799. }
  800. m := arg["mode"] // 0: 仅当前 ID,1:向上 2:向下,3:上下都加载,4:加载末尾
  801. mode := 0
  802. if nil != m {
  803. mode = int(m.(float64))
  804. }
  805. s := arg["size"]
  806. size := 102400 // 默认最大加载块数
  807. if nil != s {
  808. size = int(s.(float64))
  809. }
  810. startID := ""
  811. endID := ""
  812. startIDArg := arg["startID"]
  813. endIDArg := arg["endID"]
  814. if nil != startIDArg && nil != endIDArg {
  815. startID = startIDArg.(string)
  816. endID = endIDArg.(string)
  817. size = model.Conf.Editor.DynamicLoadBlocks
  818. }
  819. isBacklinkArg := arg["isBacklink"]
  820. isBacklink := false
  821. if nil != isBacklinkArg {
  822. isBacklink = isBacklinkArg.(bool)
  823. }
  824. blockCount, content, parentID, parent2ID, rootID, typ, eof, scroll, boxID, docPath, isBacklinkExpand, err := model.GetDoc(startID, endID, id, index, query, queryTypes, queryMethod, mode, size, isBacklink)
  825. if model.ErrBlockNotFound == err {
  826. ret.Code = 3
  827. return
  828. }
  829. if nil != err {
  830. ret.Code = 1
  831. ret.Msg = err.Error()
  832. return
  833. }
  834. // 判断是否正在同步中 https://github.com/siyuan-note/siyuan/issues/6290
  835. isSyncing := model.IsSyncingFile(rootID)
  836. ret.Data = map[string]interface{}{
  837. "id": id,
  838. "mode": mode,
  839. "parentID": parentID,
  840. "parent2ID": parent2ID,
  841. "rootID": rootID,
  842. "type": typ,
  843. "content": content,
  844. "blockCount": blockCount,
  845. "eof": eof,
  846. "scroll": scroll,
  847. "box": boxID,
  848. "path": docPath,
  849. "isSyncing": isSyncing,
  850. "isBacklinkExpand": isBacklinkExpand,
  851. }
  852. }
  853. func pushCreate(box *model.Box, p, treeID string, arg map[string]interface{}) {
  854. evt := util.NewCmdResult("create", 0, util.PushModeBroadcast)
  855. name := path.Base(p)
  856. files, _, _ := model.ListDocTree(box.ID, path.Dir(p), util.SortModeUnassigned, false, false, model.Conf.FileTree.MaxListCount)
  857. evt.Data = map[string]interface{}{
  858. "box": box,
  859. "path": p,
  860. "files": files,
  861. "name": name,
  862. "id": treeID,
  863. }
  864. evt.Callback = arg["callback"]
  865. util.PushEvent(evt)
  866. }