siyuan/kernel/model/bookmark.go

209 lines
5.8 KiB
Go

// SiYuan - Refactor your thinking
// Copyright (c) 2020-present, b3log.org
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package model
import (
"errors"
"fmt"
"sort"
"strings"
"github.com/88250/gulu"
"github.com/88250/lute/parse"
"github.com/siyuan-note/logging"
"github.com/siyuan-note/siyuan/kernel/av"
"github.com/siyuan-note/siyuan/kernel/cache"
"github.com/siyuan-note/siyuan/kernel/sql"
"github.com/siyuan-note/siyuan/kernel/treenode"
"github.com/siyuan-note/siyuan/kernel/util"
)
func RemoveBookmark(bookmark string) (err error) {
util.PushEndlessProgress(Conf.Language(116))
bookmarks := sql.QueryBookmarkBlocksByKeyword(bookmark)
treeBlocks := map[string][]string{}
for _, tag := range bookmarks {
if blocks, ok := treeBlocks[tag.RootID]; !ok {
treeBlocks[tag.RootID] = []string{tag.ID}
} else {
treeBlocks[tag.RootID] = append(blocks, tag.ID)
}
}
for treeID, blocks := range treeBlocks {
util.PushEndlessProgress("[" + treeID + "]")
tree, e := LoadTreeByBlockID(treeID)
if nil != e {
util.PushClearProgress()
return e
}
for _, blockID := range blocks {
node := treenode.GetNodeInTree(tree, blockID)
if nil == node {
continue
}
if bookmarkAttrVal := node.IALAttr("bookmark"); bookmarkAttrVal == bookmark {
node.RemoveIALAttr("bookmark")
cache.PutBlockIAL(node.ID, parse.IAL2Map(node.KramdownIAL))
}
}
util.PushEndlessProgress(fmt.Sprintf(Conf.Language(111), util.EscapeHTML(tree.Root.IALAttr("title"))))
if err = writeTreeUpsertQueue(tree); nil != err {
util.ClearPushProgress(100)
return
}
util.RandomSleep(50, 150)
}
util.ReloadUI()
return
}
func RenameBookmark(oldBookmark, newBookmark string) (err error) {
if invalidChar := treenode.ContainsMarker(newBookmark); "" != invalidChar {
return errors.New(fmt.Sprintf(Conf.Language(112), invalidChar))
}
newBookmark = strings.TrimSpace(newBookmark)
if "" == newBookmark {
return errors.New(Conf.Language(126))
}
if oldBookmark == newBookmark {
return
}
util.PushEndlessProgress(Conf.Language(110))
bookmarks := sql.QueryBookmarkBlocksByKeyword(oldBookmark)
treeBlocks := map[string][]string{}
for _, tag := range bookmarks {
if blocks, ok := treeBlocks[tag.RootID]; !ok {
treeBlocks[tag.RootID] = []string{tag.ID}
} else {
treeBlocks[tag.RootID] = append(blocks, tag.ID)
}
}
for treeID, blocks := range treeBlocks {
util.PushEndlessProgress("[" + treeID + "]")
tree, e := LoadTreeByBlockID(treeID)
if nil != e {
util.ClearPushProgress(100)
return e
}
for _, blockID := range blocks {
node := treenode.GetNodeInTree(tree, blockID)
if nil == node {
continue
}
if bookmarkAttrVal := node.IALAttr("bookmark"); bookmarkAttrVal == oldBookmark {
node.SetIALAttr("bookmark", newBookmark)
cache.PutBlockIAL(node.ID, parse.IAL2Map(node.KramdownIAL))
}
}
util.PushEndlessProgress(fmt.Sprintf(Conf.Language(111), util.EscapeHTML(tree.Root.IALAttr("title"))))
if err = writeTreeUpsertQueue(tree); nil != err {
util.ClearPushProgress(100)
return
}
util.RandomSleep(50, 150)
}
util.ReloadUI()
return
}
type BookmarkLabel string
type BookmarkBlocks []*Block
type Bookmark struct {
Name BookmarkLabel `json:"name"`
Blocks []*Block `json:"blocks"`
Type string `json:"type"` // "bookmark"
Depth int `json:"depth"`
Count int `json:"count"`
}
type Bookmarks []*Bookmark
func (s Bookmarks) Len() int { return len(s) }
func (s Bookmarks) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s Bookmarks) Less(i, j int) bool { return s[i].Name < s[j].Name }
func BookmarkLabels() (ret []string) {
ret = sql.QueryBookmarkLabels()
return
}
func BuildBookmark() (ret *Bookmarks) {
WaitForWritingFiles()
if !sql.IsEmptyQueue() {
sql.WaitForWritingDatabase()
}
ret = &Bookmarks{}
sqlBlocks := sql.QueryBookmarkBlocks()
labelBlocks := map[BookmarkLabel]BookmarkBlocks{}
blocks := fromSQLBlocks(&sqlBlocks, "", 0)
luteEngine := NewLute()
for _, block := range blocks {
if "" != block.Name {
// Blocks in the bookmark panel display their name instead of content https://github.com/siyuan-note/siyuan/issues/8514
block.Content = block.Name
} else if "NodeAttributeView" == block.Type {
// Display database title in bookmark panel https://github.com/siyuan-note/siyuan/issues/11666
avID := gulu.Str.SubStringBetween(block.Markdown, "av-id=\"", "\"")
block.Content, _ = av.GetAttributeViewName(avID)
} else {
// Improve bookmark panel rendering https://github.com/siyuan-note/siyuan/issues/9361
tree, err := LoadTreeByBlockID(block.ID)
if nil != err {
logging.LogErrorf("parse block [%s] failed: %s", block.ID, err)
} else {
n := treenode.GetNodeInTree(tree, block.ID)
block.Content = renderOutline(n, luteEngine)
}
}
label := BookmarkLabel(block.IAL["bookmark"])
if bs, ok := labelBlocks[label]; ok {
bs = append(bs, block)
labelBlocks[label] = bs
} else {
labelBlocks[label] = []*Block{block}
}
}
for label, bs := range labelBlocks {
for _, b := range bs {
b.Depth = 1
}
*ret = append(*ret, &Bookmark{Name: label, Blocks: bs, Type: "bookmark", Count: len(bs)})
}
sort.Sort(ret)
return
}