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); }; 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/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 diff --git a/kernel/model/attribute_view.go b/kernel/model/attribute_view.go index 820030f99..42b55b75b 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 } @@ -1926,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 { @@ -1949,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) @@ -2007,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/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/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())) 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 { 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