2023-06-24 20:39:55 +08:00
// SiYuan - Refactor your thinking
2022-05-26 15:18:53 +08:00
// 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 treenode
import (
"bytes"
2023-10-05 12:02:17 +08:00
"github.com/siyuan-note/siyuan/kernel/av"
2023-10-13 10:44:29 +08:00
"github.com/siyuan-note/siyuan/kernel/cache"
2022-05-26 15:18:53 +08:00
"strings"
2022-09-26 20:54:22 +08:00
"sync"
2023-10-13 10:44:29 +08:00
"text/template"
"time"
2022-05-26 15:18:53 +08:00
2023-01-15 23:50:12 +08:00
"github.com/88250/gulu"
2022-05-26 15:18:53 +08:00
"github.com/88250/lute"
"github.com/88250/lute/ast"
2022-09-14 01:30:10 +08:00
"github.com/88250/lute/editor"
2022-05-26 15:18:53 +08:00
"github.com/88250/lute/html"
"github.com/88250/lute/lex"
"github.com/88250/lute/parse"
2022-09-14 01:30:10 +08:00
"github.com/88250/lute/render"
2023-01-17 22:16:14 +08:00
"github.com/88250/vitess-sqlparser/sqlparser"
2022-07-17 12:22:32 +08:00
"github.com/siyuan-note/logging"
2023-01-17 22:16:14 +08:00
"github.com/siyuan-note/siyuan/kernel/util"
2022-05-26 15:18:53 +08:00
)
2023-01-17 22:16:14 +08:00
func GetEmbedBlockRef ( embedNode * ast . Node ) ( blockRefID string ) {
if nil == embedNode || ast . NodeBlockQueryEmbed != embedNode . Type {
return
}
scriptNode := embedNode . ChildByType ( ast . NodeBlockQueryEmbedScript )
if nil == scriptNode {
return
}
stmt := scriptNode . TokensStr ( )
parsedStmt , err := sqlparser . Parse ( stmt )
if nil != err {
return
}
switch parsedStmt . ( type ) {
case * sqlparser . Select :
slct := parsedStmt . ( * sqlparser . Select )
if nil == slct . Where || nil == slct . Where . Expr {
return
}
switch slct . Where . Expr . ( type ) {
case * sqlparser . ComparisonExpr : // WHERE id = '20060102150405-1a2b3c4'
comp := slct . Where . Expr . ( * sqlparser . ComparisonExpr )
switch comp . Left . ( type ) {
case * sqlparser . ColName :
col := comp . Left . ( * sqlparser . ColName )
if nil == col || "id" != col . Name . Lowered ( ) {
return
}
}
switch comp . Right . ( type ) {
case * sqlparser . SQLVal :
val := comp . Right . ( * sqlparser . SQLVal )
if nil == val || sqlparser . StrVal != val . Type {
return
}
idVal := string ( val . Val )
if ! ast . IsNodeIDPattern ( idVal ) {
return
}
blockRefID = idVal
}
}
}
return
}
2022-09-16 18:02:04 +08:00
func GetBlockRef ( n * ast . Node ) ( blockRefID , blockRefText , blockRefSubtype string ) {
if ! IsBlockRef ( n ) {
return
}
2022-12-08 20:32:42 +08:00
blockRefID = n . TextMarkBlockRefID
blockRefText = n . TextMarkTextContent
blockRefSubtype = n . TextMarkBlockRefSubtype
2022-09-16 18:02:04 +08:00
return
}
func IsBlockRef ( n * ast . Node ) bool {
if nil == n {
return false
}
2022-12-08 20:32:42 +08:00
return ast . NodeTextMark == n . Type && n . IsTextMarkType ( "block-ref" )
2022-09-16 18:02:04 +08:00
}
2023-01-17 22:16:14 +08:00
func IsFileAnnotationRef ( n * ast . Node ) bool {
if nil == n {
return false
}
return ast . NodeTextMark == n . Type && n . IsTextMarkType ( "file-annotation-ref" )
}
func IsEmbedBlockRef ( n * ast . Node ) bool {
return "" != GetEmbedBlockRef ( n )
}
2022-05-26 15:18:53 +08:00
func FormatNode ( node * ast . Node , luteEngine * lute . Lute ) string {
markdown , err := lute . FormatNodeSync ( node , luteEngine . ParseOptions , luteEngine . RenderOptions )
if nil != err {
root := TreeRoot ( node )
2023-03-19 00:12:28 +08:00
logging . LogFatalf ( logging . ExitCodeFatal , "format node [%s] in tree [%s] failed: %s" , node . ID , root . ID , err )
2022-05-26 15:18:53 +08:00
}
return markdown
}
2022-09-19 09:48:25 +08:00
func ExportNodeStdMd ( node * ast . Node , luteEngine * lute . Lute ) string {
markdown , err := lute . ProtyleExportMdNodeSync ( node , luteEngine . ParseOptions , luteEngine . RenderOptions )
if nil != err {
root := TreeRoot ( node )
2023-03-19 00:12:28 +08:00
logging . LogFatalf ( logging . ExitCodeFatal , "export markdown for node [%s] in tree [%s] failed: %s" , node . ID , root . ID , err )
2022-09-19 09:48:25 +08:00
}
return markdown
}
2023-12-07 20:40:16 +08:00
func IsNodeOCRed ( node * ast . Node ) ( ret bool ) {
2023-12-19 11:38:56 +08:00
if ! util . TesseractEnabled || nil == node {
2023-12-19 12:30:07 +08:00
return true
2023-12-19 11:38:56 +08:00
}
2023-12-07 20:40:16 +08:00
ret = true
ast . Walk ( node , func ( n * ast . Node , entering bool ) ast . WalkStatus {
if ! entering {
return ast . WalkContinue
}
if ast . NodeImage == n . Type {
linkDest := n . ChildByType ( ast . NodeLinkDest )
2023-12-19 11:38:56 +08:00
if nil == linkDest {
return ast . WalkContinue
}
2023-12-07 20:40:16 +08:00
2023-12-19 11:38:56 +08:00
linkDestStr := linkDest . TokensStr ( )
if ! cache . ExistAsset ( linkDestStr ) {
return ast . WalkContinue
}
if ! util . ExistsAssetText ( linkDestStr ) {
ret = false
return ast . WalkStop
2023-12-07 20:40:16 +08:00
}
}
return ast . WalkContinue
} )
return
}
2024-01-01 01:08:55 +08:00
func NodeStaticContent ( node * ast . Node , excludeTypes [ ] string , includeTextMarkATitleURL , includeAssetPath , fullAttrView bool ) string {
2022-05-26 15:18:53 +08:00
if nil == node {
return ""
}
if ast . NodeDocument == node . Type {
return node . IALAttr ( "title" )
2023-10-13 10:44:29 +08:00
}
if ast . NodeAttributeView == node . Type {
2024-01-01 01:08:55 +08:00
if fullAttrView {
return getAttributeViewContent ( node . AttributeViewID )
}
2024-04-05 18:52:04 +08:00
ret , _ := av . GetAttributeViewName ( node . AttributeViewID )
return ret
2022-05-26 15:18:53 +08:00
}
buf := bytes . Buffer { }
buf . Grow ( 4096 )
lastSpace := false
ast . Walk ( node , func ( n * ast . Node , entering bool ) ast . WalkStatus {
if ! entering {
return ast . WalkContinue
}
if n . IsContainerBlock ( ) {
if ! lastSpace {
2023-10-13 10:44:29 +08:00
buf . WriteByte ( ' ' )
2022-05-26 15:18:53 +08:00
lastSpace = true
}
return ast . WalkContinue
}
2022-12-26 19:28:47 +08:00
if gulu . Str . Contains ( n . Type . String ( ) , excludeTypes ) {
return ast . WalkContinue
}
2022-05-26 15:18:53 +08:00
switch n . Type {
2023-03-13 21:34:20 +08:00
case ast . NodeTableCell :
// 表格块写入数据库表时在单元格之间添加空格 https://github.com/siyuan-note/siyuan/issues/7654
if 0 < buf . Len ( ) && ' ' != buf . Bytes ( ) [ buf . Len ( ) - 1 ] {
buf . WriteByte ( ' ' )
}
2023-01-16 15:20:26 +08:00
case ast . NodeImage :
linkDest := n . ChildByType ( ast . NodeLinkDest )
var linkDestStr , ocrText string
if nil != linkDest {
linkDestStr = linkDest . TokensStr ( )
2023-04-14 15:22:41 +08:00
ocrText = util . GetAssetText ( linkDestStr , false )
2023-01-16 15:20:26 +08:00
}
2023-01-15 23:50:12 +08:00
2023-01-16 15:20:26 +08:00
linkText := n . ChildByType ( ast . NodeLinkText )
if nil != linkText {
buf . Write ( linkText . Tokens )
buf . WriteByte ( ' ' )
}
if "" != ocrText {
buf . WriteString ( ocrText )
buf . WriteByte ( ' ' )
2023-01-15 23:50:12 +08:00
}
2023-01-16 15:20:26 +08:00
if nil != linkDest {
2023-05-10 10:30:05 +08:00
if ! bytes . HasPrefix ( linkDest . Tokens , [ ] byte ( "assets/" ) ) || includeAssetPath {
2023-04-22 16:26:38 +08:00
buf . Write ( linkDest . Tokens )
buf . WriteByte ( ' ' )
}
2023-01-16 15:20:26 +08:00
}
if linkTitle := n . ChildByType ( ast . NodeLinkTitle ) ; nil != linkTitle {
buf . Write ( linkTitle . Tokens )
}
return ast . WalkSkipChildren
case ast . NodeLinkText :
buf . Write ( n . Tokens )
2022-06-23 10:53:00 +08:00
buf . WriteByte ( ' ' )
case ast . NodeLinkDest :
buf . Write ( n . Tokens )
buf . WriteByte ( ' ' )
case ast . NodeLinkTitle :
buf . Write ( n . Tokens )
2022-12-08 20:32:42 +08:00
case ast . NodeText , ast . NodeCodeBlockCode , ast . NodeMathBlockContent , ast . NodeHTMLBlock :
2022-10-24 11:24:13 +08:00
tokens := n . Tokens
if IsChartCodeBlockCode ( n ) {
// 图表块的内容在数据库 `blocks` 表 `content` 字段中被转义 https://github.com/siyuan-note/siyuan/issues/6326
tokens = html . UnescapeHTML ( tokens )
}
buf . Write ( tokens )
2022-09-14 19:46:01 +08:00
case ast . NodeTextMark :
2022-12-26 19:28:47 +08:00
for _ , excludeType := range excludeTypes {
2022-12-26 19:37:17 +08:00
if strings . HasPrefix ( excludeType , "NodeTextMark-" ) {
if n . IsTextMarkType ( excludeType [ len ( "NodeTextMark-" ) : ] ) {
2022-12-26 19:28:47 +08:00
return ast . WalkContinue
}
}
}
2022-09-20 11:38:36 +08:00
if n . IsTextMarkType ( "tag" ) {
buf . WriteByte ( '#' )
}
2022-09-14 19:53:22 +08:00
buf . WriteString ( n . Content ( ) )
2022-09-20 11:38:36 +08:00
if n . IsTextMarkType ( "tag" ) {
buf . WriteByte ( '#' )
}
2023-02-21 10:48:51 +08:00
if n . IsTextMarkType ( "a" ) && includeTextMarkATitleURL {
2023-02-12 22:18:54 +08:00
// 搜索不到超链接元素的 URL 和标题 https://github.com/siyuan-note/siyuan/issues/7352
if "" != n . TextMarkATitle {
2023-05-18 09:38:26 +08:00
buf . WriteString ( " " + html . UnescapeHTMLStr ( n . TextMarkATitle ) )
2023-02-12 22:18:54 +08:00
}
2023-04-22 16:26:38 +08:00
2023-05-10 10:30:05 +08:00
if ! strings . HasPrefix ( n . TextMarkAHref , "assets/" ) || includeAssetPath {
2023-05-18 09:38:26 +08:00
buf . WriteString ( " " + html . UnescapeHTMLStr ( n . TextMarkAHref ) )
2023-04-22 16:26:38 +08:00
}
2023-02-12 22:18:54 +08:00
}
2022-05-26 15:18:53 +08:00
case ast . NodeBackslash :
buf . WriteByte ( lex . ItemBackslash )
case ast . NodeBackslashContent :
buf . Write ( n . Tokens )
2024-02-28 21:08:46 +08:00
case ast . NodeAudio , ast . NodeVideo :
buf . WriteString ( GetNodeSrcTokens ( n ) )
buf . WriteByte ( ' ' )
2022-05-26 15:18:53 +08:00
}
lastSpace = false
return ast . WalkContinue
} )
2024-01-23 12:01:36 +08:00
// 这里不要 trim, 否则无法搜索首尾空格
// Improve search and replace for spaces https://github.com/siyuan-note/siyuan/issues/10231
return buf . String ( )
2022-05-26 15:18:53 +08:00
}
2024-02-28 21:08:46 +08:00
func GetNodeSrcTokens ( n * ast . Node ) ( ret string ) {
if index := bytes . Index ( n . Tokens , [ ] byte ( "src=\"" ) ) ; 0 < index {
src := n . Tokens [ index + len ( "src=\"" ) : ]
if index = bytes . Index ( src , [ ] byte ( "\"" ) ) ; 0 < index {
src = src [ : bytes . Index ( src , [ ] byte ( "\"" ) ) ]
if ! IsRelativePath ( src ) {
return
}
ret = strings . TrimSpace ( string ( src ) )
return
}
logging . LogWarnf ( "src is missing the closing double quote in tree [%s] " , n . Box + n . Path )
}
return
}
func IsRelativePath ( dest [ ] byte ) bool {
if 1 > len ( dest ) {
return false
}
if '/' == dest [ 0 ] {
return false
}
return ! bytes . Contains ( dest , [ ] byte ( ":" ) )
}
2022-05-26 15:18:53 +08:00
func FirstLeafBlock ( node * ast . Node ) ( ret * ast . Node ) {
ast . Walk ( node , func ( n * ast . Node , entering bool ) ast . WalkStatus {
if ! entering || n . IsMarker ( ) {
return ast . WalkContinue
}
if ! n . IsContainerBlock ( ) {
ret = n
return ast . WalkStop
}
return ast . WalkContinue
} )
return
}
func CountBlockNodes ( node * ast . Node ) ( ret int ) {
ast . Walk ( node , func ( n * ast . Node , entering bool ) ast . WalkStatus {
2024-04-06 10:50:25 +08:00
if ! entering || ! n . IsBlock ( ) || ast . NodeList == n . Type || ast . NodeListItem == n . Type || ast . NodeBlockquote == n . Type || ast . NodeSuperBlock == n . Type {
2022-05-26 15:18:53 +08:00
return ast . WalkContinue
}
if "1" == n . IALAttr ( "fold" ) {
ret ++
return ast . WalkSkipChildren
}
ret ++
return ast . WalkContinue
} )
return
}
func ParentNodes ( node * ast . Node ) ( parents [ ] * ast . Node ) {
2022-10-14 16:46:15 +08:00
const maxDepth = 256
i := 0
2022-05-26 15:18:53 +08:00
for n := node . Parent ; nil != n ; n = n . Parent {
2022-10-14 16:46:15 +08:00
i ++
2022-05-26 15:18:53 +08:00
parents = append ( parents , n )
if ast . NodeDocument == n . Type {
return
}
2022-10-14 16:46:15 +08:00
if maxDepth < i {
logging . LogWarnf ( "parent nodes of node [%s] is too deep" , node . ID )
return
}
2022-05-26 15:18:53 +08:00
}
return
}
2023-02-02 22:35:01 +08:00
func ChildBlockNodes ( node * ast . Node ) ( children [ ] * ast . Node ) {
children = [ ] * ast . Node { }
if ! node . IsContainerBlock ( ) || ast . NodeDocument == node . Type {
children = append ( children , node )
return
}
ast . Walk ( node , func ( n * ast . Node , entering bool ) ast . WalkStatus {
if ! entering || ! n . IsBlock ( ) {
return ast . WalkContinue
}
children = append ( children , n )
return ast . WalkContinue
} )
return
}
2022-05-26 15:18:53 +08:00
func ParentBlock ( node * ast . Node ) * ast . Node {
for p := node . Parent ; nil != p ; p = p . Parent {
if "" != p . ID && p . IsBlock ( ) {
return p
}
}
return nil
}
func GetNodeInTree ( tree * parse . Tree , id string ) ( ret * ast . Node ) {
ast . Walk ( tree . Root , func ( n * ast . Node , entering bool ) ast . WalkStatus {
if ! entering {
return ast . WalkContinue
}
if id == n . ID {
ret = n
ret . Box = tree . Box
ret . Path = tree . Path
return ast . WalkStop
}
return ast . WalkContinue
} )
return
}
func GetDocTitleImgPath ( root * ast . Node ) ( ret string ) {
if nil == root {
return
}
const background = "background-image: url("
titleImg := root . IALAttr ( "title-img" )
titleImg = strings . TrimSpace ( titleImg )
titleImg = html . UnescapeString ( titleImg )
titleImg = strings . ReplaceAll ( titleImg , "background-image:url(" , background )
if ! strings . Contains ( titleImg , background ) {
return
}
start := strings . Index ( titleImg , background ) + len ( background )
end := strings . LastIndex ( titleImg , ")" )
ret = titleImg [ start : end ]
ret = strings . TrimPrefix ( ret , "\"" )
ret = strings . TrimPrefix ( ret , "'" )
ret = strings . TrimSuffix ( ret , "\"" )
ret = strings . TrimSuffix ( ret , "'" )
return ret
}
var typeAbbrMap = map [ string ] string {
// 块级元素
"NodeDocument" : "d" ,
"NodeHeading" : "h" ,
"NodeList" : "l" ,
"NodeListItem" : "i" ,
"NodeCodeBlock" : "c" ,
"NodeMathBlock" : "m" ,
"NodeTable" : "t" ,
"NodeBlockquote" : "b" ,
"NodeSuperBlock" : "s" ,
"NodeParagraph" : "p" ,
"NodeHTMLBlock" : "html" ,
"NodeBlockQueryEmbed" : "query_embed" ,
2023-10-05 12:37:34 +08:00
"NodeAttributeView" : "av" ,
2022-05-26 15:18:53 +08:00
"NodeKramdownBlockIAL" : "ial" ,
"NodeIFrame" : "iframe" ,
"NodeWidget" : "widget" ,
"NodeThematicBreak" : "tb" ,
"NodeVideo" : "video" ,
"NodeAudio" : "audio" ,
2023-12-16 23:25:14 +08:00
// 行级元素
"NodeText" : "text" ,
"NodeImage" : "img" ,
"NodeLinkText" : "link_text" ,
"NodeLinkDest" : "link_dest" ,
"NodeTextMark" : "textmark" ,
2022-05-26 15:18:53 +08:00
}
var abbrTypeMap = map [ string ] string { }
func init ( ) {
for typ , abbr := range typeAbbrMap {
abbrTypeMap [ abbr ] = typ
}
}
func TypeAbbr ( nodeType string ) string {
return typeAbbrMap [ nodeType ]
}
func FromAbbrType ( abbrType string ) string {
return abbrTypeMap [ abbrType ]
}
func SubTypeAbbr ( n * ast . Node ) string {
switch n . Type {
case ast . NodeList , ast . NodeListItem :
if 0 == n . ListData . Typ {
return "u"
}
if 1 == n . ListData . Typ {
return "o"
}
if 3 == n . ListData . Typ {
return "t"
}
case ast . NodeHeading :
if 1 == n . HeadingLevel {
return "h1"
}
if 2 == n . HeadingLevel {
return "h2"
}
if 3 == n . HeadingLevel {
return "h3"
}
if 4 == n . HeadingLevel {
return "h4"
}
if 5 == n . HeadingLevel {
return "h5"
}
if 6 == n . HeadingLevel {
return "h6"
}
}
return ""
}
2022-09-26 20:54:22 +08:00
var DynamicRefTexts = sync . Map { }
2022-05-26 15:18:53 +08:00
func SetDynamicBlockRefText ( blockRef * ast . Node , refText string ) {
2022-09-16 18:02:04 +08:00
if ! IsBlockRef ( blockRef ) {
2022-05-26 15:18:53 +08:00
return
}
2022-09-16 18:02:04 +08:00
blockRef . TextMarkBlockRefSubtype = "d"
blockRef . TextMarkTextContent = refText
2022-09-26 20:54:22 +08:00
// 偶发编辑文档标题后引用处的动态锚文本不更新 https://github.com/siyuan-note/siyuan/issues/5891
DynamicRefTexts . Store ( blockRef . TextMarkBlockRefID , refText )
2022-05-26 15:18:53 +08:00
}
2022-09-14 01:30:10 +08:00
func IsChartCodeBlockCode ( code * ast . Node ) bool {
if nil == code . Previous || ast . NodeCodeBlockFenceInfoMarker != code . Previous . Type || 1 > len ( code . Previous . CodeBlockInfo ) {
return false
}
2023-01-17 22:16:14 +08:00
language := gulu . Str . FromBytes ( code . Previous . CodeBlockInfo )
2022-09-14 01:30:10 +08:00
language = strings . ReplaceAll ( language , editor . Caret , "" )
return render . NoHighlight ( language )
}
2023-10-13 10:44:29 +08:00
func getAttributeViewContent ( avID string ) ( content string ) {
if "" == avID {
return
}
attrView , err := av . ParseAttributeView ( avID )
if nil != err {
logging . LogErrorf ( "parse attribute view [%s] failed: %s" , avID , err )
return
}
buf := bytes . Buffer { }
2023-12-01 09:05:28 +08:00
buf . WriteString ( attrView . Name )
buf . WriteByte ( ' ' )
2023-10-13 10:44:29 +08:00
for _ , v := range attrView . Views {
buf . WriteString ( v . Name )
buf . WriteByte ( ' ' )
}
if 1 > len ( attrView . Views ) {
content = strings . TrimSpace ( buf . String ( ) )
return
}
var view * av . View
for _ , v := range attrView . Views {
if av . LayoutTypeTable == v . LayoutType {
view = v
break
}
}
if nil == view {
2023-12-01 09:05:28 +08:00
content = strings . TrimSpace ( buf . String ( ) )
2023-10-13 10:44:29 +08:00
return
}
table , err := renderAttributeViewTable ( attrView , view )
if nil != err {
content = strings . TrimSpace ( buf . String ( ) )
return
}
for _ , col := range table . Columns {
buf . WriteString ( col . Name )
buf . WriteByte ( ' ' )
}
for _ , row := range table . Rows {
for _ , cell := range row . Cells {
if nil == cell . Value {
continue
}
buf . WriteString ( cell . Value . String ( ) )
buf . WriteByte ( ' ' )
}
}
content = strings . TrimSpace ( buf . String ( ) )
return
}
func renderAttributeViewTable ( attrView * av . AttributeView , view * av . View ) ( ret * av . Table , err error ) {
ret = & av . Table {
2024-03-01 22:40:56 +08:00
ID : view . ID ,
Icon : view . Icon ,
Name : view . Name ,
HideAttrViewName : view . HideAttrViewName ,
Columns : [ ] * av . TableColumn { } ,
Rows : [ ] * av . TableRow { } ,
2023-10-13 10:44:29 +08:00
}
// 组装列
for _ , col := range view . Table . Columns {
2023-12-24 09:55:12 +08:00
key , _ := attrView . GetKey ( col . ID )
if nil == key {
continue
2023-10-13 10:44:29 +08:00
}
ret . Columns = append ( ret . Columns , & av . TableColumn {
ID : key . ID ,
Name : key . Name ,
Type : key . Type ,
Icon : key . Icon ,
Options : key . Options ,
NumberFormat : key . NumberFormat ,
Template : key . Template ,
2023-12-23 17:45:46 +08:00
Relation : key . Relation ,
Rollup : key . Rollup ,
2024-04-05 21:46:41 +08:00
Date : key . Date ,
2023-10-13 10:44:29 +08:00
Wrap : col . Wrap ,
Hidden : col . Hidden ,
Width : col . Width ,
2023-11-10 10:22:19 +08:00
Pin : col . Pin ,
2023-10-13 10:44:29 +08:00
Calc : col . Calc ,
} )
}
// 生成行
rows := map [ string ] [ ] * av . KeyValues { }
for _ , keyValues := range attrView . KeyValues {
for _ , val := range keyValues . Values {
values := rows [ val . BlockID ]
if nil == values {
values = [ ] * av . KeyValues { { Key : keyValues . Key , Values : [ ] * av . Value { val } } }
} else {
values = append ( values , & av . KeyValues { Key : keyValues . Key , Values : [ ] * av . Value { val } } )
}
rows [ val . BlockID ] = values
}
}
// 过滤掉不存在的行
var notFound [ ] string
for blockID , keyValues := range rows {
blockValue := getRowBlockValue ( keyValues )
if nil == blockValue {
notFound = append ( notFound , blockID )
continue
}
if blockValue . IsDetached {
continue
}
if nil != blockValue . Block && "" == blockValue . Block . ID {
notFound = append ( notFound , blockID )
continue
}
if GetBlockTree ( blockID ) == nil {
notFound = append ( notFound , blockID )
}
}
for _ , blockID := range notFound {
delete ( rows , blockID )
}
// 生成行单元格
for rowID , row := range rows {
var tableRow av . TableRow
for _ , col := range ret . Columns {
var tableCell * av . TableCell
for _ , keyValues := range row {
if keyValues . Key . ID == col . ID {
tableCell = & av . TableCell {
ID : keyValues . Values [ 0 ] . ID ,
Value : keyValues . Values [ 0 ] ,
ValueType : col . Type ,
}
break
}
}
if nil == tableCell {
tableCell = & av . TableCell {
ID : ast . NewNodeID ( ) ,
ValueType : col . Type ,
}
}
tableRow . ID = rowID
switch tableCell . ValueType {
case av . KeyTypeNumber : // 格式化数字
if nil != tableCell . Value && nil != tableCell . Value . Number && tableCell . Value . Number . IsNotEmpty {
tableCell . Value . Number . Format = col . NumberFormat
tableCell . Value . Number . FormatNumber ( )
}
case av . KeyTypeTemplate : // 渲染模板列
tableCell . Value = & av . Value { ID : tableCell . ID , KeyID : col . ID , BlockID : rowID , Type : av . KeyTypeTemplate , Template : & av . ValueTemplate { Content : col . Template } }
case av . KeyTypeCreated : // 填充创建时间列值,后面再渲染
tableCell . Value = & av . Value { ID : tableCell . ID , KeyID : col . ID , BlockID : rowID , Type : av . KeyTypeCreated }
case av . KeyTypeUpdated : // 填充更新时间列值,后面再渲染
tableCell . Value = & av . Value { ID : tableCell . ID , KeyID : col . ID , BlockID : rowID , Type : av . KeyTypeUpdated }
2023-12-30 12:08:20 +08:00
case av . KeyTypeRelation : // 清空关联列值,后面再渲染 https://ld246.com/article/1703831044435
if nil != tableCell . Value && nil != tableCell . Value . Relation {
tableCell . Value . Relation . Contents = nil
}
2023-10-13 10:44:29 +08:00
}
2023-10-27 22:28:56 +08:00
FillAttributeViewTableCellNilValue ( tableCell , rowID , col . ID )
2023-10-13 10:44:29 +08:00
tableRow . Cells = append ( tableRow . Cells , tableCell )
}
ret . Rows = append ( ret . Rows , & tableRow )
}
2024-02-26 09:18:44 +08:00
// 渲染自动生成的列值,比如关联列、汇总列、创建时间列和更新时间列
2023-10-13 10:44:29 +08:00
for _ , row := range ret . Rows {
for _ , cell := range row . Cells {
switch cell . ValueType {
2023-12-24 22:27:38 +08:00
case av . KeyTypeRollup : // 渲染汇总列
rollupKey , _ := attrView . GetKey ( cell . Value . KeyID )
if nil == rollupKey || nil == rollupKey . Rollup {
break
}
relKey , _ := attrView . GetKey ( rollupKey . Rollup . RelationKeyID )
if nil == relKey || nil == relKey . Relation {
break
}
relVal := attrView . GetValue ( relKey . ID , row . ID )
if nil == relVal || nil == relVal . Relation {
break
}
destAv , _ := av . ParseAttributeView ( relKey . Relation . AvID )
if nil == destAv {
break
}
2024-01-01 16:27:01 +08:00
destKey , _ := destAv . GetKey ( rollupKey . Rollup . KeyID )
if nil == destKey {
continue
}
2024-01-01 16:08:46 +08:00
2024-01-01 16:27:01 +08:00
for _ , blockID := range relVal . Relation . BlockIDs {
2023-12-29 11:34:08 +08:00
destVal := destAv . GetValue ( rollupKey . Rollup . KeyID , blockID )
if nil == destVal {
2024-04-03 09:37:42 +08:00
if destAv . ExistBlock ( blockID ) { // 数据库中存在行但是列值不存在是数据未初始化,这里补一个默认值
destVal = GetAttributeViewDefaultValue ( ast . NewNodeID ( ) , rollupKey . Rollup . KeyID , blockID , destKey . Type )
}
if nil == destVal {
continue
}
2023-12-29 11:34:08 +08:00
}
2024-01-01 16:08:46 +08:00
if av . KeyTypeNumber == destKey . Type {
destVal . Number . Format = destKey . NumberFormat
2023-12-30 20:49:57 +08:00
destVal . Number . FormatNumber ( )
}
2023-12-30 23:35:43 +08:00
2024-01-01 15:14:52 +08:00
cell . Value . Rollup . Contents = append ( cell . Value . Rollup . Contents , destVal . Clone ( ) )
2023-12-24 22:27:38 +08:00
}
2023-12-30 23:35:43 +08:00
2024-01-01 16:27:01 +08:00
cell . Value . Rollup . RenderContents ( rollupKey . Rollup . Calc , destKey )
2024-02-26 09:18:44 +08:00
// 将汇总列的值保存到 rows 中,后续渲染模板列的时候会用到,下同
// Database table view template columns support reading relation, rollup, created and updated columns https://github.com/siyuan-note/siyuan/issues/10442
keyValues := rows [ row . ID ]
keyValues = append ( keyValues , & av . KeyValues { Key : rollupKey , Values : [ ] * av . Value { { ID : cell . Value . ID , KeyID : rollupKey . ID , BlockID : row . ID , Type : av . KeyTypeRollup , Rollup : cell . Value . Rollup } } } )
rows [ row . ID ] = keyValues
2023-12-23 21:47:01 +08:00
case av . KeyTypeRelation : // 渲染关联列
relKey , _ := attrView . GetKey ( cell . Value . KeyID )
if nil != relKey && nil != relKey . Relation {
destAv , _ := av . ParseAttributeView ( relKey . Relation . AvID )
if nil != destAv {
2024-03-07 16:11:58 +08:00
blocks := map [ string ] * av . Value { }
2023-12-23 21:47:01 +08:00
for _ , blockValue := range destAv . GetBlockKeyValues ( ) . Values {
2024-03-07 16:11:58 +08:00
blocks [ blockValue . BlockID ] = blockValue
2023-12-23 21:47:01 +08:00
}
for _ , blockID := range cell . Value . Relation . BlockIDs {
cell . Value . Relation . Contents = append ( cell . Value . Relation . Contents , blocks [ blockID ] )
}
}
}
2023-10-13 10:44:29 +08:00
case av . KeyTypeCreated : // 渲染创建时间
createdStr := row . ID [ : len ( "20060102150405" ) ]
created , parseErr := time . ParseInLocation ( "20060102150405" , createdStr , time . Local )
if nil == parseErr {
cell . Value . Created = av . NewFormattedValueCreated ( created . UnixMilli ( ) , 0 , av . CreatedFormatNone )
cell . Value . Created . IsNotEmpty = true
} else {
cell . Value . Created = av . NewFormattedValueCreated ( time . Now ( ) . UnixMilli ( ) , 0 , av . CreatedFormatNone )
}
2024-02-26 09:18:44 +08:00
keyValues := rows [ row . ID ]
createdKey , _ := attrView . GetKey ( cell . Value . KeyID )
keyValues = append ( keyValues , & av . KeyValues { Key : createdKey , Values : [ ] * av . Value { { ID : cell . Value . ID , KeyID : createdKey . ID , BlockID : row . ID , Type : av . KeyTypeCreated , Created : cell . Value . Created } } } )
rows [ row . ID ] = keyValues
2023-10-13 10:44:29 +08:00
case av . KeyTypeUpdated : // 渲染更新时间
ial := map [ string ] string { }
block := row . GetBlockValue ( )
2023-12-18 22:29:17 +08:00
if nil != block && ! block . IsDetached {
2023-10-13 10:44:29 +08:00
ial = cache . GetBlockIAL ( row . ID )
if nil == ial {
ial = map [ string ] string { }
}
}
updatedStr := ial [ "updated" ]
2023-12-18 22:29:17 +08:00
if "" == updatedStr && nil != block {
2023-10-13 10:44:29 +08:00
cell . Value . Updated = av . NewFormattedValueUpdated ( block . Block . Updated , 0 , av . UpdatedFormatNone )
cell . Value . Updated . IsNotEmpty = true
} else {
updated , parseErr := time . ParseInLocation ( "20060102150405" , updatedStr , time . Local )
if nil == parseErr {
cell . Value . Updated = av . NewFormattedValueUpdated ( updated . UnixMilli ( ) , 0 , av . UpdatedFormatNone )
cell . Value . Updated . IsNotEmpty = true
} else {
cell . Value . Updated = av . NewFormattedValueUpdated ( time . Now ( ) . UnixMilli ( ) , 0 , av . UpdatedFormatNone )
}
}
2024-02-26 09:18:44 +08:00
keyValues := rows [ row . ID ]
updatedKey , _ := attrView . GetKey ( cell . Value . KeyID )
keyValues = append ( keyValues , & av . KeyValues { Key : updatedKey , Values : [ ] * av . Value { { ID : cell . Value . ID , KeyID : updatedKey . ID , BlockID : row . ID , Type : av . KeyTypeUpdated , Updated : cell . Value . Updated } } } )
rows [ row . ID ] = keyValues
}
}
}
// 最后单独渲染模板列,这样模板列就可以使用汇总、关联、创建时间和更新时间列的值了
// Database table view template columns support reading relation, rollup, created and updated columns https://github.com/siyuan-note/siyuan/issues/10442
for _ , row := range ret . Rows {
for _ , cell := range row . Cells {
switch cell . ValueType {
case av . KeyTypeTemplate : // 渲染模板列
keyValues := rows [ row . ID ]
ial := map [ string ] string { }
block := row . GetBlockValue ( )
if nil != block && ! block . IsDetached {
ial = cache . GetBlockIAL ( row . ID )
if nil == ial {
ial = map [ string ] string { }
}
}
2024-03-09 10:54:37 +08:00
content := renderTemplateCol ( ial , keyValues , cell . Value . Template . Content )
2024-02-26 09:18:44 +08:00
cell . Value . Template . Content = content
2023-10-13 10:44:29 +08:00
}
}
}
return
}
2023-10-27 22:28:56 +08:00
func FillAttributeViewTableCellNilValue ( tableCell * av . TableCell , rowID , colID string ) {
if nil == tableCell . Value {
2024-01-01 14:32:32 +08:00
tableCell . Value = GetAttributeViewDefaultValue ( tableCell . ID , colID , rowID , tableCell . ValueType )
return
2023-10-27 22:28:56 +08:00
}
2024-01-01 14:32:32 +08:00
2023-10-27 22:28:56 +08:00
tableCell . Value . Type = tableCell . ValueType
switch tableCell . ValueType {
case av . KeyTypeText :
if nil == tableCell . Value . Text {
tableCell . Value . Text = & av . ValueText { }
}
case av . KeyTypeNumber :
if nil == tableCell . Value . Number {
tableCell . Value . Number = & av . ValueNumber { }
}
case av . KeyTypeDate :
if nil == tableCell . Value . Date {
tableCell . Value . Date = & av . ValueDate { }
}
case av . KeyTypeSelect :
if 1 > len ( tableCell . Value . MSelect ) {
tableCell . Value . MSelect = [ ] * av . ValueSelect { }
}
case av . KeyTypeMSelect :
if 1 > len ( tableCell . Value . MSelect ) {
tableCell . Value . MSelect = [ ] * av . ValueSelect { }
}
case av . KeyTypeURL :
if nil == tableCell . Value . URL {
tableCell . Value . URL = & av . ValueURL { }
}
case av . KeyTypeEmail :
if nil == tableCell . Value . Email {
tableCell . Value . Email = & av . ValueEmail { }
}
case av . KeyTypePhone :
if nil == tableCell . Value . Phone {
tableCell . Value . Phone = & av . ValuePhone { }
}
case av . KeyTypeMAsset :
if 1 > len ( tableCell . Value . MAsset ) {
tableCell . Value . MAsset = [ ] * av . ValueAsset { }
}
case av . KeyTypeTemplate :
if nil == tableCell . Value . Template {
tableCell . Value . Template = & av . ValueTemplate { }
}
case av . KeyTypeCreated :
if nil == tableCell . Value . Created {
tableCell . Value . Created = & av . ValueCreated { }
}
case av . KeyTypeUpdated :
if nil == tableCell . Value . Updated {
tableCell . Value . Updated = & av . ValueUpdated { }
}
2023-11-17 09:03:17 +08:00
case av . KeyTypeCheckbox :
if nil == tableCell . Value . Checkbox {
tableCell . Value . Checkbox = & av . ValueCheckbox { }
}
2023-12-15 20:05:14 +08:00
case av . KeyTypeRelation :
if nil == tableCell . Value . Relation {
tableCell . Value . Relation = & av . ValueRelation { }
}
case av . KeyTypeRollup :
if nil == tableCell . Value . Rollup {
tableCell . Value . Rollup = & av . ValueRollup { }
}
2023-10-27 22:28:56 +08:00
}
}
2024-01-01 14:32:32 +08:00
func GetAttributeViewDefaultValue ( valueID , keyID , blockID string , typ av . KeyType ) ( ret * av . Value ) {
2024-03-03 16:58:37 +08:00
if "" == valueID {
valueID = ast . NewNodeID ( )
}
2024-01-01 14:32:32 +08:00
ret = & av . Value { ID : valueID , KeyID : keyID , BlockID : blockID , Type : typ }
2024-03-03 16:58:37 +08:00
createdStr := valueID [ : len ( "20060102150405" ) ]
created , parseErr := time . ParseInLocation ( "20060102150405" , createdStr , time . Local )
if nil == parseErr {
ret . CreatedAt = created . UnixMilli ( )
} else {
ret . CreatedAt = time . Now ( ) . UnixMilli ( )
}
if 0 == ret . UpdatedAt {
2024-04-09 23:37:00 +08:00
ret . UpdatedAt = ret . CreatedAt
2024-03-03 16:58:37 +08:00
}
2024-01-01 14:32:32 +08:00
switch typ {
case av . KeyTypeText :
ret . Text = & av . ValueText { }
case av . KeyTypeNumber :
ret . Number = & av . ValueNumber { }
case av . KeyTypeDate :
ret . Date = & av . ValueDate { }
case av . KeyTypeSelect :
ret . MSelect = [ ] * av . ValueSelect { }
case av . KeyTypeMSelect :
ret . MSelect = [ ] * av . ValueSelect { }
case av . KeyTypeURL :
ret . URL = & av . ValueURL { }
case av . KeyTypeEmail :
ret . Email = & av . ValueEmail { }
case av . KeyTypePhone :
ret . Phone = & av . ValuePhone { }
case av . KeyTypeMAsset :
ret . MAsset = [ ] * av . ValueAsset { }
case av . KeyTypeTemplate :
ret . Template = & av . ValueTemplate { }
case av . KeyTypeCreated :
ret . Created = & av . ValueCreated { }
case av . KeyTypeUpdated :
ret . Updated = & av . ValueUpdated { }
case av . KeyTypeCheckbox :
ret . Checkbox = & av . ValueCheckbox { }
case av . KeyTypeRelation :
ret . Relation = & av . ValueRelation { }
case av . KeyTypeRollup :
ret . Rollup = & av . ValueRollup { }
}
return
}
2024-03-09 10:54:37 +08:00
func renderTemplateCol ( ial map [ string ] string , rowValues [ ] * av . KeyValues , tplContent string ) string {
2023-10-13 10:44:29 +08:00
if "" == ial [ "id" ] {
block := getRowBlockValue ( rowValues )
ial [ "id" ] = block . Block . ID
}
if "" == ial [ "updated" ] {
block := getRowBlockValue ( rowValues )
ial [ "updated" ] = time . UnixMilli ( block . Block . Updated ) . Format ( "20060102150405" )
}
goTpl := template . New ( "" ) . Delims ( ".action{" , "}" )
2024-01-04 22:31:42 +08:00
tplFuncMap := util . BuiltInTemplateFuncs ( )
// 这里存在依赖问题所以不支持 SQLTemplateFuncs(&tplFuncMap)
goTpl = goTpl . Funcs ( tplFuncMap )
tpl , tplErr := goTpl . Funcs ( tplFuncMap ) . Parse ( tplContent )
2023-10-13 10:44:29 +08:00
if nil != tplErr {
logging . LogWarnf ( "parse template [%s] failed: %s" , tplContent , tplErr )
return ""
}
buf := & bytes . Buffer { }
dataModel := map [ string ] interface { } { } // 复制一份 IAL 以避免修改原始数据
for k , v := range ial {
dataModel [ k ] = v
// Database template column supports `created` and `updated` built-in variables https://github.com/siyuan-note/siyuan/issues/9364
createdStr := ial [ "id" ]
if "" != createdStr {
createdStr = createdStr [ : len ( "20060102150405" ) ]
}
created , parseErr := time . ParseInLocation ( "20060102150405" , createdStr , time . Local )
if nil == parseErr {
dataModel [ "created" ] = created
} else {
logging . LogWarnf ( "parse created [%s] failed: %s" , createdStr , parseErr )
dataModel [ "created" ] = time . Now ( )
}
updatedStr := ial [ "updated" ]
updated , parseErr := time . ParseInLocation ( "20060102150405" , updatedStr , time . Local )
if nil == parseErr {
dataModel [ "updated" ] = updated
} else {
dataModel [ "updated" ] = time . Now ( )
}
}
2024-03-09 10:54:37 +08:00
2023-10-13 10:44:29 +08:00
for _ , rowValue := range rowValues {
if 0 < len ( rowValue . Values ) {
v := rowValue . Values [ 0 ]
if av . KeyTypeNumber == v . Type {
2024-04-02 20:24:10 +08:00
if nil != v . Number && v . Number . IsNotEmpty {
dataModel [ rowValue . Key . Name ] = v . Number . Content
}
2023-12-15 09:53:34 +08:00
} else if av . KeyTypeDate == v . Type {
2024-04-02 20:24:10 +08:00
if nil != v . Date && v . Date . IsNotEmpty {
dataModel [ rowValue . Key . Name ] = time . UnixMilli ( v . Date . Content )
}
2024-03-31 23:32:00 +08:00
} else if av . KeyTypeRollup == v . Type {
if 0 < len ( v . Rollup . Contents ) && av . KeyTypeNumber == v . Rollup . Contents [ 0 ] . Type {
// 汇总数字时仅取第一个数字填充模板
dataModel [ rowValue . Key . Name ] = v . Rollup . Contents [ 0 ] . Number . Content
}
2023-10-13 10:44:29 +08:00
} else {
dataModel [ rowValue . Key . Name ] = v . String ( )
}
}
}
2024-03-09 10:54:37 +08:00
2023-10-13 10:44:29 +08:00
if err := tpl . Execute ( buf , dataModel ) ; nil != err {
logging . LogWarnf ( "execute template [%s] failed: %s" , tplContent , err )
}
return buf . String ( )
}
func getRowBlockValue ( keyValues [ ] * av . KeyValues ) ( ret * av . Value ) {
for _ , kv := range keyValues {
if av . KeyTypeBlock == kv . Key . Type && 0 < len ( kv . Values ) {
ret = kv . Values [ 0 ]
break
}
}
return
}