From 2e40a0a480b17ba45825333db1c367764c2a6d85 Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Thu, 12 Jun 2025 17:14:54 +0800 Subject: [PATCH 1/3] :art: Database gallery view https://github.com/siyuan-note/siyuan/issues/10414 --- kernel/av/layout.go | 23 ++++++++++++++- kernel/av/layout_gallery.go | 27 +++++++++++++++++ kernel/av/layout_table.go | 59 ++++++++++++++++++++++++++++--------- kernel/sql/av.go | 55 ++++++++++++++++++++++++++++++++++ kernel/sql/av_gallery.go | 53 ++------------------------------- kernel/sql/av_table.go | 54 ++------------------------------- 6 files changed, 153 insertions(+), 118 deletions(-) diff --git a/kernel/av/layout.go b/kernel/av/layout.go index ccdcafa4c..0cb68a953 100644 --- a/kernel/av/layout.go +++ b/kernel/av/layout.go @@ -63,13 +63,34 @@ type BaseInstanceField struct { Date *Date `json:"date,omitempty"` // 日期设置 } +// CollectionLayout 描述了集合布局的接口。 +type CollectionLayout interface { + + // GetItemIDs 返回集合中所有项目的 ID。 + GetItemIDs() []string +} + +// Collection 描述了一个集合的接口。 +// 集合可以是表格、画廊等,包含多个项目。 +type Collection interface { + + // GetItems 返回集合中的所有项目。 + GetItems() (ret []Item) + + // SetItems 设置集合中的项目。 + SetItems(items []Item) +} + // Item 描述了一个项目的接口。 -// 项目可以表格行、画廊卡片或其他视图类型的实体。 +// 项目可以是表格行、画廊卡片等。 type Item interface { // GetBlockValue 返回主键的值。 GetBlockValue() *Value + // GetValues 返回项目的所有字段值。 + GetValues() []*Value + // GetID 返回项目的 ID。 GetID() string } diff --git a/kernel/av/layout_gallery.go b/kernel/av/layout_gallery.go index 5fe668dac..4c668e408 100644 --- a/kernel/av/layout_gallery.go +++ b/kernel/av/layout_gallery.go @@ -37,6 +37,10 @@ type LayoutGallery struct { CardIDs []string `json:"cardIds"` // 卡片 ID,用于自定义排序 } +func (layoutGallery *LayoutGallery) GetItemIDs() (ret []string) { + return layoutGallery.CardIDs +} + func NewLayoutGallery() *LayoutGallery { return &LayoutGallery{ BaseLayout: &BaseLayout{ @@ -126,6 +130,29 @@ func (card *GalleryCard) GetBlockValue() (ret *Value) { return } +func (card *GalleryCard) GetValues() (ret []*Value) { + ret = []*Value{} + for _, v := range card.Values { + ret = append(ret, v.Value) + } + return +} + +func (gallery *Gallery) GetItems() (ret []Item) { + ret = []Item{} + for _, card := range gallery.Cards { + ret = append(ret, card) + } + return +} + +func (gallery *Gallery) SetItems(items []Item) { + gallery.Cards = []*GalleryCard{} + for _, item := range items { + gallery.Cards = append(gallery.Cards, item.(*GalleryCard)) + } +} + func (gallery *Gallery) GetType() LayoutType { return LayoutTypeGallery } diff --git a/kernel/av/layout_table.go b/kernel/av/layout_table.go index 29ebed480..d040596a8 100644 --- a/kernel/av/layout_table.go +++ b/kernel/av/layout_table.go @@ -30,6 +30,10 @@ type LayoutTable struct { RowIDs []string `json:"rowIds"` // 行 ID,用于自定义排序 } +func (layoutTable *LayoutTable) GetItemIDs() (ret []string) { + return layoutTable.RowIDs +} + func NewLayoutTable() *LayoutTable { return &LayoutTable{ BaseLayout: &BaseLayout{ @@ -87,20 +91,6 @@ type TableCell struct { BgColor string `json:"bgColor"` // 单元格背景颜色 } -func (row *TableRow) GetID() string { - return row.ID -} - -func (row *TableRow) GetBlockValue() (ret *Value) { - for _, cell := range row.Cells { - if KeyTypeBlock == cell.ValueType { - ret = cell.Value - break - } - } - return -} - func (row *TableRow) GetValue(keyID string) (ret *Value) { for _, cell := range row.Cells { if nil != cell.Value && keyID == cell.Value.KeyID { @@ -120,6 +110,47 @@ func (table *Table) GetColumn(id string) *TableColumn { return nil } +func (row *TableRow) GetID() string { + return row.ID +} + +func (row *TableRow) GetBlockValue() (ret *Value) { + for _, cell := range row.Cells { + if KeyTypeBlock == cell.ValueType { + ret = cell.Value + break + } + } + return +} + +func (row *TableRow) GetValues() (ret []*Value) { + ret = []*Value{} + for _, cell := range row.Cells { + if nil != cell.Value { + ret = append(ret, cell.Value) + } + } + return +} + +func (table *Table) GetItems() (ret []Item) { + ret = []Item{} + for _, row := range table.Rows { + if nil != row { + ret = append(ret, row) + } + } + return +} + +func (table *Table) SetItems(items []Item) { + table.Rows = []*TableRow{} + for _, item := range items { + table.Rows = append(table.Rows, item.(*TableRow)) + } +} + func (*Table) GetType() LayoutType { return LayoutTypeTable } diff --git a/kernel/sql/av.go b/kernel/sql/av.go index 4815dabb9..6d66488d7 100644 --- a/kernel/sql/av.go +++ b/kernel/sql/av.go @@ -19,6 +19,7 @@ package sql import ( "bytes" "fmt" + "sort" "strings" "text/template" "time" @@ -559,3 +560,57 @@ func removeMissingField(attrView *av.AttributeView, view *av.View, missingKeyID av.SaveAttributeView(attrView) } } + +// filterByQuery 根据搜索条件过滤 +func filterByQuery(query string, collection av.Collection) { + query = strings.TrimSpace(query) + if "" != query { + query = strings.Join(strings.Fields(query), " ") // 将连续空格转换为一个空格 + keywords := strings.Split(query, " ") // 按空格分割关键字 + + // 使用 AND 逻辑 https://github.com/siyuan-note/siyuan/issues/11535 + var hitItems []av.Item + for _, item := range collection.GetItems() { + hit := false + for _, cell := range item.GetValues() { + allKeywordsHit := true + for _, keyword := range keywords { + if !strings.Contains(strings.ToLower(cell.String(true)), strings.ToLower(keyword)) { + allKeywordsHit = false + break + } + } + if allKeywordsHit { + hit = true + break + } + } + if hit { + hitItems = append(hitItems, item) + } + } + collection.SetItems(hitItems) + if 1 > len(collection.GetItems()) { + collection.SetItems([]av.Item{}) + } + } +} + +// manualSort 处理用户手动排序。 +func manualSort(collectionLayout av.CollectionLayout, collection av.Collection) { + sortRowIDs := map[string]int{} + for i, itemID := range collectionLayout.GetItemIDs() { + sortRowIDs[itemID] = i + } + + items := collection.GetItems() + sort.Slice(items, func(i, j int) bool { + iv := sortRowIDs[items[i].GetID()] + jv := sortRowIDs[items[j].GetID()] + if iv == jv { + return items[i].GetID() < items[j].GetID() + } + return iv < jv + }) + collection.SetItems(items) +} diff --git a/kernel/sql/av_gallery.go b/kernel/sql/av_gallery.go index c13d59f87..42a6c8839 100644 --- a/kernel/sql/av_gallery.go +++ b/kernel/sql/av_gallery.go @@ -3,7 +3,6 @@ package sql import ( "bytes" "fmt" - "sort" "strings" "github.com/88250/lute" @@ -134,56 +133,8 @@ func RenderAttributeViewGallery(attrView *av.AttributeView, view *av.View, query util.PushErrMsg(fmt.Sprintf(util.Langs[util.Lang][44], util.EscapeHTML(renderTemplateErr.Error())), 30000) } - // 根据搜索条件过滤 - query = strings.TrimSpace(query) - if "" != query { - // 将连续空格转换为一个空格 - query = strings.Join(strings.Fields(query), " ") - // 按空格分割关键字 - keywords := strings.Split(query, " ") - // 使用 AND 逻辑 https://github.com/siyuan-note/siyuan/issues/11535 - var hitCards []*av.GalleryCard - for _, card := range ret.Cards { - hit := false - for _, value := range card.Values { - allKeywordsHit := true - for _, keyword := range keywords { - if !strings.Contains(strings.ToLower(value.Value.String(true)), strings.ToLower(keyword)) { - allKeywordsHit = false - break - } - } - if allKeywordsHit { - hit = true - break - } - } - if hit { - hitCards = append(hitCards, card) - } - } - ret.Cards = hitCards - if 1 > len(ret.Cards) { - ret.Cards = []*av.GalleryCard{} - } - } - - // 自定义排序 - sortCardIDs := map[string]int{} - if 0 < len(view.Gallery.CardIDs) { - for i, cardID := range view.Gallery.CardIDs { - sortCardIDs[cardID] = i - } - } - - sort.Slice(ret.Cards, func(i, j int) bool { - iv := sortCardIDs[ret.Cards[i].ID] - jv := sortCardIDs[ret.Cards[j].ID] - if iv == jv { - return ret.Cards[i].ID < ret.Cards[j].ID - } - return iv < jv - }) + filterByQuery(query, ret) + manualSort(view.Gallery, ret) return } diff --git a/kernel/sql/av_table.go b/kernel/sql/av_table.go index 8f85baa4c..14ffedc52 100644 --- a/kernel/sql/av_table.go +++ b/kernel/sql/av_table.go @@ -16,8 +16,6 @@ package sql import ( "fmt" - "sort" - "strings" "github.com/88250/lute/ast" "github.com/siyuan-note/siyuan/kernel/av" @@ -140,55 +138,7 @@ func RenderAttributeViewTable(attrView *av.AttributeView, view *av.View, query s util.PushErrMsg(fmt.Sprintf(util.Langs[util.Lang][44], util.EscapeHTML(renderTemplateErr.Error())), 30000) } - // 根据搜索条件过滤 - query = strings.TrimSpace(query) - if "" != query { - // 将连续空格转换为一个空格 - query = strings.Join(strings.Fields(query), " ") - // 按空格分割关键字 - keywords := strings.Split(query, " ") - // 使用 AND 逻辑 https://github.com/siyuan-note/siyuan/issues/11535 - var hitRows []*av.TableRow - for _, row := range ret.Rows { - hit := false - for _, cell := range row.Cells { - allKeywordsHit := true - for _, keyword := range keywords { - if !strings.Contains(strings.ToLower(cell.Value.String(true)), strings.ToLower(keyword)) { - allKeywordsHit = false - break - } - } - if allKeywordsHit { - hit = true - break - } - } - if hit { - hitRows = append(hitRows, row) - } - } - ret.Rows = hitRows - if 1 > len(ret.Rows) { - ret.Rows = []*av.TableRow{} - } - } - - // 自定义排序 - sortRowIDs := map[string]int{} - if 0 < len(view.Table.RowIDs) { - for i, rowID := range view.Table.RowIDs { - sortRowIDs[rowID] = i - } - } - - sort.Slice(ret.Rows, func(i, j int) bool { - iv := sortRowIDs[ret.Rows[i].ID] - jv := sortRowIDs[ret.Rows[j].ID] - if iv == jv { - return ret.Rows[i].ID < ret.Rows[j].ID - } - return iv < jv - }) + filterByQuery(query, ret) + manualSort(view.Table, ret) return } From 19082e9d5f1ee48acde0a21a062ae5f20545f8b2 Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Thu, 12 Jun 2025 18:08:44 +0800 Subject: [PATCH 2/3] :art: Improve export of empty documents with subdocuments https://github.com/siyuan-note/siyuan/issues/15009 --- kernel/model/export.go | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/kernel/model/export.go b/kernel/model/export.go index 81466c135..33e667589 100644 --- a/kernel/model/export.go +++ b/kernel/model/export.go @@ -1993,13 +1993,13 @@ func ExportMarkdownContent(id string, refMode, embedMode int, addYfm bool) (hPat return } -func exportMarkdownContent(id, ext string, exportRefMode int, defBlockIDs []string, singleFile bool, treeCache *map[string]*parse.Tree) (hPath, exportedMd string) { +func exportMarkdownContent(id, ext string, exportRefMode int, defBlockIDs []string, singleFile bool, treeCache *map[string]*parse.Tree) (tree *parse.Tree, exportedMd string, isEmpty bool) { tree, err := loadTreeWithCache(id, treeCache) if err != nil { logging.LogErrorf("load tree by block id [%s] failed: %s", id, err) return } - hPath = tree.HPath + isEmpty = nil == tree.Root.FirstChild.FirstChild exportedMd = exportMarkdownContent0(tree, "", false, ext, exportRefMode, Conf.Export.BlockEmbedMode, Conf.Export.FileAnnotationRefMode, Conf.Export.TagOpenMarker, Conf.Export.TagCloseMarker, @@ -3128,7 +3128,11 @@ func exportPandocConvertZip(baseFolderName string, docPaths, defBlockIDs []strin luteEngine := util.NewLute() for i, p := range docPaths { id := util.GetTreeID(p) - hPath, md := exportMarkdownContent(id, ext, exportRefMode, defBlockIDs, false, treeCache) + tree, md, isEmpty := exportMarkdownContent(id, ext, exportRefMode, defBlockIDs, false, treeCache) + if nil == tree { + continue + } + hPath := tree.HPath dir, name = path.Split(hPath) dir = util.FilterFilePath(dir) // 导出文档时未移除不支持的文件名符号 https://github.com/siyuan-note/siyuan/issues/4590 name = util.FilterFileName(name) @@ -3147,8 +3151,17 @@ func exportPandocConvertZip(baseFolderName string, docPaths, defBlockIDs []strin continue } + if isEmpty { + entries, readErr := os.ReadDir(filepath.Join(util.DataDir, tree.Box, strings.TrimSuffix(tree.Path, ".sy"))) + if nil == readErr && 0 < len(entries) { + // 如果文档内容为空并且存在子文档则仅导出文件夹 + // Improve export of empty documents with subdocuments https://github.com/siyuan-note/siyuan/issues/15009 + continue + } + } + // 解析导出后的标准 Markdown,汇总 assets - tree := parse.Parse("", gulu.Str.ToBytes(md), luteEngine.ParseOptions) + tree = parse.Parse("", gulu.Str.ToBytes(md), luteEngine.ParseOptions) var assets []string assets = append(assets, assetsLinkDestsInTree(tree)...) for _, asset := range assets { From 7899c729106919eb454c27d475713a5a901504e1 Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Thu, 12 Jun 2025 18:19:08 +0800 Subject: [PATCH 3/3] :art: Database gallery view https://github.com/siyuan-note/siyuan/issues/10414 --- kernel/model/attribute_view.go | 1 + 1 file changed, 1 insertion(+) diff --git a/kernel/model/attribute_view.go b/kernel/model/attribute_view.go index 3f41eb151..79c945369 100644 --- a/kernel/model/attribute_view.go +++ b/kernel/model/attribute_view.go @@ -3074,6 +3074,7 @@ func SortAttributeViewViewKey(avID, blockID, keyID, previousKeyID string) (err e break } } + view.Gallery.CardFields = util.InsertElem(view.Gallery.CardFields, previousIndex, field) } err = av.SaveAttributeView(attrView)