From 667aa38d42bd80a5774d7aceeba02b51bfdacbde Mon Sep 17 00:00:00 2001
From: Daniel <845765@qq.com>
Date: Wed, 13 Mar 2024 21:49:53 +0800
Subject: [PATCH 1/6] :art: Use the path ending with `/` when loading the
widget https://github.com/siyuan-note/siyuan/issues/10520
---
app/src/protyle/hint/extend.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/app/src/protyle/hint/extend.ts b/app/src/protyle/hint/extend.ts
index 06ea1dd2e..2a137d804 100644
--- a/app/src/protyle/hint/extend.ts
+++ b/app/src/protyle/hint/extend.ts
@@ -447,7 +447,9 @@ export const hintRenderTemplate = (value: string, protyle: IProtyle, nodeElement
export const hintRenderWidget = (value: string, protyle: IProtyle) => {
focusByRange(protyle.toolbar.range);
- insertHTML(protyle.lute.SpinBlockDOM(``), protyle, true);
+ // src 地址以 / 结尾
+ // Use the path ending with `/` when loading the widget https://github.com/siyuan-note/siyuan/issues/10520
+ insertHTML(protyle.lute.SpinBlockDOM(``), protyle, true);
hideElements(["util"], protyle);
};
From cc9aed6aecb3228777f9a99235b741be8faddd54 Mon Sep 17 00:00:00 2001
From: Daniel <845765@qq.com>
Date: Wed, 13 Mar 2024 21:59:59 +0800
Subject: [PATCH 2/6] :bug: Fix sort av view not working
---
kernel/model/attribute_view.go | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/kernel/model/attribute_view.go b/kernel/model/attribute_view.go
index 820030f99..7d2364b00 100644
--- a/kernel/model/attribute_view.go
+++ b/kernel/model/attribute_view.go
@@ -1376,15 +1376,14 @@ func (tx *Transaction) doSortAttrViewView(operation *Operation) (ret *TxErr) {
return &TxErr{code: TxErrWriteAttributeView, id: operation.AvID, msg: err.Error()}
}
- view, err := getAttrViewViewByBlockID(attrView, operation.BlockID)
+ view := attrView.GetView(operation.ID)
if nil == view {
logging.LogErrorf("get view failed: %s", operation.BlockID)
return &TxErr{code: TxErrWriteAttributeView, id: operation.AvID, msg: err.Error()}
}
viewID := view.ID
- previewViewID := operation.PreviousID
-
- if viewID == previewViewID {
+ previousViewID := operation.PreviousID
+ if viewID == previousViewID {
return
}
@@ -1402,7 +1401,7 @@ func (tx *Transaction) doSortAttrViewView(operation *Operation) (ret *TxErr) {
attrView.Views = append(attrView.Views[:index], attrView.Views[index+1:]...)
for i, v := range attrView.Views {
- if v.ID == previewViewID {
+ if v.ID == previousViewID {
previousIndex = i + 1
break
}
From e02e4daa1cbb2bbf03654bde183273a4c75f3828 Mon Sep 17 00:00:00 2001
From: Daniel <845765@qq.com>
Date: Wed, 13 Mar 2024 23:23:52 +0800
Subject: [PATCH 3/6] :bug: Database cannot filter out rows with relations that
are empty or not empty Fix https://github.com/siyuan-note/siyuan/issues/10601
---
kernel/av/filter.go | 554 +++++++++++++++++++++++++++++++
kernel/av/sort.go | 243 ++++++++++++++
kernel/av/table.go | 773 --------------------------------------------
3 files changed, 797 insertions(+), 773 deletions(-)
diff --git a/kernel/av/filter.go b/kernel/av/filter.go
index 190bce6b7..f830f4a8e 100644
--- a/kernel/av/filter.go
+++ b/kernel/av/filter.go
@@ -17,6 +17,9 @@
package av
import (
+ "strings"
+ "time"
+
"github.com/siyuan-note/siyuan/kernel/util"
)
@@ -75,6 +78,557 @@ const (
FilterOperatorIsFalse FilterOperator = "Is false"
)
+func (value *Value) Filter(filter *ViewFilter, attrView *AttributeView, rowID string) bool {
+ if nil == filter || (nil == filter.Value && nil == filter.RelativeDate) {
+ return true
+ }
+
+ if nil != filter.Value && value.Type != filter.Value.Type {
+ // 由于字段类型被用户编辑过导致和过滤器值类型不匹配,该情况下不过滤
+ return true
+ }
+
+ if nil != value.Rollup && KeyTypeRollup == value.Type && nil != filter && nil != filter.Value && KeyTypeRollup == filter.Value.Type &&
+ nil != filter.Value.Rollup && 0 < len(filter.Value.Rollup.Contents) {
+ // 单独处理汇总类型的比较
+
+ // 处理为空和不为空
+ switch filter.Operator {
+ case FilterOperatorIsEmpty:
+ return 0 == len(value.Rollup.Contents)
+ case FilterOperatorIsNotEmpty:
+ return 0 != len(value.Rollup.Contents)
+ }
+
+ // 处理值比较
+ key, _ := attrView.GetKey(value.KeyID)
+ if nil == key {
+ return false
+ }
+
+ relKey, _ := attrView.GetKey(key.Rollup.RelationKeyID)
+ if nil == relKey {
+ return false
+ }
+
+ relVal := attrView.GetValue(relKey.ID, rowID)
+ if nil == relVal || nil == relVal.Relation {
+ return false
+ }
+
+ destAv, _ := ParseAttributeView(relKey.Relation.AvID)
+ if nil == destAv {
+ return false
+ }
+
+ for _, blockID := range relVal.Relation.BlockIDs {
+ destVal := destAv.GetValue(key.Rollup.KeyID, blockID)
+ if nil == destVal {
+ continue
+ }
+
+ if destVal.filter(filter.Value.Rollup.Contents[0], filter.RelativeDate, filter.RelativeDate2, filter.Operator) {
+ return true
+ }
+ }
+ return false
+ }
+
+ if nil != value.Relation && KeyTypeRelation == value.Type && 0 < len(value.Relation.Contents) && nil != filter && nil != filter.Value && KeyTypeRelation == filter.Value.Type &&
+ nil != filter.Value.Relation && 0 < len(filter.Value.Relation.BlockIDs) {
+ // 单独处理关联类型的比较
+
+ for _, relationValue := range value.Relation.Contents {
+ filterValue := &Value{Type: KeyTypeBlock, Block: &ValueBlock{Content: filter.Value.Relation.BlockIDs[0]}}
+ if relationValue.filter(filterValue, filter.RelativeDate, filter.RelativeDate2, filter.Operator) {
+ return true
+ }
+ }
+ return false
+ }
+ return value.filter(filter.Value, filter.RelativeDate, filter.RelativeDate2, filter.Operator)
+}
+
+func (value *Value) filter(other *Value, relativeDate, relativeDate2 *RelativeDate, operator FilterOperator) bool {
+ switch value.Type {
+ case KeyTypeBlock:
+ if nil != value.Block && nil != other && nil != other.Block {
+ switch operator {
+ case FilterOperatorIsEqual:
+ return value.Block.Content == other.Block.Content
+ case FilterOperatorIsNotEqual:
+ return value.Block.Content != other.Block.Content
+ case FilterOperatorContains:
+ return strings.Contains(value.Block.Content, other.Block.Content)
+ case FilterOperatorDoesNotContain:
+ return !strings.Contains(value.Block.Content, other.Block.Content)
+ case FilterOperatorStartsWith:
+ return strings.HasPrefix(value.Block.Content, other.Block.Content)
+ case FilterOperatorEndsWith:
+ return strings.HasSuffix(value.Block.Content, other.Block.Content)
+ case FilterOperatorIsEmpty:
+ return "" == strings.TrimSpace(value.Block.Content)
+ case FilterOperatorIsNotEmpty:
+ return "" != strings.TrimSpace(value.Block.Content)
+ }
+ }
+ case KeyTypeText:
+ if nil != value.Text && nil != other && nil != other.Text {
+ switch operator {
+ case FilterOperatorIsEqual:
+ if "" == strings.TrimSpace(other.Text.Content) {
+ return true
+ }
+ return value.Text.Content == other.Text.Content
+ case FilterOperatorIsNotEqual:
+ if "" == strings.TrimSpace(other.Text.Content) {
+ return true
+ }
+ return value.Text.Content != other.Text.Content
+ case FilterOperatorContains:
+ if "" == strings.TrimSpace(other.Text.Content) {
+ return true
+ }
+ return strings.Contains(value.Text.Content, other.Text.Content)
+ case FilterOperatorDoesNotContain:
+ if "" == strings.TrimSpace(other.Text.Content) {
+ return true
+ }
+ return !strings.Contains(value.Text.Content, other.Text.Content)
+ case FilterOperatorStartsWith:
+ if "" == strings.TrimSpace(other.Text.Content) {
+ return true
+ }
+ return strings.HasPrefix(value.Text.Content, other.Text.Content)
+ case FilterOperatorEndsWith:
+ if "" == strings.TrimSpace(other.Text.Content) {
+ return true
+ }
+ return strings.HasSuffix(value.Text.Content, other.Text.Content)
+ case FilterOperatorIsEmpty:
+ return "" == strings.TrimSpace(value.Text.Content)
+ case FilterOperatorIsNotEmpty:
+ return "" != strings.TrimSpace(value.Text.Content)
+ }
+ }
+ case KeyTypeNumber:
+ if nil != value.Number && nil != other && nil != other.Number {
+ switch operator {
+ case FilterOperatorIsEqual:
+ if !other.Number.IsNotEmpty {
+ return true
+ }
+ return value.Number.Content == other.Number.Content
+ case FilterOperatorIsNotEqual:
+ if !other.Number.IsNotEmpty {
+ return true
+ }
+ return value.Number.Content != other.Number.Content
+ case FilterOperatorIsGreater:
+ return value.Number.Content > other.Number.Content
+ case FilterOperatorIsGreaterOrEqual:
+ return value.Number.Content >= other.Number.Content
+ case FilterOperatorIsLess:
+ return value.Number.Content < other.Number.Content
+ case FilterOperatorIsLessOrEqual:
+ return value.Number.Content <= other.Number.Content
+ case FilterOperatorIsEmpty:
+ return !value.Number.IsNotEmpty
+ case FilterOperatorIsNotEmpty:
+ return value.Number.IsNotEmpty
+ }
+ }
+ case KeyTypeDate:
+ if nil != value.Date {
+ if nil != relativeDate {
+ // 使用相对时间比较
+
+ count := relativeDate.Count
+ unit := relativeDate.Unit
+ direction := relativeDate.Direction
+ relativeTimeStart, relativeTimeEnd := calcRelativeTimeRegion(count, unit, direction)
+ _, relativeTimeEnd2 := calcRelativeTimeRegion(relativeDate2.Count, relativeDate2.Unit, relativeDate2.Direction)
+ return filterTime(value.Date.Content, value.Date.IsNotEmpty, relativeTimeStart, relativeTimeEnd, relativeTimeEnd2, operator)
+ } else { // 使用具体时间比较
+ if nil == other.Date {
+ return true
+ }
+
+ otherTime := time.UnixMilli(other.Date.Content)
+ otherStart := time.Date(otherTime.Year(), otherTime.Month(), otherTime.Day(), 0, 0, 0, 0, otherTime.Location())
+ otherEnd := time.Date(otherTime.Year(), otherTime.Month(), otherTime.Day(), 23, 59, 59, 999999999, otherTime.Location())
+ return filterTime(value.Date.Content, value.Date.IsNotEmpty, otherStart, otherEnd, time.Now(), operator)
+ }
+ }
+ case KeyTypeCreated:
+ if nil != value.Created {
+ if nil != relativeDate {
+ // 使用相对时间比较
+
+ count := relativeDate.Count
+ unit := relativeDate.Unit
+ direction := relativeDate.Direction
+ relativeTimeStart, relativeTimeEnd := calcRelativeTimeRegion(count, unit, direction)
+ return filterTime(value.Created.Content, true, relativeTimeStart, relativeTimeEnd, time.Now(), operator)
+ } else { // 使用具体时间比较
+ if nil == other.Created {
+ return true
+ }
+
+ otherTime := time.UnixMilli(other.Created.Content)
+ otherStart := time.Date(otherTime.Year(), otherTime.Month(), otherTime.Day(), 0, 0, 0, 0, otherTime.Location())
+ otherEnd := time.Date(otherTime.Year(), otherTime.Month(), otherTime.Day(), 23, 59, 59, 999999999, otherTime.Location())
+ return filterTime(value.Created.Content, value.Created.IsNotEmpty, otherStart, otherEnd, time.Now(), operator)
+ }
+ }
+ case KeyTypeUpdated:
+ if nil != value.Updated {
+ if nil != relativeDate {
+ // 使用相对时间比较
+
+ count := relativeDate.Count
+ unit := relativeDate.Unit
+ direction := relativeDate.Direction
+ relativeTimeStart, relativeTimeEnd := calcRelativeTimeRegion(count, unit, direction)
+ return filterTime(value.Updated.Content, true, relativeTimeStart, relativeTimeEnd, time.Now(), operator)
+ } else { // 使用具体时间比较
+ if nil == other.Updated {
+ return true
+ }
+
+ otherTime := time.UnixMilli(other.Updated.Content)
+ otherStart := time.Date(otherTime.Year(), otherTime.Month(), otherTime.Day(), 0, 0, 0, 0, otherTime.Location())
+ otherEnd := time.Date(otherTime.Year(), otherTime.Month(), otherTime.Day(), 23, 59, 59, 999999999, otherTime.Location())
+ return filterTime(value.Updated.Content, value.Updated.IsNotEmpty, otherStart, otherEnd, time.Now(), operator)
+ }
+ }
+ case KeyTypeSelect, KeyTypeMSelect:
+ if nil != value.MSelect {
+ if nil != other && nil != other.MSelect {
+ switch operator {
+ case FilterOperatorIsEqual, FilterOperatorContains:
+ contains := false
+ for _, v := range value.MSelect {
+ for _, v2 := range other.MSelect {
+ if v.Content == v2.Content {
+ contains = true
+ break
+ }
+ }
+ }
+ return contains
+ case FilterOperatorIsNotEqual, FilterOperatorDoesNotContain:
+ contains := false
+ for _, v := range value.MSelect {
+ for _, v2 := range other.MSelect {
+ if v.Content == v2.Content {
+ contains = true
+ break
+ }
+ }
+ }
+ return !contains
+ case FilterOperatorIsEmpty:
+ return 0 == len(value.MSelect) || 1 == len(value.MSelect) && "" == value.MSelect[0].Content
+ case FilterOperatorIsNotEmpty:
+ return 0 != len(value.MSelect) && !(1 == len(value.MSelect) && "" == value.MSelect[0].Content)
+ }
+ return false
+ }
+
+ // 没有设置比较值
+
+ switch operator {
+ case FilterOperatorIsEqual, FilterOperatorIsNotEqual, FilterOperatorContains, FilterOperatorDoesNotContain:
+ return true
+ case FilterOperatorIsEmpty:
+ return 0 == len(value.MSelect) || 1 == len(value.MSelect) && "" == value.MSelect[0].Content
+ case FilterOperatorIsNotEmpty:
+ return 0 != len(value.MSelect) && !(1 == len(value.MSelect) && "" == value.MSelect[0].Content)
+ }
+ }
+ case KeyTypeURL:
+ if nil != value.URL && nil != other && nil != other.URL {
+ switch operator {
+ case FilterOperatorIsEqual:
+ return value.URL.Content == other.URL.Content
+ case FilterOperatorIsNotEqual:
+ return value.URL.Content != other.URL.Content
+ case FilterOperatorContains:
+ return strings.Contains(value.URL.Content, other.URL.Content)
+ case FilterOperatorDoesNotContain:
+ return !strings.Contains(value.URL.Content, other.URL.Content)
+ case FilterOperatorStartsWith:
+ return strings.HasPrefix(value.URL.Content, other.URL.Content)
+ case FilterOperatorEndsWith:
+ return strings.HasSuffix(value.URL.Content, other.URL.Content)
+ case FilterOperatorIsEmpty:
+ return "" == strings.TrimSpace(value.URL.Content)
+ case FilterOperatorIsNotEmpty:
+ return "" != strings.TrimSpace(value.URL.Content)
+ }
+ }
+ case KeyTypeEmail:
+ if nil != value.Email && nil != other && nil != other.Email {
+ switch operator {
+ case FilterOperatorIsEqual:
+ return value.Email.Content == other.Email.Content
+ case FilterOperatorIsNotEqual:
+ return value.Email.Content != other.Email.Content
+ case FilterOperatorContains:
+ return strings.Contains(value.Email.Content, other.Email.Content)
+ case FilterOperatorDoesNotContain:
+ return !strings.Contains(value.Email.Content, other.Email.Content)
+ case FilterOperatorStartsWith:
+ return strings.HasPrefix(value.Email.Content, other.Email.Content)
+ case FilterOperatorEndsWith:
+ return strings.HasSuffix(value.Email.Content, other.Email.Content)
+ case FilterOperatorIsEmpty:
+ return "" == strings.TrimSpace(value.Email.Content)
+ case FilterOperatorIsNotEmpty:
+ return "" != strings.TrimSpace(value.Email.Content)
+ }
+ }
+ case KeyTypePhone:
+ if nil != value.Phone && nil != other && nil != other.Phone {
+ switch operator {
+ case FilterOperatorIsEqual:
+ return value.Phone.Content == other.Phone.Content
+ case FilterOperatorIsNotEqual:
+ return value.Phone.Content != other.Phone.Content
+ case FilterOperatorContains:
+ return strings.Contains(value.Phone.Content, other.Phone.Content)
+ case FilterOperatorDoesNotContain:
+ return !strings.Contains(value.Phone.Content, other.Phone.Content)
+ case FilterOperatorStartsWith:
+ return strings.HasPrefix(value.Phone.Content, other.Phone.Content)
+ case FilterOperatorEndsWith:
+ return strings.HasSuffix(value.Phone.Content, other.Phone.Content)
+ case FilterOperatorIsEmpty:
+ return "" == strings.TrimSpace(value.Phone.Content)
+ case FilterOperatorIsNotEmpty:
+ return "" != strings.TrimSpace(value.Phone.Content)
+ }
+ }
+ case KeyTypeMAsset:
+ if nil != value.MAsset && nil != other && nil != other.MAsset && 0 < len(value.MAsset) && 0 < len(other.MAsset) {
+ switch operator {
+ case FilterOperatorIsEqual, FilterOperatorContains:
+ contains := false
+ for _, v := range value.MAsset {
+ for _, v2 := range other.MAsset {
+ if v.Content == v2.Content {
+ contains = true
+ break
+ }
+ }
+ }
+ return contains
+ case FilterOperatorIsNotEqual, FilterOperatorDoesNotContain:
+ contains := false
+ for _, v := range value.MAsset {
+ for _, v2 := range other.MAsset {
+ if v.Content == v2.Content {
+ contains = true
+ break
+ }
+ }
+ }
+ return !contains
+ case FilterOperatorIsEmpty:
+ return 0 == len(value.MAsset) || 1 == len(value.MAsset) && "" == value.MAsset[0].Content
+ case FilterOperatorIsNotEmpty:
+ return 0 != len(value.MAsset) && !(1 == len(value.MAsset) && "" == value.MAsset[0].Content)
+ }
+ }
+ case KeyTypeTemplate:
+ if nil != value.Template && nil != other && nil != other.Template {
+ switch operator {
+ case FilterOperatorIsEqual:
+ if "" == strings.TrimSpace(other.Template.Content) {
+ return true
+ }
+ return value.Template.Content == other.Template.Content
+ case FilterOperatorIsNotEqual:
+ if "" == strings.TrimSpace(other.Template.Content) {
+ return true
+ }
+ return value.Template.Content != other.Template.Content
+ case FilterOperatorIsGreater:
+ if "" == strings.TrimSpace(other.Template.Content) {
+ return true
+ }
+ return value.Template.Content > other.Template.Content
+ case FilterOperatorIsGreaterOrEqual:
+ if "" == strings.TrimSpace(other.Template.Content) {
+ return true
+ }
+ return value.Template.Content >= other.Template.Content
+ case FilterOperatorIsLess:
+ if "" == strings.TrimSpace(other.Template.Content) {
+ return true
+ }
+ return value.Template.Content < other.Template.Content
+ case FilterOperatorIsLessOrEqual:
+ if "" == strings.TrimSpace(other.Template.Content) {
+ return true
+ }
+ return value.Template.Content <= other.Template.Content
+ case FilterOperatorContains:
+ if "" == strings.TrimSpace(other.Template.Content) {
+ return true
+ }
+ return strings.Contains(value.Template.Content, other.Template.Content)
+ case FilterOperatorDoesNotContain:
+ if "" == strings.TrimSpace(other.Template.Content) {
+ return true
+ }
+ return !strings.Contains(value.Template.Content, other.Template.Content)
+ case FilterOperatorStartsWith:
+ if "" == strings.TrimSpace(other.Template.Content) {
+ return true
+ }
+ return strings.HasPrefix(value.Template.Content, other.Template.Content)
+ case FilterOperatorEndsWith:
+ if "" == strings.TrimSpace(other.Template.Content) {
+ return true
+ }
+ return strings.HasSuffix(value.Template.Content, other.Template.Content)
+ case FilterOperatorIsEmpty:
+ return "" == strings.TrimSpace(value.Template.Content)
+ case FilterOperatorIsNotEmpty:
+ return "" != strings.TrimSpace(value.Template.Content)
+ }
+ }
+ case KeyTypeCheckbox:
+ if nil != value.Checkbox {
+ switch operator {
+ case FilterOperatorIsTrue:
+ return value.Checkbox.Checked
+ case FilterOperatorIsFalse:
+ return !value.Checkbox.Checked
+ }
+ }
+ }
+
+ switch operator {
+ case FilterOperatorIsEmpty:
+ return value.IsEmpty()
+ case FilterOperatorIsNotEmpty:
+ return !value.IsEmpty()
+ }
+ return false
+}
+
+func filterTime(valueMills int64, valueIsNotEmpty bool, otherValueStart, otherValueEnd, otherValueEnd2 time.Time, operator FilterOperator) bool {
+ valueTime := time.UnixMilli(valueMills)
+ switch operator {
+ case FilterOperatorIsEqual:
+ return (valueTime.After(otherValueStart) || valueTime.Equal(otherValueStart)) && valueTime.Before(otherValueEnd)
+ case FilterOperatorIsNotEqual:
+ return valueTime.Before(otherValueStart) || valueTime.After(otherValueEnd)
+ case FilterOperatorIsGreater:
+ return valueTime.After(otherValueEnd) || valueTime.Equal(otherValueEnd)
+ case FilterOperatorIsGreaterOrEqual:
+ return valueTime.After(otherValueStart) || valueTime.Equal(otherValueStart)
+ case FilterOperatorIsLess:
+ return valueTime.Before(otherValueStart)
+ case FilterOperatorIsLessOrEqual:
+ return valueTime.Before(otherValueEnd) || valueTime.Equal(otherValueEnd)
+ case FilterOperatorIsBetween:
+ return (valueTime.After(otherValueStart) || valueTime.Equal(otherValueStart)) && (valueTime.Before(otherValueEnd2) || valueTime.Equal(otherValueEnd2))
+ case FilterOperatorIsEmpty:
+ return !valueIsNotEmpty
+ case FilterOperatorIsNotEmpty:
+ return valueIsNotEmpty
+ }
+ return false
+}
+
+// 根据 Count、Unit 和 Direction 计算相对当前时间的开始时间和结束时间
+func calcRelativeTimeRegion(count int, unit RelativeDateUnit, direction RelativeDateDirection) (start, end time.Time) {
+ now := time.Now()
+ switch unit {
+ case RelativeDateUnitDay:
+ switch direction {
+ case RelativeDateDirectionBefore:
+ // 结束时间使用今天的开始时间
+ end = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
+ // 开始时间使用结束时间减去 count 天
+ start = end.AddDate(0, 0, -count)
+ case RelativeDateDirectionThis:
+ // 开始时间使用今天的开始时间
+ start = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
+ // 结束时间使用开始时间加上 count 天
+ end = start.AddDate(0, 0, count)
+ case RelativeDateDirectionAfter:
+ // 开始时间使用今天的结束时间
+ start = time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 999999999, now.Location())
+ // 结束时间使用开始时间加上 count 天
+ end = start.AddDate(0, 0, count)
+ }
+ case RelativeDateUnitWeek:
+ weekday := int(now.Weekday())
+ if 0 == weekday {
+ weekday = 7
+ }
+ switch direction {
+ case RelativeDateDirectionBefore:
+ // 结束时间使用本周的开始时间
+ end = time.Date(now.Year(), now.Month(), now.Day()-weekday, 0, 0, 0, 0, now.Location())
+ // 开始时间使用结束时间减去 count*7 天
+ start = end.AddDate(0, 0, -count*7)
+ case RelativeDateDirectionThis:
+ // 开始时间使用本周的开始时间
+ start = time.Date(now.Year(), now.Month(), now.Day()-weekday, 0, 0, 0, 0, now.Location())
+ // 结束时间使用开始时间加上 count*7 天
+ end = start.AddDate(0, 0, count*7)
+ case RelativeDateDirectionAfter:
+ // 开始时间使用本周的结束时间
+ start = time.Date(now.Year(), now.Month(), now.Day()-weekday+7, 23, 59, 59, 999999999, now.Location())
+ // 结束时间使用开始时间加上 count*7 天
+ end = start.AddDate(0, 0, count*7)
+ }
+ case RelativeDateUnitMonth:
+ switch direction {
+ case RelativeDateDirectionBefore:
+ // 结束时间使用本月的开始时间
+ end = time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
+ // 开始时间使用结束时间减去 count 个月
+ start = end.AddDate(0, -count, 0)
+ case RelativeDateDirectionThis:
+ // 开始时间使用本月的开始时间
+ start = time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
+ // 结束时间使用开始时间加上 count 个月
+ end = start.AddDate(0, count, 0)
+ case RelativeDateDirectionAfter:
+ // 开始时间使用本月的结束时间
+ start = time.Date(now.Year(), now.Month()+1, 1, 0, 0, 0, 0, now.Location()).Add(-time.Nanosecond)
+ // 结束时间使用开始时间加上 count 个月
+ end = start.AddDate(0, count, 0)
+ }
+ case RelativeDateUnitYear:
+ switch direction {
+ case RelativeDateDirectionBefore:
+ // 结束时间使用今年的开始时间
+ end = time.Date(now.Year(), 1, 1, 0, 0, 0, 0, now.Location())
+ // 开始时间使用结束时间减去 count 年
+ start = end.AddDate(-count, 0, 0)
+ case RelativeDateDirectionThis:
+ // 开始时间使用今年的开始时间
+ start = time.Date(now.Year(), 1, 1, 0, 0, 0, 0, now.Location())
+ // 结束时间使用开始时间加上 count 年
+ end = start.AddDate(count, 0, 0)
+ case RelativeDateDirectionAfter:
+ // 开始时间使用今年的结束时间
+ start = time.Date(now.Year()+1, 1, 1, 0, 0, 0, 0, now.Location()).Add(-time.Nanosecond)
+ // 结束时间使用开始时间加上 count 年
+ end = start.AddDate(count, 0, 0)
+ }
+ }
+ return
+}
+
func (filter *ViewFilter) GetAffectValue(key *Key, defaultVal *Value) (ret *Value) {
if nil != filter.Value {
if KeyTypeRelation == filter.Value.Type || KeyTypeTemplate == filter.Value.Type || KeyTypeRollup == filter.Value.Type || KeyTypeUpdated == filter.Value.Type || KeyTypeCreated == filter.Value.Type {
diff --git a/kernel/av/sort.go b/kernel/av/sort.go
index d95877a05..fef2cd9ed 100644
--- a/kernel/av/sort.go
+++ b/kernel/av/sort.go
@@ -16,6 +16,14 @@
package av
+import (
+ "bytes"
+ "strconv"
+ "strings"
+
+ "github.com/siyuan-note/siyuan/kernel/util"
+)
+
type Sortable interface {
SortRows()
}
@@ -31,3 +39,238 @@ const (
SortOrderAsc SortOrder = "ASC"
SortOrderDesc SortOrder = "DESC"
)
+
+func (value *Value) Compare(other *Value) int {
+ switch value.Type {
+ case KeyTypeBlock:
+ if nil != value.Block && nil != other.Block {
+ ret := strings.Compare(value.Block.Content, other.Block.Content)
+ if 0 == ret {
+ ret = int(value.CreatedAt - other.CreatedAt)
+ }
+ return ret
+ }
+ case KeyTypeText:
+ if nil != value.Text && nil != other.Text {
+ ret := strings.Compare(value.Text.Content, other.Text.Content)
+ if 0 == ret {
+ ret = int(value.CreatedAt - other.CreatedAt)
+ }
+ return ret
+ }
+ case KeyTypeNumber:
+ if nil != value.Number && nil != other.Number {
+ if value.Number.IsNotEmpty {
+ if !other.Number.IsNotEmpty {
+ return 1
+ }
+
+ if value.Number.Content > other.Number.Content {
+ return 1
+ } else if value.Number.Content < other.Number.Content {
+ return -1
+ } else {
+ return int(value.CreatedAt - other.CreatedAt)
+ }
+ } else {
+ if other.Number.IsNotEmpty {
+ return -1
+ }
+ return int(value.CreatedAt - other.CreatedAt)
+ }
+ }
+ case KeyTypeDate:
+ if nil != value.Date && nil != other.Date {
+ if value.Date.IsNotEmpty {
+ if !other.Date.IsNotEmpty {
+ return 1
+ }
+ if value.Date.Content > other.Date.Content {
+ return 1
+ } else if value.Date.Content < other.Date.Content {
+ return -1
+ } else {
+ return int(value.CreatedAt - other.CreatedAt)
+ }
+ } else {
+ if other.Date.IsNotEmpty {
+ return -1
+ }
+ return int(value.CreatedAt - other.CreatedAt)
+ }
+ }
+ case KeyTypeCreated:
+ if nil != value.Created && nil != other.Created {
+ if value.Created.Content > other.Created.Content {
+ return 1
+ } else if value.Created.Content < other.Created.Content {
+ return -1
+ } else {
+ return int(value.CreatedAt - other.CreatedAt)
+ }
+ }
+ case KeyTypeUpdated:
+ if nil != value.Updated && nil != other.Updated {
+ if value.Updated.Content > other.Updated.Content {
+ return 1
+ } else if value.Updated.Content < other.Updated.Content {
+ return -1
+ } else {
+ return int(value.CreatedAt - other.CreatedAt)
+ }
+ }
+ case KeyTypeSelect, KeyTypeMSelect:
+ if nil != value.MSelect && nil != other.MSelect {
+ var v1 string
+ for _, v := range value.MSelect {
+ v1 += v.Content
+ }
+ var v2 string
+ for _, v := range other.MSelect {
+ v2 += v.Content
+ }
+ ret := strings.Compare(v1, v2)
+ if 0 == ret {
+ ret = int(value.CreatedAt - other.CreatedAt)
+ }
+ return ret
+ }
+ case KeyTypeURL:
+ if nil != value.URL && nil != other.URL {
+ ret := strings.Compare(value.URL.Content, other.URL.Content)
+ if 0 == ret {
+ ret = int(value.CreatedAt - other.CreatedAt)
+ }
+ return ret
+ }
+ case KeyTypeEmail:
+ if nil != value.Email && nil != other.Email {
+ ret := strings.Compare(value.Email.Content, other.Email.Content)
+ if 0 == ret {
+ ret = int(value.CreatedAt - other.CreatedAt)
+ }
+ return ret
+ }
+ case KeyTypePhone:
+ if nil != value.Phone && nil != other.Phone {
+ ret := strings.Compare(value.Phone.Content, other.Phone.Content)
+ if 0 == ret {
+ ret = int(value.CreatedAt - other.CreatedAt)
+ }
+ return ret
+ }
+ case KeyTypeMAsset:
+ if nil != value.MAsset && nil != other.MAsset {
+ var v1 string
+ for _, v := range value.MAsset {
+ v1 += v.Content
+ }
+ var v2 string
+ for _, v := range other.MAsset {
+ v2 += v.Content
+ }
+ ret := strings.Compare(v1, v2)
+ if 0 == ret {
+ ret = int(value.CreatedAt - other.CreatedAt)
+ }
+ return ret
+ }
+ case KeyTypeTemplate:
+ if nil != value.Template && nil != other.Template {
+ vContent := strings.TrimSpace(value.Template.Content)
+ oContent := strings.TrimSpace(other.Template.Content)
+ if util.IsNumeric(vContent) && util.IsNumeric(oContent) {
+ v1, _ := strconv.ParseFloat(vContent, 64)
+ v2, _ := strconv.ParseFloat(oContent, 64)
+ if v1 > v2 {
+ return 1
+ }
+ if v1 < v2 {
+ return -1
+ }
+ return int(value.CreatedAt - other.CreatedAt)
+ }
+ ret := strings.Compare(value.Template.Content, other.Template.Content)
+ if 0 == ret {
+ ret = int(value.CreatedAt - other.CreatedAt)
+ }
+ return ret
+ }
+ case KeyTypeCheckbox:
+ if nil != value.Checkbox && nil != other.Checkbox {
+ if value.Checkbox.Checked && !other.Checkbox.Checked {
+ return 1
+ }
+ if !value.Checkbox.Checked && other.Checkbox.Checked {
+ return -1
+ }
+ return int(value.CreatedAt - other.CreatedAt)
+ }
+ case KeyTypeRelation:
+ if nil != value.Relation && nil != other.Relation {
+ vContentBuf := bytes.Buffer{}
+ for _, c := range value.Relation.Contents {
+ vContentBuf.WriteString(c.String())
+ vContentBuf.WriteByte(' ')
+ }
+ vContent := strings.TrimSpace(vContentBuf.String())
+ oContentBuf := bytes.Buffer{}
+ for _, c := range other.Relation.Contents {
+ oContentBuf.WriteString(c.String())
+ oContentBuf.WriteByte(' ')
+ }
+ oContent := strings.TrimSpace(oContentBuf.String())
+
+ if util.IsNumeric(vContent) && util.IsNumeric(oContent) {
+ v1, _ := strconv.ParseFloat(vContent, 64)
+ v2, _ := strconv.ParseFloat(oContent, 64)
+ if v1 > v2 {
+ return 1
+ }
+
+ if v1 < v2 {
+ return -1
+ }
+ return int(value.CreatedAt - other.CreatedAt)
+ }
+ ret := strings.Compare(vContent, oContent)
+ if 0 == ret {
+ ret = int(value.CreatedAt - other.CreatedAt)
+ }
+ return ret
+ }
+ case KeyTypeRollup:
+ if nil != value.Rollup && nil != other.Rollup {
+ vContentBuf := bytes.Buffer{}
+ for _, c := range value.Rollup.Contents {
+ vContentBuf.WriteString(c.String())
+ vContentBuf.WriteByte(' ')
+ }
+ vContent := strings.TrimSpace(vContentBuf.String())
+ oContentBuf := bytes.Buffer{}
+ for _, c := range other.Rollup.Contents {
+ oContentBuf.WriteString(c.String())
+ oContentBuf.WriteByte(' ')
+ }
+ oContent := strings.TrimSpace(oContentBuf.String())
+
+ if util.IsNumeric(vContent) && util.IsNumeric(oContent) {
+ v1, _ := strconv.ParseFloat(vContent, 64)
+ v2, _ := strconv.ParseFloat(oContent, 64)
+ if v1 > v2 {
+ return 1
+ }
+ if v1 < v2 {
+ return -1
+ }
+ return int(value.CreatedAt - other.CreatedAt)
+ }
+ ret := strings.Compare(vContent, oContent)
+ if 0 == ret {
+ ret = int(value.CreatedAt - other.CreatedAt)
+ }
+ return ret
+ }
+ }
+ return int(value.CreatedAt - other.CreatedAt)
+}
diff --git a/kernel/av/table.go b/kernel/av/table.go
index 6ebfe77fd..10fcbf369 100644
--- a/kernel/av/table.go
+++ b/kernel/av/table.go
@@ -17,13 +17,9 @@
package av
import (
- "bytes"
- "github.com/siyuan-note/siyuan/kernel/util"
"math"
"sort"
"strconv"
- "strings"
- "time"
)
// LayoutTable 描述了表格布局的结构。
@@ -82,775 +78,6 @@ const (
CalcOperatorPercentUnchecked CalcOperator = "Percent unchecked"
)
-func (value *Value) Compare(other *Value) int {
- switch value.Type {
- case KeyTypeBlock:
- if nil != value.Block && nil != other.Block {
- ret := strings.Compare(value.Block.Content, other.Block.Content)
- if 0 == ret {
- ret = int(value.CreatedAt - other.CreatedAt)
- }
- return ret
- }
- case KeyTypeText:
- if nil != value.Text && nil != other.Text {
- ret := strings.Compare(value.Text.Content, other.Text.Content)
- if 0 == ret {
- ret = int(value.CreatedAt - other.CreatedAt)
- }
- return ret
- }
- case KeyTypeNumber:
- if nil != value.Number && nil != other.Number {
- if value.Number.IsNotEmpty {
- if !other.Number.IsNotEmpty {
- return 1
- }
-
- if value.Number.Content > other.Number.Content {
- return 1
- } else if value.Number.Content < other.Number.Content {
- return -1
- } else {
- return int(value.CreatedAt - other.CreatedAt)
- }
- } else {
- if other.Number.IsNotEmpty {
- return -1
- }
- return int(value.CreatedAt - other.CreatedAt)
- }
- }
- case KeyTypeDate:
- if nil != value.Date && nil != other.Date {
- if value.Date.IsNotEmpty {
- if !other.Date.IsNotEmpty {
- return 1
- }
- if value.Date.Content > other.Date.Content {
- return 1
- } else if value.Date.Content < other.Date.Content {
- return -1
- } else {
- return int(value.CreatedAt - other.CreatedAt)
- }
- } else {
- if other.Date.IsNotEmpty {
- return -1
- }
- return int(value.CreatedAt - other.CreatedAt)
- }
- }
- case KeyTypeCreated:
- if nil != value.Created && nil != other.Created {
- if value.Created.Content > other.Created.Content {
- return 1
- } else if value.Created.Content < other.Created.Content {
- return -1
- } else {
- return int(value.CreatedAt - other.CreatedAt)
- }
- }
- case KeyTypeUpdated:
- if nil != value.Updated && nil != other.Updated {
- if value.Updated.Content > other.Updated.Content {
- return 1
- } else if value.Updated.Content < other.Updated.Content {
- return -1
- } else {
- return int(value.CreatedAt - other.CreatedAt)
- }
- }
- case KeyTypeSelect, KeyTypeMSelect:
- if nil != value.MSelect && nil != other.MSelect {
- var v1 string
- for _, v := range value.MSelect {
- v1 += v.Content
- }
- var v2 string
- for _, v := range other.MSelect {
- v2 += v.Content
- }
- ret := strings.Compare(v1, v2)
- if 0 == ret {
- ret = int(value.CreatedAt - other.CreatedAt)
- }
- return ret
- }
- case KeyTypeURL:
- if nil != value.URL && nil != other.URL {
- ret := strings.Compare(value.URL.Content, other.URL.Content)
- if 0 == ret {
- ret = int(value.CreatedAt - other.CreatedAt)
- }
- return ret
- }
- case KeyTypeEmail:
- if nil != value.Email && nil != other.Email {
- ret := strings.Compare(value.Email.Content, other.Email.Content)
- if 0 == ret {
- ret = int(value.CreatedAt - other.CreatedAt)
- }
- return ret
- }
- case KeyTypePhone:
- if nil != value.Phone && nil != other.Phone {
- ret := strings.Compare(value.Phone.Content, other.Phone.Content)
- if 0 == ret {
- ret = int(value.CreatedAt - other.CreatedAt)
- }
- return ret
- }
- case KeyTypeMAsset:
- if nil != value.MAsset && nil != other.MAsset {
- var v1 string
- for _, v := range value.MAsset {
- v1 += v.Content
- }
- var v2 string
- for _, v := range other.MAsset {
- v2 += v.Content
- }
- ret := strings.Compare(v1, v2)
- if 0 == ret {
- ret = int(value.CreatedAt - other.CreatedAt)
- }
- return ret
- }
- case KeyTypeTemplate:
- if nil != value.Template && nil != other.Template {
- vContent := strings.TrimSpace(value.Template.Content)
- oContent := strings.TrimSpace(other.Template.Content)
- if util.IsNumeric(vContent) && util.IsNumeric(oContent) {
- v1, _ := strconv.ParseFloat(vContent, 64)
- v2, _ := strconv.ParseFloat(oContent, 64)
- if v1 > v2 {
- return 1
- }
- if v1 < v2 {
- return -1
- }
- return int(value.CreatedAt - other.CreatedAt)
- }
- ret := strings.Compare(value.Template.Content, other.Template.Content)
- if 0 == ret {
- ret = int(value.CreatedAt - other.CreatedAt)
- }
- return ret
- }
- case KeyTypeCheckbox:
- if nil != value.Checkbox && nil != other.Checkbox {
- if value.Checkbox.Checked && !other.Checkbox.Checked {
- return 1
- }
- if !value.Checkbox.Checked && other.Checkbox.Checked {
- return -1
- }
- return int(value.CreatedAt - other.CreatedAt)
- }
- case KeyTypeRelation:
- if nil != value.Relation && nil != other.Relation {
- vContentBuf := bytes.Buffer{}
- for _, c := range value.Relation.Contents {
- vContentBuf.WriteString(c.String())
- vContentBuf.WriteByte(' ')
- }
- vContent := strings.TrimSpace(vContentBuf.String())
- oContentBuf := bytes.Buffer{}
- for _, c := range other.Relation.Contents {
- oContentBuf.WriteString(c.String())
- oContentBuf.WriteByte(' ')
- }
- oContent := strings.TrimSpace(oContentBuf.String())
-
- if util.IsNumeric(vContent) && util.IsNumeric(oContent) {
- v1, _ := strconv.ParseFloat(vContent, 64)
- v2, _ := strconv.ParseFloat(oContent, 64)
- if v1 > v2 {
- return 1
- }
-
- if v1 < v2 {
- return -1
- }
- return int(value.CreatedAt - other.CreatedAt)
- }
- ret := strings.Compare(vContent, oContent)
- if 0 == ret {
- ret = int(value.CreatedAt - other.CreatedAt)
- }
- return ret
- }
- case KeyTypeRollup:
- if nil != value.Rollup && nil != other.Rollup {
- vContentBuf := bytes.Buffer{}
- for _, c := range value.Rollup.Contents {
- vContentBuf.WriteString(c.String())
- vContentBuf.WriteByte(' ')
- }
- vContent := strings.TrimSpace(vContentBuf.String())
- oContentBuf := bytes.Buffer{}
- for _, c := range other.Rollup.Contents {
- oContentBuf.WriteString(c.String())
- oContentBuf.WriteByte(' ')
- }
- oContent := strings.TrimSpace(oContentBuf.String())
-
- if util.IsNumeric(vContent) && util.IsNumeric(oContent) {
- v1, _ := strconv.ParseFloat(vContent, 64)
- v2, _ := strconv.ParseFloat(oContent, 64)
- if v1 > v2 {
- return 1
- }
- if v1 < v2 {
- return -1
- }
- return int(value.CreatedAt - other.CreatedAt)
- }
- ret := strings.Compare(vContent, oContent)
- if 0 == ret {
- ret = int(value.CreatedAt - other.CreatedAt)
- }
- return ret
- }
- }
- return int(value.CreatedAt - other.CreatedAt)
-}
-
-func (value *Value) Filter(filter *ViewFilter, attrView *AttributeView, rowID string) bool {
- if nil == filter || (nil == filter.Value && nil == filter.RelativeDate) {
- return true
- }
-
- if nil != filter.Value && value.Type != filter.Value.Type {
- // 由于字段类型被用户编辑过导致和过滤器值类型不匹配,该情况下不过滤
- return true
- }
-
- if nil != value.Rollup && KeyTypeRollup == value.Type && nil != filter && nil != filter.Value && KeyTypeRollup == filter.Value.Type &&
- nil != filter.Value.Rollup && 0 < len(filter.Value.Rollup.Contents) {
- // 单独处理汇总类型的比较
- key, _ := attrView.GetKey(value.KeyID)
- if nil == key {
- return false
- }
-
- relKey, _ := attrView.GetKey(key.Rollup.RelationKeyID)
- if nil == relKey {
- return false
- }
-
- relVal := attrView.GetValue(relKey.ID, rowID)
- if nil == relVal || nil == relVal.Relation {
- return false
- }
-
- destAv, _ := ParseAttributeView(relKey.Relation.AvID)
- if nil == destAv {
- return false
- }
-
- for _, blockID := range relVal.Relation.BlockIDs {
- destVal := destAv.GetValue(key.Rollup.KeyID, blockID)
- if nil == destVal {
- continue
- }
-
- if destVal.filter(filter.Value.Rollup.Contents[0], filter.RelativeDate, filter.RelativeDate2, filter.Operator) {
- return true
- }
- }
- return false
- }
-
- if nil != value.Relation && KeyTypeRelation == value.Type && 0 < len(value.Relation.Contents) && nil != filter && nil != filter.Value && KeyTypeRelation == filter.Value.Type &&
- nil != filter.Value.Relation && 0 < len(filter.Value.Relation.BlockIDs) {
- // 单独处理关联类型的比较
- for _, relationValue := range value.Relation.Contents {
- filterValue := &Value{Type: KeyTypeBlock, Block: &ValueBlock{Content: filter.Value.Relation.BlockIDs[0]}}
- if relationValue.filter(filterValue, filter.RelativeDate, filter.RelativeDate2, filter.Operator) {
- return true
- }
- }
- return false
- }
-
- return value.filter(filter.Value, filter.RelativeDate, filter.RelativeDate2, filter.Operator)
-}
-
-func (value *Value) filter(other *Value, relativeDate, relativeDate2 *RelativeDate, operator FilterOperator) bool {
- switch value.Type {
- case KeyTypeBlock:
- if nil != value.Block && nil != other && nil != other.Block {
- switch operator {
- case FilterOperatorIsEqual:
- return value.Block.Content == other.Block.Content
- case FilterOperatorIsNotEqual:
- return value.Block.Content != other.Block.Content
- case FilterOperatorContains:
- return strings.Contains(value.Block.Content, other.Block.Content)
- case FilterOperatorDoesNotContain:
- return !strings.Contains(value.Block.Content, other.Block.Content)
- case FilterOperatorStartsWith:
- return strings.HasPrefix(value.Block.Content, other.Block.Content)
- case FilterOperatorEndsWith:
- return strings.HasSuffix(value.Block.Content, other.Block.Content)
- case FilterOperatorIsEmpty:
- return "" == strings.TrimSpace(value.Block.Content)
- case FilterOperatorIsNotEmpty:
- return "" != strings.TrimSpace(value.Block.Content)
- }
- }
- case KeyTypeText:
- if nil != value.Text && nil != other && nil != other.Text {
- switch operator {
- case FilterOperatorIsEqual:
- if "" == strings.TrimSpace(other.Text.Content) {
- return true
- }
- return value.Text.Content == other.Text.Content
- case FilterOperatorIsNotEqual:
- if "" == strings.TrimSpace(other.Text.Content) {
- return true
- }
- return value.Text.Content != other.Text.Content
- case FilterOperatorContains:
- if "" == strings.TrimSpace(other.Text.Content) {
- return true
- }
- return strings.Contains(value.Text.Content, other.Text.Content)
- case FilterOperatorDoesNotContain:
- if "" == strings.TrimSpace(other.Text.Content) {
- return true
- }
- return !strings.Contains(value.Text.Content, other.Text.Content)
- case FilterOperatorStartsWith:
- if "" == strings.TrimSpace(other.Text.Content) {
- return true
- }
- return strings.HasPrefix(value.Text.Content, other.Text.Content)
- case FilterOperatorEndsWith:
- if "" == strings.TrimSpace(other.Text.Content) {
- return true
- }
- return strings.HasSuffix(value.Text.Content, other.Text.Content)
- case FilterOperatorIsEmpty:
- return "" == strings.TrimSpace(value.Text.Content)
- case FilterOperatorIsNotEmpty:
- return "" != strings.TrimSpace(value.Text.Content)
- }
- }
- case KeyTypeNumber:
- if nil != value.Number && nil != other && nil != other.Number {
- switch operator {
- case FilterOperatorIsEqual:
- if !other.Number.IsNotEmpty {
- return true
- }
- return value.Number.Content == other.Number.Content
- case FilterOperatorIsNotEqual:
- if !other.Number.IsNotEmpty {
- return true
- }
- return value.Number.Content != other.Number.Content
- case FilterOperatorIsGreater:
- return value.Number.Content > other.Number.Content
- case FilterOperatorIsGreaterOrEqual:
- return value.Number.Content >= other.Number.Content
- case FilterOperatorIsLess:
- return value.Number.Content < other.Number.Content
- case FilterOperatorIsLessOrEqual:
- return value.Number.Content <= other.Number.Content
- case FilterOperatorIsEmpty:
- return !value.Number.IsNotEmpty
- case FilterOperatorIsNotEmpty:
- return value.Number.IsNotEmpty
- }
- }
- case KeyTypeDate:
- if nil != value.Date {
- if nil != relativeDate {
- // 使用相对时间比较
-
- count := relativeDate.Count
- unit := relativeDate.Unit
- direction := relativeDate.Direction
- relativeTimeStart, relativeTimeEnd := calcRelativeTimeRegion(count, unit, direction)
- _, relativeTimeEnd2 := calcRelativeTimeRegion(relativeDate2.Count, relativeDate2.Unit, relativeDate2.Direction)
- return filterTime(value.Date.Content, value.Date.IsNotEmpty, relativeTimeStart, relativeTimeEnd, relativeTimeEnd2, operator)
- } else { // 使用具体时间比较
- if nil == other.Date {
- return true
- }
-
- otherTime := time.UnixMilli(other.Date.Content)
- otherStart := time.Date(otherTime.Year(), otherTime.Month(), otherTime.Day(), 0, 0, 0, 0, otherTime.Location())
- otherEnd := time.Date(otherTime.Year(), otherTime.Month(), otherTime.Day(), 23, 59, 59, 999999999, otherTime.Location())
- return filterTime(value.Date.Content, value.Date.IsNotEmpty, otherStart, otherEnd, time.Now(), operator)
- }
- }
- case KeyTypeCreated:
- if nil != value.Created {
- if nil != relativeDate {
- // 使用相对时间比较
-
- count := relativeDate.Count
- unit := relativeDate.Unit
- direction := relativeDate.Direction
- relativeTimeStart, relativeTimeEnd := calcRelativeTimeRegion(count, unit, direction)
- return filterTime(value.Created.Content, true, relativeTimeStart, relativeTimeEnd, time.Now(), operator)
- } else { // 使用具体时间比较
- if nil == other.Created {
- return true
- }
-
- otherTime := time.UnixMilli(other.Created.Content)
- otherStart := time.Date(otherTime.Year(), otherTime.Month(), otherTime.Day(), 0, 0, 0, 0, otherTime.Location())
- otherEnd := time.Date(otherTime.Year(), otherTime.Month(), otherTime.Day(), 23, 59, 59, 999999999, otherTime.Location())
- return filterTime(value.Created.Content, value.Created.IsNotEmpty, otherStart, otherEnd, time.Now(), operator)
- }
- }
- case KeyTypeUpdated:
- if nil != value.Updated {
- if nil != relativeDate {
- // 使用相对时间比较
-
- count := relativeDate.Count
- unit := relativeDate.Unit
- direction := relativeDate.Direction
- relativeTimeStart, relativeTimeEnd := calcRelativeTimeRegion(count, unit, direction)
- return filterTime(value.Updated.Content, true, relativeTimeStart, relativeTimeEnd, time.Now(), operator)
- } else { // 使用具体时间比较
- if nil == other.Updated {
- return true
- }
-
- otherTime := time.UnixMilli(other.Updated.Content)
- otherStart := time.Date(otherTime.Year(), otherTime.Month(), otherTime.Day(), 0, 0, 0, 0, otherTime.Location())
- otherEnd := time.Date(otherTime.Year(), otherTime.Month(), otherTime.Day(), 23, 59, 59, 999999999, otherTime.Location())
- return filterTime(value.Updated.Content, value.Updated.IsNotEmpty, otherStart, otherEnd, time.Now(), operator)
- }
- }
- case KeyTypeSelect, KeyTypeMSelect:
- if nil != value.MSelect {
- if nil != other && nil != other.MSelect {
- switch operator {
- case FilterOperatorIsEqual, FilterOperatorContains:
- contains := false
- for _, v := range value.MSelect {
- for _, v2 := range other.MSelect {
- if v.Content == v2.Content {
- contains = true
- break
- }
- }
- }
- return contains
- case FilterOperatorIsNotEqual, FilterOperatorDoesNotContain:
- contains := false
- for _, v := range value.MSelect {
- for _, v2 := range other.MSelect {
- if v.Content == v2.Content {
- contains = true
- break
- }
- }
- }
- return !contains
- case FilterOperatorIsEmpty:
- return 0 == len(value.MSelect) || 1 == len(value.MSelect) && "" == value.MSelect[0].Content
- case FilterOperatorIsNotEmpty:
- return 0 != len(value.MSelect) && !(1 == len(value.MSelect) && "" == value.MSelect[0].Content)
- }
- return false
- }
-
- // 没有设置比较值
-
- switch operator {
- case FilterOperatorIsEqual, FilterOperatorIsNotEqual, FilterOperatorContains, FilterOperatorDoesNotContain:
- return true
- case FilterOperatorIsEmpty:
- return 0 == len(value.MSelect) || 1 == len(value.MSelect) && "" == value.MSelect[0].Content
- case FilterOperatorIsNotEmpty:
- return 0 != len(value.MSelect) && !(1 == len(value.MSelect) && "" == value.MSelect[0].Content)
- }
- }
- case KeyTypeURL:
- if nil != value.URL && nil != other && nil != other.URL {
- switch operator {
- case FilterOperatorIsEqual:
- return value.URL.Content == other.URL.Content
- case FilterOperatorIsNotEqual:
- return value.URL.Content != other.URL.Content
- case FilterOperatorContains:
- return strings.Contains(value.URL.Content, other.URL.Content)
- case FilterOperatorDoesNotContain:
- return !strings.Contains(value.URL.Content, other.URL.Content)
- case FilterOperatorStartsWith:
- return strings.HasPrefix(value.URL.Content, other.URL.Content)
- case FilterOperatorEndsWith:
- return strings.HasSuffix(value.URL.Content, other.URL.Content)
- case FilterOperatorIsEmpty:
- return "" == strings.TrimSpace(value.URL.Content)
- case FilterOperatorIsNotEmpty:
- return "" != strings.TrimSpace(value.URL.Content)
- }
- }
- case KeyTypeEmail:
- if nil != value.Email && nil != other && nil != other.Email {
- switch operator {
- case FilterOperatorIsEqual:
- return value.Email.Content == other.Email.Content
- case FilterOperatorIsNotEqual:
- return value.Email.Content != other.Email.Content
- case FilterOperatorContains:
- return strings.Contains(value.Email.Content, other.Email.Content)
- case FilterOperatorDoesNotContain:
- return !strings.Contains(value.Email.Content, other.Email.Content)
- case FilterOperatorStartsWith:
- return strings.HasPrefix(value.Email.Content, other.Email.Content)
- case FilterOperatorEndsWith:
- return strings.HasSuffix(value.Email.Content, other.Email.Content)
- case FilterOperatorIsEmpty:
- return "" == strings.TrimSpace(value.Email.Content)
- case FilterOperatorIsNotEmpty:
- return "" != strings.TrimSpace(value.Email.Content)
- }
- }
- case KeyTypePhone:
- if nil != value.Phone && nil != other && nil != other.Phone {
- switch operator {
- case FilterOperatorIsEqual:
- return value.Phone.Content == other.Phone.Content
- case FilterOperatorIsNotEqual:
- return value.Phone.Content != other.Phone.Content
- case FilterOperatorContains:
- return strings.Contains(value.Phone.Content, other.Phone.Content)
- case FilterOperatorDoesNotContain:
- return !strings.Contains(value.Phone.Content, other.Phone.Content)
- case FilterOperatorStartsWith:
- return strings.HasPrefix(value.Phone.Content, other.Phone.Content)
- case FilterOperatorEndsWith:
- return strings.HasSuffix(value.Phone.Content, other.Phone.Content)
- case FilterOperatorIsEmpty:
- return "" == strings.TrimSpace(value.Phone.Content)
- case FilterOperatorIsNotEmpty:
- return "" != strings.TrimSpace(value.Phone.Content)
- }
- }
- case KeyTypeMAsset:
- if nil != value.MAsset && nil != other && nil != other.MAsset && 0 < len(value.MAsset) && 0 < len(other.MAsset) {
- switch operator {
- case FilterOperatorIsEqual, FilterOperatorContains:
- contains := false
- for _, v := range value.MAsset {
- for _, v2 := range other.MAsset {
- if v.Content == v2.Content {
- contains = true
- break
- }
- }
- }
- return contains
- case FilterOperatorIsNotEqual, FilterOperatorDoesNotContain:
- contains := false
- for _, v := range value.MAsset {
- for _, v2 := range other.MAsset {
- if v.Content == v2.Content {
- contains = true
- break
- }
- }
- }
- return !contains
- case FilterOperatorIsEmpty:
- return 0 == len(value.MAsset) || 1 == len(value.MAsset) && "" == value.MAsset[0].Content
- case FilterOperatorIsNotEmpty:
- return 0 != len(value.MAsset) && !(1 == len(value.MAsset) && "" == value.MAsset[0].Content)
- }
- }
- case KeyTypeTemplate:
- if nil != value.Template && nil != other && nil != other.Template {
- switch operator {
- case FilterOperatorIsEqual:
- if "" == strings.TrimSpace(other.Template.Content) {
- return true
- }
- return value.Template.Content == other.Template.Content
- case FilterOperatorIsNotEqual:
- if "" == strings.TrimSpace(other.Template.Content) {
- return true
- }
- return value.Template.Content != other.Template.Content
- case FilterOperatorIsGreater:
- if "" == strings.TrimSpace(other.Template.Content) {
- return true
- }
- return value.Template.Content > other.Template.Content
- case FilterOperatorIsGreaterOrEqual:
- if "" == strings.TrimSpace(other.Template.Content) {
- return true
- }
- return value.Template.Content >= other.Template.Content
- case FilterOperatorIsLess:
- if "" == strings.TrimSpace(other.Template.Content) {
- return true
- }
- return value.Template.Content < other.Template.Content
- case FilterOperatorIsLessOrEqual:
- if "" == strings.TrimSpace(other.Template.Content) {
- return true
- }
- return value.Template.Content <= other.Template.Content
- case FilterOperatorContains:
- if "" == strings.TrimSpace(other.Template.Content) {
- return true
- }
- return strings.Contains(value.Template.Content, other.Template.Content)
- case FilterOperatorDoesNotContain:
- if "" == strings.TrimSpace(other.Template.Content) {
- return true
- }
- return !strings.Contains(value.Template.Content, other.Template.Content)
- case FilterOperatorStartsWith:
- if "" == strings.TrimSpace(other.Template.Content) {
- return true
- }
- return strings.HasPrefix(value.Template.Content, other.Template.Content)
- case FilterOperatorEndsWith:
- if "" == strings.TrimSpace(other.Template.Content) {
- return true
- }
- return strings.HasSuffix(value.Template.Content, other.Template.Content)
- case FilterOperatorIsEmpty:
- return "" == strings.TrimSpace(value.Template.Content)
- case FilterOperatorIsNotEmpty:
- return "" != strings.TrimSpace(value.Template.Content)
- }
- }
- case KeyTypeCheckbox:
- if nil != value.Checkbox {
- switch operator {
- case FilterOperatorIsTrue:
- return value.Checkbox.Checked
- case FilterOperatorIsFalse:
- return !value.Checkbox.Checked
- }
- }
- }
- return false
-}
-
-func filterTime(valueMills int64, valueIsNotEmpty bool, otherValueStart, otherValueEnd, otherValueEnd2 time.Time, operator FilterOperator) bool {
- valueTime := time.UnixMilli(valueMills)
- switch operator {
- case FilterOperatorIsEqual:
- return (valueTime.After(otherValueStart) || valueTime.Equal(otherValueStart)) && valueTime.Before(otherValueEnd)
- case FilterOperatorIsNotEqual:
- return valueTime.Before(otherValueStart) || valueTime.After(otherValueEnd)
- case FilterOperatorIsGreater:
- return valueTime.After(otherValueEnd) || valueTime.Equal(otherValueEnd)
- case FilterOperatorIsGreaterOrEqual:
- return valueTime.After(otherValueStart) || valueTime.Equal(otherValueStart)
- case FilterOperatorIsLess:
- return valueTime.Before(otherValueStart)
- case FilterOperatorIsLessOrEqual:
- return valueTime.Before(otherValueEnd) || valueTime.Equal(otherValueEnd)
- case FilterOperatorIsBetween:
- return (valueTime.After(otherValueStart) || valueTime.Equal(otherValueStart)) && (valueTime.Before(otherValueEnd2) || valueTime.Equal(otherValueEnd2))
- case FilterOperatorIsEmpty:
- return !valueIsNotEmpty
- case FilterOperatorIsNotEmpty:
- return valueIsNotEmpty
- }
- return false
-}
-
-// 根据 Count、Unit 和 Direction 计算相对当前时间的开始时间和结束时间
-func calcRelativeTimeRegion(count int, unit RelativeDateUnit, direction RelativeDateDirection) (start, end time.Time) {
- now := time.Now()
- switch unit {
- case RelativeDateUnitDay:
- switch direction {
- case RelativeDateDirectionBefore:
- // 结束时间使用今天的开始时间
- end = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
- // 开始时间使用结束时间减去 count 天
- start = end.AddDate(0, 0, -count)
- case RelativeDateDirectionThis:
- // 开始时间使用今天的开始时间
- start = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
- // 结束时间使用开始时间加上 count 天
- end = start.AddDate(0, 0, count)
- case RelativeDateDirectionAfter:
- // 开始时间使用今天的结束时间
- start = time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 999999999, now.Location())
- // 结束时间使用开始时间加上 count 天
- end = start.AddDate(0, 0, count)
- }
- case RelativeDateUnitWeek:
- weekday := int(now.Weekday())
- if 0 == weekday {
- weekday = 7
- }
- switch direction {
- case RelativeDateDirectionBefore:
- // 结束时间使用本周的开始时间
- end = time.Date(now.Year(), now.Month(), now.Day()-weekday, 0, 0, 0, 0, now.Location())
- // 开始时间使用结束时间减去 count*7 天
- start = end.AddDate(0, 0, -count*7)
- case RelativeDateDirectionThis:
- // 开始时间使用本周的开始时间
- start = time.Date(now.Year(), now.Month(), now.Day()-weekday, 0, 0, 0, 0, now.Location())
- // 结束时间使用开始时间加上 count*7 天
- end = start.AddDate(0, 0, count*7)
- case RelativeDateDirectionAfter:
- // 开始时间使用本周的结束时间
- start = time.Date(now.Year(), now.Month(), now.Day()-weekday+7, 23, 59, 59, 999999999, now.Location())
- // 结束时间使用开始时间加上 count*7 天
- end = start.AddDate(0, 0, count*7)
- }
- case RelativeDateUnitMonth:
- switch direction {
- case RelativeDateDirectionBefore:
- // 结束时间使用本月的开始时间
- end = time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
- // 开始时间使用结束时间减去 count 个月
- start = end.AddDate(0, -count, 0)
- case RelativeDateDirectionThis:
- // 开始时间使用本月的开始时间
- start = time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
- // 结束时间使用开始时间加上 count 个月
- end = start.AddDate(0, count, 0)
- case RelativeDateDirectionAfter:
- // 开始时间使用本月的结束时间
- start = time.Date(now.Year(), now.Month()+1, 1, 0, 0, 0, 0, now.Location()).Add(-time.Nanosecond)
- // 结束时间使用开始时间加上 count 个月
- end = start.AddDate(0, count, 0)
- }
- case RelativeDateUnitYear:
- switch direction {
- case RelativeDateDirectionBefore:
- // 结束时间使用今年的开始时间
- end = time.Date(now.Year(), 1, 1, 0, 0, 0, 0, now.Location())
- // 开始时间使用结束时间减去 count 年
- start = end.AddDate(-count, 0, 0)
- case RelativeDateDirectionThis:
- // 开始时间使用今年的开始时间
- start = time.Date(now.Year(), 1, 1, 0, 0, 0, 0, now.Location())
- // 结束时间使用开始时间加上 count 年
- end = start.AddDate(count, 0, 0)
- case RelativeDateDirectionAfter:
- // 开始时间使用今年的结束时间
- start = time.Date(now.Year()+1, 1, 1, 0, 0, 0, 0, now.Location()).Add(-time.Nanosecond)
- // 结束时间使用开始时间加上 count 年
- end = start.AddDate(count, 0, 0)
- }
- }
- return
-}
-
// Table 描述了表格实例的结构。
type Table struct {
ID string `json:"id"` // 表格布局 ID
From 271cf982f74774f9667635cacfae46e7a14f15e0 Mon Sep 17 00:00:00 2001
From: Daniel <845765@qq.com>
Date: Wed, 13 Mar 2024 23:54:33 +0800
Subject: [PATCH 4/6] :art: After opening the user guide, prevent folding the
doc tree when syncing data
---
kernel/model/repository.go | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/kernel/model/repository.go b/kernel/model/repository.go
index 68ec0d002..bfc7569f1 100644
--- a/kernel/model/repository.go
+++ b/kernel/model/repository.go
@@ -1475,8 +1475,11 @@ func processSyncMergeResult(exit, byHand bool, mergeResult *dejavu.MergeResult,
}
util.WaitForUILoaded()
- util.BroadcastByType("main", "syncMergeResult", 0, "",
- map[string]interface{}{"upsertRootIDs": upsertRootIDs, "removeRootIDs": removeRootIDs})
+
+ if 0 < len(upsertRootIDs) || 0 < len(removeRootIDs) {
+ util.BroadcastByType("main", "syncMergeResult", 0, "",
+ map[string]interface{}{"upsertRootIDs": upsertRootIDs, "removeRootIDs": removeRootIDs})
+ }
time.Sleep(2 * time.Second)
util.PushStatusBar(fmt.Sprintf(Conf.Language(149), elapsed.Seconds()))
From 6ff917f48debb43f5aefd86c3767adeeb8a991d0 Mon Sep 17 00:00:00 2001
From: Daniel <845765@qq.com>
Date: Thu, 14 Mar 2024 09:46:54 +0800
Subject: [PATCH 5/6] :art: Open the last workspace by default
https://github.com/siyuan-note/siyuan/issues/10570
---
kernel/model/conf.go | 12 ++++++++++++
kernel/util/working.go | 1 -
2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/kernel/model/conf.go b/kernel/model/conf.go
index c664da166..2ee3ffc74 100644
--- a/kernel/model/conf.go
+++ b/kernel/model/conf.go
@@ -587,6 +587,18 @@ func Close(force bool, execInstallPkg int) (exitCode int) {
clearWorkspaceTemp()
clearCorruptedNotebooks()
clearPortJSON()
+
+ // 将当前工作空间放到工作空间列表的最后一个
+ // Open the last workspace by default https://github.com/siyuan-note/siyuan/issues/10570
+ workspacePaths, err := util.ReadWorkspacePaths()
+ if nil != err {
+ logging.LogErrorf("read workspace paths failed: %s", err)
+ } else {
+ workspacePaths = gulu.Str.RemoveElem(workspacePaths, util.WorkspaceDir)
+ workspacePaths = append(workspacePaths, util.WorkspaceDir)
+ util.WriteWorkspacePaths(workspacePaths)
+ }
+
util.UnlockWorkspace()
time.Sleep(500 * time.Millisecond)
diff --git a/kernel/util/working.go b/kernel/util/working.go
index 03caaf096..43dc0c82e 100644
--- a/kernel/util/working.go
+++ b/kernel/util/working.go
@@ -243,7 +243,6 @@ func initWorkspaceDir(workspaceArg string) {
} else {
workspacePaths, _ = ReadWorkspacePaths()
if 0 < len(workspacePaths) {
- // 取最后一个(也就是最近打开的)工作空间
WorkspaceDir = workspacePaths[len(workspacePaths)-1]
} else {
WorkspaceDir = defaultWorkspaceDir
From 63914488f053d5350c78ca808ab6172470192963 Mon Sep 17 00:00:00 2001
From: Daniel <845765@qq.com>
Date: Thu, 14 Mar 2024 10:58:49 +0800
Subject: [PATCH 6/6] :art: `Add to Database` no longer autofills filter values
https://github.com/siyuan-note/siyuan/issues/10587
---
kernel/api/av.go | 6 +++++-
kernel/model/attribute_view.go | 10 +++++-----
kernel/model/transaction.go | 21 +++++++++++----------
3 files changed, 21 insertions(+), 16 deletions(-)
diff --git a/kernel/api/av.go b/kernel/api/av.go
index 687c4f831..36c648ece 100644
--- a/kernel/api/av.go
+++ b/kernel/api/av.go
@@ -122,8 +122,12 @@ func addAttributeViewValues(c *gin.Context) {
previousID = arg["previousID"].(string)
}
isDetached := arg["isDetached"].(bool)
+ ignoreFillFilter := true
+ if nil != arg["ignoreFillFilter"] {
+ ignoreFillFilter = arg["ignoreFillFilter"].(bool)
+ }
- err := model.AddAttributeViewBlock(nil, srcIDs, avID, blockID, previousID, isDetached)
+ err := model.AddAttributeViewBlock(nil, srcIDs, avID, blockID, previousID, isDetached, ignoreFillFilter)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
diff --git a/kernel/model/attribute_view.go b/kernel/model/attribute_view.go
index 7d2364b00..42b55b75b 100644
--- a/kernel/model/attribute_view.go
+++ b/kernel/model/attribute_view.go
@@ -1925,14 +1925,14 @@ func setAttributeViewColumnCalc(operation *Operation) (err error) {
}
func (tx *Transaction) doInsertAttrViewBlock(operation *Operation) (ret *TxErr) {
- err := AddAttributeViewBlock(tx, operation.SrcIDs, operation.AvID, operation.BlockID, operation.PreviousID, operation.IsDetached)
+ err := AddAttributeViewBlock(tx, operation.SrcIDs, operation.AvID, operation.BlockID, operation.PreviousID, operation.IsDetached, operation.IgnoreFillFilterVal)
if nil != err {
return &TxErr{code: TxErrWriteAttributeView, id: operation.AvID, msg: err.Error()}
}
return
}
-func AddAttributeViewBlock(tx *Transaction, srcIDs []string, avID, blockID, previousBlockID string, isDetached bool) (err error) {
+func AddAttributeViewBlock(tx *Transaction, srcIDs []string, avID, blockID, previousBlockID string, isDetached, ignoreFillFilter bool) (err error) {
for _, id := range srcIDs {
var tree *parse.Tree
if !isDetached {
@@ -1948,14 +1948,14 @@ func AddAttributeViewBlock(tx *Transaction, srcIDs []string, avID, blockID, prev
}
}
- if avErr := addAttributeViewBlock(avID, blockID, previousBlockID, id, isDetached, tree, tx); nil != avErr {
+ if avErr := addAttributeViewBlock(avID, blockID, previousBlockID, id, isDetached, ignoreFillFilter, tree, tx); nil != avErr {
return avErr
}
}
return
}
-func addAttributeViewBlock(avID, blockID, previousBlockID, addingBlockID string, isDetached bool, tree *parse.Tree, tx *Transaction) (err error) {
+func addAttributeViewBlock(avID, blockID, previousBlockID, addingBlockID string, isDetached, ignoreFillFilter bool, tree *parse.Tree, tx *Transaction) (err error) {
var node *ast.Node
if !isDetached {
node = treenode.GetNodeInTree(tree, addingBlockID)
@@ -2006,7 +2006,7 @@ func addAttributeViewBlock(avID, blockID, previousBlockID, addingBlockID string,
// 如果存在过滤条件,则将过滤条件应用到新添加的块上
view, _ := getAttrViewViewByBlockID(attrView, blockID)
- if nil != view && 0 < len(view.Table.Filters) {
+ if nil != view && 0 < len(view.Table.Filters) && !ignoreFillFilter {
viewable, _ := renderAttributeViewTable(attrView, view, "")
viewable.FilterRows(attrView)
viewable.SortRows()
diff --git a/kernel/model/transaction.go b/kernel/model/transaction.go
index 1029b18e4..88c356d2e 100644
--- a/kernel/model/transaction.go
+++ b/kernel/model/transaction.go
@@ -1231,16 +1231,17 @@ type Operation struct {
DeckID string `json:"deckID"` // 用于添加/删除闪卡
- AvID string `json:"avID"` // 属性视图 ID
- SrcIDs []string `json:"srcIDs"` // 用于将块拖拽到属性视图中
- IsDetached bool `json:"isDetached"` // 用于标识是否是脱离块,仅存在于属性视图中
- Name string `json:"name"` // 属性视图列名
- Typ string `json:"type"` // 属性视图列类型
- Format string `json:"format"` // 属性视图列格式化
- KeyID string `json:"keyID"` // 属性视列 ID
- RowID string `json:"rowID"` // 属性视图行 ID
- IsTwoWay bool `json:"isTwoWay"` // 属性视图关联列是否是双向关系
- BackRelationKeyID string `json:"backRelationKeyID"` // 属性视图关联列回链关联列的 ID
+ AvID string `json:"avID"` // 属性视图 ID
+ SrcIDs []string `json:"srcIDs"` // 用于将块拖拽到属性视图中
+ IsDetached bool `json:"isDetached"` // 用于标识是否未绑定块,仅存在于属性视图中
+ IgnoreFillFilterVal bool `json:"ignoreFillFilter"` // 用于标识是否忽略填充筛选值
+ Name string `json:"name"` // 属性视图列名
+ Typ string `json:"type"` // 属性视图列类型
+ Format string `json:"format"` // 属性视图列格式化
+ KeyID string `json:"keyID"` // 属性视列 ID
+ RowID string `json:"rowID"` // 属性视图行 ID
+ IsTwoWay bool `json:"isTwoWay"` // 属性视图关联列是否是双向关系
+ BackRelationKeyID string `json:"backRelationKeyID"` // 属性视图关联列回链关联列的 ID
}
type Transaction struct {