outline.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  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. "time"
  19. "github.com/88250/lute/ast"
  20. "github.com/88250/lute/parse"
  21. "github.com/emirpasic/gods/stacks/linkedliststack"
  22. "github.com/siyuan-note/siyuan/kernel/treenode"
  23. "github.com/siyuan-note/siyuan/kernel/util"
  24. )
  25. func (tx *Transaction) doMoveOutlineHeading(operation *Operation) (ret *TxErr) {
  26. headingID := operation.ID
  27. previousID := operation.PreviousID
  28. parentID := operation.ParentID
  29. tree, err := tx.loadTree(headingID)
  30. if nil != err {
  31. return &TxErr{code: TxErrCodeBlockNotFound, id: headingID}
  32. }
  33. operation.RetData = tree.Root.ID
  34. if headingID == parentID || headingID == previousID {
  35. return
  36. }
  37. heading := treenode.GetNodeInTree(tree, headingID)
  38. if nil == heading {
  39. return &TxErr{code: TxErrCodeBlockNotFound, id: headingID}
  40. }
  41. if ast.NodeDocument != heading.Parent.Type {
  42. // 仅支持文档根节点下第一层标题,不支持容器块内标题
  43. util.PushMsg(Conf.language(240), 5000)
  44. return
  45. }
  46. headings := []*ast.Node{}
  47. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  48. if entering && ast.NodeHeading == n.Type && !n.ParentIs(ast.NodeBlockquote) {
  49. headings = append(headings, n)
  50. }
  51. return ast.WalkContinue
  52. })
  53. headingChildren := treenode.HeadingChildren(heading)
  54. if "" != previousID {
  55. previousHeading := treenode.GetNodeInTree(tree, previousID)
  56. if nil == previousHeading {
  57. return &TxErr{code: TxErrCodeBlockNotFound, id: previousID}
  58. }
  59. if ast.NodeDocument != previousHeading.Parent.Type {
  60. // 仅支持文档根节点下第一层标题,不支持容器块内标题
  61. util.PushMsg(Conf.language(248), 5000)
  62. return
  63. }
  64. for _, h := range headingChildren {
  65. if h.ID == previousID {
  66. // 不能移动到自己的子标题下
  67. util.PushMsg(Conf.language(241), 5000)
  68. return
  69. }
  70. }
  71. generateOpTypeHistory(tree, HistoryOpOutline)
  72. targetNode := previousHeading
  73. previousHeadingChildren := treenode.HeadingChildren(previousHeading)
  74. if 0 < len(previousHeadingChildren) {
  75. targetNode = previousHeadingChildren[len(previousHeadingChildren)-1]
  76. }
  77. for _, h := range headingChildren {
  78. if h.ID == targetNode.ID {
  79. // 目标节点是当前标题的子节点,不需要移动
  80. return
  81. }
  82. }
  83. diffLevel := heading.HeadingLevel - previousHeading.HeadingLevel
  84. heading.HeadingLevel = previousHeading.HeadingLevel
  85. for i := len(headingChildren) - 1; i >= 0; i-- {
  86. child := headingChildren[i]
  87. if ast.NodeHeading == child.Type {
  88. child.HeadingLevel -= diffLevel
  89. if 6 < child.HeadingLevel {
  90. child.HeadingLevel = 6
  91. }
  92. }
  93. targetNode.InsertAfter(child)
  94. }
  95. targetNode.InsertAfter(heading)
  96. } else if "" != parentID {
  97. parentHeading := treenode.GetNodeInTree(tree, parentID)
  98. if nil == parentHeading {
  99. return &TxErr{code: TxErrCodeBlockNotFound, id: parentID}
  100. }
  101. if ast.NodeDocument != parentHeading.Parent.Type {
  102. // 仅支持文档根节点下第一层标题,不支持容器块内标题
  103. util.PushMsg(Conf.language(248), 5000)
  104. return
  105. }
  106. for _, h := range headingChildren {
  107. if h.ID == parentID {
  108. // 不能移动到自己的子标题下
  109. util.PushMsg(Conf.language(241), 5000)
  110. return
  111. }
  112. }
  113. generateOpTypeHistory(tree, HistoryOpOutline)
  114. targetNode := parentHeading
  115. parentHeadingChildren := treenode.HeadingChildren(parentHeading)
  116. // 找到下方第一个非标题节点
  117. var tmp []*ast.Node
  118. for _, child := range parentHeadingChildren {
  119. if ast.NodeHeading == child.Type {
  120. break
  121. }
  122. tmp = append(tmp, child)
  123. }
  124. parentHeadingChildren = tmp
  125. if 0 < len(parentHeadingChildren) {
  126. for _, child := range parentHeadingChildren {
  127. if child.ID == headingID {
  128. break
  129. }
  130. targetNode = child
  131. }
  132. }
  133. diffLevel := heading.HeadingLevel - parentHeading.HeadingLevel - 1
  134. heading.HeadingLevel = parentHeading.HeadingLevel + 1
  135. if 6 < heading.HeadingLevel {
  136. heading.HeadingLevel = 6
  137. }
  138. for i := len(headingChildren) - 1; i >= 0; i-- {
  139. child := headingChildren[i]
  140. if ast.NodeHeading == child.Type {
  141. child.HeadingLevel -= diffLevel
  142. if 6 < child.HeadingLevel {
  143. child.HeadingLevel = 6
  144. }
  145. }
  146. targetNode.InsertAfter(child)
  147. }
  148. targetNode.InsertAfter(heading)
  149. } else {
  150. generateOpTypeHistory(tree, HistoryOpOutline)
  151. // 移到第一个标题前
  152. var firstHeading *ast.Node
  153. for n := tree.Root.FirstChild; nil != n; n = n.Next {
  154. if ast.NodeHeading == n.Type {
  155. firstHeading = n
  156. break
  157. }
  158. }
  159. if nil == firstHeading || firstHeading.ID == heading.ID {
  160. return
  161. }
  162. diffLevel := heading.HeadingLevel - firstHeading.HeadingLevel
  163. heading.HeadingLevel = firstHeading.HeadingLevel
  164. firstHeading.InsertBefore(heading)
  165. for i := 0; i < len(headingChildren); i++ {
  166. child := headingChildren[i]
  167. if ast.NodeHeading == child.Type {
  168. child.HeadingLevel -= diffLevel
  169. if 6 < child.HeadingLevel {
  170. child.HeadingLevel = 6
  171. }
  172. }
  173. firstHeading.InsertBefore(child)
  174. }
  175. }
  176. if err = tx.writeTree(tree); nil != err {
  177. return
  178. }
  179. return
  180. }
  181. func Outline(rootID string) (ret []*Path, err error) {
  182. time.Sleep(util.FrontendQueueInterval)
  183. WaitForWritingFiles()
  184. ret = []*Path{}
  185. tree, _ := LoadTreeByBlockID(rootID)
  186. if nil == tree {
  187. return
  188. }
  189. ret = outline(tree)
  190. return
  191. }
  192. func outline(tree *parse.Tree) (ret []*Path) {
  193. luteEngine := NewLute()
  194. var headings []*Block
  195. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  196. if entering && ast.NodeHeading == n.Type && !n.ParentIs(ast.NodeBlockquote) {
  197. n.Box, n.Path = tree.Box, tree.Path
  198. block := &Block{
  199. RootID: tree.Root.ID,
  200. Depth: n.HeadingLevel,
  201. Box: n.Box,
  202. Path: n.Path,
  203. ID: n.ID,
  204. Content: renderOutline(n, luteEngine),
  205. Type: n.Type.String(),
  206. SubType: treenode.SubTypeAbbr(n),
  207. }
  208. headings = append(headings, block)
  209. return ast.WalkSkipChildren
  210. }
  211. return ast.WalkContinue
  212. })
  213. if 1 > len(headings) {
  214. return
  215. }
  216. var blocks []*Block
  217. stack := linkedliststack.New()
  218. for _, h := range headings {
  219. L:
  220. for ; ; stack.Pop() {
  221. cur, ok := stack.Peek()
  222. if !ok {
  223. blocks = append(blocks, h)
  224. stack.Push(h)
  225. break L
  226. }
  227. tip := cur.(*Block)
  228. if tip.Depth < h.Depth {
  229. tip.Children = append(tip.Children, h)
  230. stack.Push(h)
  231. break L
  232. }
  233. tip.Count = len(tip.Children)
  234. }
  235. }
  236. ret = toFlatTree(blocks, 0, "outline", tree)
  237. if 0 < len(ret) {
  238. children := ret[0].Blocks
  239. ret = nil
  240. for _, b := range children {
  241. resetDepth(b, 0)
  242. ret = append(ret, &Path{
  243. ID: b.ID,
  244. Box: b.Box,
  245. Name: b.Content,
  246. NodeType: b.Type,
  247. Type: "outline",
  248. SubType: b.SubType,
  249. Blocks: b.Children,
  250. Depth: 0,
  251. Count: b.Count,
  252. })
  253. }
  254. }
  255. return
  256. }
  257. func resetDepth(b *Block, depth int) {
  258. b.Depth = depth
  259. b.Count = len(b.Children)
  260. for _, c := range b.Children {
  261. resetDepth(c, depth+1)
  262. }
  263. }