path.go 12 KB

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