path.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  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 model
  17. import (
  18. "bytes"
  19. "os"
  20. "path"
  21. "path/filepath"
  22. "sort"
  23. "strings"
  24. "github.com/88250/lute/ast"
  25. "github.com/88250/lute/parse"
  26. "github.com/siyuan-note/logging"
  27. "github.com/siyuan-note/siyuan/kernel/search"
  28. "github.com/siyuan-note/siyuan/kernel/sql"
  29. "github.com/siyuan-note/siyuan/kernel/treenode"
  30. "github.com/siyuan-note/siyuan/kernel/util"
  31. )
  32. func createDocsByHPath(boxID, hPath, content, parentID string) (id string, existed bool, err error) {
  33. hPath = strings.TrimSuffix(hPath, ".sy")
  34. pathBuilder := bytes.Buffer{}
  35. pathBuilder.WriteString("/")
  36. hPathBuilder := bytes.Buffer{}
  37. hPathBuilder.WriteString("/")
  38. if "" != parentID {
  39. // The save path is incorrect when creating a sub-doc by ref in a doc with the same name https://github.com/siyuan-note/siyuan/issues/8138
  40. // 在指定父文档 ID 的情况下优先查找父文档
  41. parentHPath, name := path.Split(hPath)
  42. parentHPath = strings.TrimSuffix(parentHPath, "/")
  43. preferredParent := treenode.GetBlockTreeRootByHPathPreferredParentID(boxID, parentHPath, parentID)
  44. if nil != preferredParent && preferredParent.ID == parentID {
  45. // 如果父文档存在且 ID 一致,则直接在父文档下创建
  46. id = ast.NewNodeID()
  47. p := strings.TrimSuffix(preferredParent.Path, ".sy") + "/" + id + ".sy"
  48. if _, err = createDoc(boxID, p, name, content); nil != err {
  49. return
  50. }
  51. }
  52. }
  53. parts := strings.Split(hPath, "/")[1:]
  54. for i, part := range parts {
  55. hPathBuilder.WriteString(part)
  56. hp := hPathBuilder.String()
  57. root := treenode.GetBlockTreeRootByHPath(boxID, hp)
  58. isNotLast := i < len(parts)-1
  59. if nil == root {
  60. id = ast.NewNodeID()
  61. pathBuilder.WriteString(id)
  62. docP := pathBuilder.String() + ".sy"
  63. if isNotLast {
  64. if _, err = createDoc(boxID, docP, part, ""); nil != err {
  65. return
  66. }
  67. } else {
  68. if _, err = createDoc(boxID, docP, part, content); nil != err {
  69. return
  70. }
  71. }
  72. if isNotLast {
  73. dirPath := filepath.Join(util.DataDir, boxID, pathBuilder.String())
  74. if err = os.MkdirAll(dirPath, 0755); nil != err {
  75. logging.LogErrorf("mkdir [%s] failed: %s", dirPath, err)
  76. return
  77. }
  78. }
  79. } else {
  80. id = root.ID
  81. pathBuilder.WriteString(root.ID)
  82. if !isNotLast {
  83. pathBuilder.WriteString(".sy")
  84. }
  85. }
  86. if isNotLast {
  87. pathBuilder.WriteString("/")
  88. hPathBuilder.WriteString("/")
  89. }
  90. }
  91. return
  92. }
  93. func toFlatTree(blocks []*Block, baseDepth int, typ string, tree *parse.Tree) (ret []*Path) {
  94. var blockRoots []*Block
  95. for _, block := range blocks {
  96. root := getBlockIn(blockRoots, block.RootID)
  97. if nil == root {
  98. root, _ = getBlock(block.RootID, tree)
  99. blockRoots = append(blockRoots, root)
  100. }
  101. if nil == root {
  102. return
  103. }
  104. block.Depth = baseDepth + 1
  105. block.Count = len(block.Children)
  106. root.Children = append(root.Children, block)
  107. }
  108. for _, root := range blockRoots {
  109. treeNode := &Path{
  110. ID: root.ID,
  111. Box: root.Box,
  112. Name: path.Base(root.HPath),
  113. NodeType: root.Type,
  114. Type: typ,
  115. SubType: root.SubType,
  116. Depth: baseDepth,
  117. Count: len(root.Children),
  118. Updated: root.IAL["updated"],
  119. Created: root.ID[:14],
  120. }
  121. for _, c := range root.Children {
  122. treeNode.Blocks = append(treeNode.Blocks, c)
  123. }
  124. ret = append(ret, treeNode)
  125. if "backlink" == typ {
  126. treeNode.HPath = root.HPath
  127. }
  128. }
  129. sort.Slice(ret, func(i, j int) bool {
  130. return ret[i].ID > ret[j].ID
  131. })
  132. return
  133. }
  134. func toSubTree(blocks []*Block, keyword string) (ret []*Path) {
  135. keyword = strings.TrimSpace(keyword)
  136. var blockRoots []*Block
  137. for _, block := range blocks {
  138. root := getBlockIn(blockRoots, block.RootID)
  139. if nil == root {
  140. root, _ = getBlock(block.RootID, nil)
  141. blockRoots = append(blockRoots, root)
  142. }
  143. block.Depth = 1
  144. block.Count = len(block.Children)
  145. root.Children = append(root.Children, block)
  146. }
  147. for _, root := range blockRoots {
  148. treeNode := &Path{
  149. ID: root.ID,
  150. Box: root.Box,
  151. Name: path.Base(root.HPath),
  152. Type: "backlink",
  153. NodeType: "NodeDocument",
  154. SubType: root.SubType,
  155. Depth: 0,
  156. Count: len(root.Children),
  157. }
  158. for _, c := range root.Children {
  159. if "NodeListItem" == c.Type {
  160. tree, _ := loadTreeByBlockID(c.RootID)
  161. li := treenode.GetNodeInTree(tree, c.ID)
  162. if nil == li || nil == li.FirstChild {
  163. // 反链面板拖拽到文档以后可能会出现这种情况 https://github.com/siyuan-note/siyuan/issues/5363
  164. continue
  165. }
  166. var first *sql.Block
  167. if 3 != li.ListData.Typ {
  168. first = sql.GetBlock(li.FirstChild.ID)
  169. } else {
  170. first = sql.GetBlock(li.FirstChild.Next.ID)
  171. }
  172. name := first.Content
  173. parentPos := 0
  174. if "" != keyword {
  175. parentPos, name = search.MarkText(name, keyword, 12, Conf.Search.CaseSensitive)
  176. }
  177. subRoot := &Path{
  178. ID: li.ID,
  179. Box: li.Box,
  180. Name: name,
  181. Type: "backlink",
  182. NodeType: li.Type.String(),
  183. SubType: c.SubType,
  184. Depth: 1,
  185. Count: 1,
  186. }
  187. unfold := true
  188. for liFirstBlockSpan := li.FirstChild.FirstChild; nil != liFirstBlockSpan; liFirstBlockSpan = liFirstBlockSpan.Next {
  189. if treenode.IsBlockRef(liFirstBlockSpan) {
  190. continue
  191. }
  192. if "" != strings.TrimSpace(liFirstBlockSpan.Text()) {
  193. unfold = false
  194. break
  195. }
  196. }
  197. for next := li.FirstChild.Next; nil != next; next = next.Next {
  198. subBlock, _ := getBlock(next.ID, tree)
  199. if unfold {
  200. if ast.NodeList == next.Type {
  201. for subLi := next.FirstChild; nil != subLi; subLi = subLi.Next {
  202. subLiBlock, _ := getBlock(subLi.ID, tree)
  203. var subFirst *sql.Block
  204. if 3 != subLi.ListData.Typ {
  205. subFirst = sql.GetBlock(subLi.FirstChild.ID)
  206. } else {
  207. subFirst = sql.GetBlock(subLi.FirstChild.Next.ID)
  208. }
  209. subPos := 0
  210. content := subFirst.Content
  211. if "" != keyword {
  212. subPos, content = search.MarkText(subFirst.Content, keyword, 12, Conf.Search.CaseSensitive)
  213. }
  214. if -1 < subPos {
  215. parentPos = 0 // 需要显示父级
  216. }
  217. subLiBlock.Content = content
  218. subLiBlock.Depth = 2
  219. subRoot.Blocks = append(subRoot.Blocks, subLiBlock)
  220. }
  221. } else if ast.NodeHeading == next.Type {
  222. subBlock.Depth = 2
  223. subRoot.Blocks = append(subRoot.Blocks, subBlock)
  224. headingChildren := treenode.HeadingChildren(next)
  225. var breakSub bool
  226. for _, n := range headingChildren {
  227. block, _ := getBlock(n.ID, tree)
  228. subPos := 0
  229. content := block.Content
  230. if "" != keyword {
  231. subPos, content = search.MarkText(block.Content, keyword, 12, Conf.Search.CaseSensitive)
  232. }
  233. if -1 < subPos {
  234. parentPos = 0
  235. }
  236. block.Content = content
  237. block.Depth = 3
  238. subRoot.Blocks = append(subRoot.Blocks, block)
  239. if ast.NodeHeading == n.Type {
  240. // 跳过子标题下面的块
  241. breakSub = true
  242. break
  243. }
  244. }
  245. if breakSub {
  246. break
  247. }
  248. } else {
  249. if nil == treenode.HeadingParent(next) {
  250. subBlock.Depth = 2
  251. subRoot.Blocks = append(subRoot.Blocks, subBlock)
  252. }
  253. }
  254. }
  255. }
  256. if -1 < parentPos {
  257. treeNode.Children = append(treeNode.Children, subRoot)
  258. }
  259. } else if "NodeHeading" == c.Type {
  260. tree, _ := loadTreeByBlockID(c.RootID)
  261. h := treenode.GetNodeInTree(tree, c.ID)
  262. if nil == h {
  263. continue
  264. }
  265. name := sql.GetBlock(h.ID).Content
  266. parentPos := 0
  267. if "" != keyword {
  268. parentPos, name = search.MarkText(name, keyword, 12, Conf.Search.CaseSensitive)
  269. }
  270. subRoot := &Path{
  271. ID: h.ID,
  272. Box: h.Box,
  273. Name: name,
  274. Type: "backlink",
  275. NodeType: h.Type.String(),
  276. SubType: c.SubType,
  277. Depth: 1,
  278. Count: 1,
  279. }
  280. unfold := true
  281. for headingFirstSpan := h.FirstChild; nil != headingFirstSpan; headingFirstSpan = headingFirstSpan.Next {
  282. if treenode.IsBlockRef(headingFirstSpan) {
  283. continue
  284. }
  285. if "" != strings.TrimSpace(headingFirstSpan.Text()) {
  286. unfold = false
  287. break
  288. }
  289. }
  290. if unfold {
  291. headingChildren := treenode.HeadingChildren(h)
  292. for _, headingChild := range headingChildren {
  293. if ast.NodeList == headingChild.Type {
  294. for subLi := headingChild.FirstChild; nil != subLi; subLi = subLi.Next {
  295. subLiBlock, _ := getBlock(subLi.ID, tree)
  296. var subFirst *sql.Block
  297. if 3 != subLi.ListData.Typ {
  298. subFirst = sql.GetBlock(subLi.FirstChild.ID)
  299. } else {
  300. subFirst = sql.GetBlock(subLi.FirstChild.Next.ID)
  301. }
  302. subPos := 0
  303. content := subFirst.Content
  304. if "" != keyword {
  305. subPos, content = search.MarkText(content, keyword, 12, Conf.Search.CaseSensitive)
  306. }
  307. if -1 < subPos {
  308. parentPos = 0
  309. }
  310. subLiBlock.Content = subFirst.Content
  311. subLiBlock.Depth = 2
  312. subRoot.Blocks = append(subRoot.Blocks, subLiBlock)
  313. }
  314. } else {
  315. subBlock, _ := getBlock(headingChild.ID, tree)
  316. subBlock.Depth = 2
  317. subRoot.Blocks = append(subRoot.Blocks, subBlock)
  318. }
  319. }
  320. }
  321. if -1 < parentPos {
  322. treeNode.Children = append(treeNode.Children, subRoot)
  323. }
  324. } else {
  325. pos := 0
  326. content := c.Content
  327. if "" != keyword {
  328. pos, content = search.MarkText(content, keyword, 12, Conf.Search.CaseSensitive)
  329. }
  330. if -1 < pos {
  331. treeNode.Blocks = append(treeNode.Blocks, c)
  332. }
  333. }
  334. }
  335. rootPos := -1
  336. var rootContent string
  337. if "" != keyword {
  338. rootPos, rootContent = search.MarkText(treeNode.Name, keyword, 12, Conf.Search.CaseSensitive)
  339. treeNode.Name = rootContent
  340. }
  341. if 0 < len(treeNode.Children) || 0 < len(treeNode.Blocks) || (-1 < rootPos && "" != keyword) {
  342. ret = append(ret, treeNode)
  343. }
  344. }
  345. sort.Slice(ret, func(i, j int) bool {
  346. return ret[i].ID > ret[j].ID
  347. })
  348. return
  349. }
  350. func getBlockIn(blocks []*Block, id string) *Block {
  351. if "" == id {
  352. return nil
  353. }
  354. for _, block := range blocks {
  355. if block.ID == id {
  356. return block
  357. }
  358. }
  359. return nil
  360. }