mirror of
https://github.com/siyuan-note/siyuan.git
synced 2026-07-03 14:09:06 +02:00
db13899f38
Signed-off-by: Daniel <845765@qq.com>
1603 lines
56 KiB
Go
1603 lines
56 KiB
Go
// SiYuan - Refactor your thinking
|
|
// Copyright (c) 2020-present, b3log.org
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package av
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/88250/lute/ast"
|
|
"github.com/siyuan-note/logging"
|
|
"github.com/siyuan-note/siyuan/kernel/util"
|
|
)
|
|
|
|
// ViewFilter 描述了视图过滤规则的结构。
|
|
// 叶子节点:Column/Operator/Value 等字段有效,Filters 为空。
|
|
// 分组节点:Combination 指明子节点的组合方式,Filters 为子节点列表(递归)。
|
|
type ViewFilter struct {
|
|
Column string `json:"column"` // 字段(列)ID,叶子节点有效
|
|
Qualifier FilterQuantifier `json:"quantifier,omitempty"` // 量词,叶子节点有效
|
|
Operator FilterOperator `json:"operator"` // 操作符,叶子节点有效
|
|
Value *Value `json:"value"` // 过滤值,叶子节点有效
|
|
RelativeDate *RelativeDate `json:"relativeDate,omitempty"` // 相对时间,叶子节点有效
|
|
RelativeDate2 *RelativeDate `json:"relativeDate2,omitempty"` // 第二个相对时间,叶子节点有效
|
|
Combination FilterCombination `json:"combination,omitempty"` // 组合方式,分组节点有效
|
|
Filters []*ViewFilter `json:"filters,omitempty"` // 子节点,分组节点有效(递归)
|
|
}
|
|
|
|
// FilterCombination 描述了分组节点中子过滤条件的组合方式。
|
|
type FilterCombination string
|
|
|
|
const (
|
|
FilterCombinationAnd FilterCombination = "and" // 全部满足
|
|
FilterCombinationOr FilterCombination = "or" // 任一满足
|
|
)
|
|
|
|
// IsGroup 判断该过滤节点是否为分组节点:设置了组合方式或带有子节点即为分组。
|
|
func (filter *ViewFilter) IsGroup() bool {
|
|
return nil != filter && ("" != filter.Combination || 0 < len(filter.Filters))
|
|
}
|
|
|
|
type RelativeDateUnit int
|
|
|
|
const (
|
|
RelativeDateUnitDay = iota
|
|
RelativeDateUnitWeek
|
|
RelativeDateUnitMonth
|
|
RelativeDateUnitYear
|
|
)
|
|
|
|
type RelativeDateDirection int
|
|
|
|
const (
|
|
RelativeDateDirectionBefore = -1
|
|
RelativeDateDirectionThis = 0
|
|
RelativeDateDirectionAfter = 1
|
|
)
|
|
|
|
type RelativeDate struct {
|
|
Count int `json:"count"` // 数量
|
|
Unit RelativeDateUnit `json:"unit"` // 单位:0 天、1 周、2 月、3 年
|
|
Direction RelativeDateDirection `json:"direction"` // 方向:-1 前、0 当前、1 后
|
|
}
|
|
|
|
type FilterOperator string
|
|
|
|
const (
|
|
FilterOperatorIsEqual FilterOperator = "="
|
|
FilterOperatorIsNotEqual FilterOperator = "!="
|
|
FilterOperatorIsGreater FilterOperator = ">"
|
|
FilterOperatorIsGreaterOrEqual FilterOperator = ">="
|
|
FilterOperatorIsLess FilterOperator = "<"
|
|
FilterOperatorIsLessOrEqual FilterOperator = "<="
|
|
FilterOperatorContains FilterOperator = "Contains"
|
|
FilterOperatorDoesNotContain FilterOperator = "Does not contains"
|
|
FilterOperatorIsEmpty FilterOperator = "Is empty"
|
|
FilterOperatorIsNotEmpty FilterOperator = "Is not empty"
|
|
FilterOperatorStartsWith FilterOperator = "Starts with"
|
|
FilterOperatorEndsWith FilterOperator = "Ends with"
|
|
FilterOperatorIsBetween FilterOperator = "Is between"
|
|
FilterOperatorIsTrue FilterOperator = "Is true"
|
|
FilterOperatorIsFalse FilterOperator = "Is false"
|
|
)
|
|
|
|
type FilterQuantifier string
|
|
|
|
const (
|
|
FilterQuantifierUndefined FilterQuantifier = "" // 等同于 Any
|
|
FilterQuantifierAny FilterQuantifier = "Any"
|
|
FilterQuantifierAll FilterQuantifier = "All"
|
|
FilterQuantifierNone FilterQuantifier = "None"
|
|
)
|
|
|
|
func Filter(viewable Viewable, attrView *AttributeView, rollupFurtherCollections map[string]Collection, cachedAttrViews map[string]*AttributeView) {
|
|
collection := viewable.(Collection)
|
|
filters := collection.GetFilters()
|
|
if 1 > len(filters) {
|
|
return
|
|
}
|
|
|
|
// 归一化为单一根组:spec 5 起顶层应为一个根组;旧数据/异常数据为扁平叶子数组时,
|
|
// 在内存里包成隐式 AND 根组参与求值,不修改原数据、不崩溃。
|
|
root := normalizeFiltersAsRoot(filters)
|
|
if nil == root {
|
|
return
|
|
}
|
|
|
|
// 递归收集所有叶子引用的列 ID 在 values 中的下标,避免每行重复查找。
|
|
fields := collection.GetFields()
|
|
colIndexByColumn := map[string]int{}
|
|
collectLeafColumnIndexes([]*ViewFilter{root}, fields, colIndexByColumn)
|
|
|
|
var items []Item
|
|
for _, item := range collection.GetItems() {
|
|
values := item.GetValues()
|
|
if evalNode(root, values, colIndexByColumn, attrView, item.GetID(), rollupFurtherCollections, cachedAttrViews) {
|
|
items = append(items, item)
|
|
}
|
|
}
|
|
collection.SetItems(items)
|
|
}
|
|
|
|
// normalizeFiltersAsRoot 将 view.Filters 归一化为一个根组并返回。
|
|
// 正常情况(spec 5)下 filters 为 [根组],直接返回 filters[0]。
|
|
// 若 filters 为扁平叶子数组(旧数据或异常),构造一个隐式 AND 根组返回(不写回原 slice)。
|
|
func normalizeFiltersAsRoot(filters []*ViewFilter) *ViewFilter {
|
|
if 1 > len(filters) {
|
|
return nil
|
|
}
|
|
if 1 == len(filters) && filters[0].IsGroup() {
|
|
return filters[0]
|
|
}
|
|
return &ViewFilter{Combination: FilterCombinationAnd, Filters: filters}
|
|
}
|
|
|
|
func ValidateFilterDepth(filters []*ViewFilter) (err error) {
|
|
for _, f := range filters {
|
|
if err = validateFilterNodeDepth(f, 0); nil != err {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func validateFilterNodeDepth(node *ViewFilter, depth int) (err error) {
|
|
if nil == node || !node.IsGroup() {
|
|
return
|
|
}
|
|
if depth > MaxFilterNestingDepth {
|
|
logging.LogErrorf("filter nesting depth [%d] exceeds the maximum [%d]", depth, MaxFilterNestingDepth)
|
|
err = ErrFilterTooDeep
|
|
return
|
|
}
|
|
for _, child := range node.Filters {
|
|
if err = validateFilterNodeDepth(child, depth+1); nil != err {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// collectLeafColumnIndexes 递归收集所有叶子节点引用的列 ID 在 fields 中的下标。
|
|
func collectLeafColumnIndexes(nodes []*ViewFilter, fields []Field, colIndexByColumn map[string]int) {
|
|
for _, node := range nodes {
|
|
if nil == node {
|
|
continue
|
|
}
|
|
if node.IsGroup() {
|
|
collectLeafColumnIndexes(node.Filters, fields, colIndexByColumn)
|
|
continue
|
|
}
|
|
if "" == node.Column {
|
|
continue
|
|
}
|
|
if _, ok := colIndexByColumn[node.Column]; ok {
|
|
continue
|
|
}
|
|
for i, f := range fields {
|
|
if f.GetID() == node.Column {
|
|
colIndexByColumn[node.Column] = i
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// evalNode 递归求值:分组按 Combination 聚合子节点,叶子调用原单元格求值逻辑。
|
|
// 空分组:AND 恒真、OR 恒假。叶子节点的空值/未配置语义由 evalLeaf → value.Filter 保留原扁平行为。
|
|
func evalNode(node *ViewFilter, values []*Value, colIndexByColumn map[string]int, attrView *AttributeView, itemID string, rollupFurtherCollections map[string]Collection, cachedAttrViews map[string]*AttributeView) bool {
|
|
if nil == node {
|
|
return false
|
|
}
|
|
|
|
if node.IsGroup() {
|
|
if 1 > len(node.Filters) {
|
|
// 空分组:AND 视为全部满足(恒真,不阻断);OR 视为任一满足(空集恒假)。
|
|
return FilterCombinationOr != node.Combination
|
|
}
|
|
switch node.Combination {
|
|
case FilterCombinationOr:
|
|
for _, child := range node.Filters {
|
|
if evalNode(child, values, colIndexByColumn, attrView, itemID, rollupFurtherCollections, cachedAttrViews) {
|
|
return true // OR 短路:任一子节点通过即通过
|
|
}
|
|
}
|
|
return false
|
|
default: // FilterCombinationAnd 及空值兜底为 AND
|
|
for _, child := range node.Filters {
|
|
if !evalNode(child, values, colIndexByColumn, attrView, itemID, rollupFurtherCollections, cachedAttrViews) {
|
|
return false // AND 短路:任一子节点不通过即不通过
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
// 叶子节点:列不存在(字段被删)则不过;其余交由 evalLeaf → value.Filter 判定。
|
|
// 注意:未配置的叶子(Value/RelativeDate 均为 nil)由 value.Filter 返回 true(视为通过),
|
|
// 保留扁平时代的原语义,避免对已存在但未填值的过滤条件产生回归。
|
|
index, ok := colIndexByColumn[node.Column]
|
|
if !ok {
|
|
return false // 列不存在(字段被删)则不过
|
|
}
|
|
|
|
return evalLeaf(node, values, index, attrView, itemID, rollupFurtherCollections, cachedAttrViews)
|
|
}
|
|
|
|
// evalLeaf 对叶子过滤节点做单元格级判定,保留扁平时代 Filter() 的空值特判与 text 豁免语义。
|
|
func evalLeaf(filter *ViewFilter, values []*Value, index int, attrView *AttributeView, itemID string, rollupFurtherCollections map[string]Collection, cachedAttrViews map[string]*AttributeView) bool {
|
|
if 0 > index || index >= len(values) {
|
|
return false
|
|
}
|
|
|
|
operator := filter.Operator
|
|
if nil == values[index] {
|
|
// 单元格无值:Is not empty 不过、Is empty 过、其余操作符一律不过
|
|
if FilterOperatorIsNotEmpty == operator {
|
|
return false
|
|
} else if FilterOperatorIsEmpty == operator {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
return values[index].Filter(filter, attrView, itemID, rollupFurtherCollections, cachedAttrViews)
|
|
}
|
|
|
|
// remapFilterColumns 递归将过滤节点树中所有叶子引用的列 ID 映射为新 ID(用于复制视图)。
|
|
func remapFilterColumns(nodes []*ViewFilter, keyIDMap map[string]string) []*ViewFilter {
|
|
for _, node := range nodes {
|
|
if nil == node {
|
|
continue
|
|
}
|
|
if node.IsGroup() {
|
|
remapFilterColumns(node.Filters, keyIDMap)
|
|
continue
|
|
}
|
|
node.Column = keyIDMap[node.Column]
|
|
}
|
|
return nodes
|
|
}
|
|
|
|
// CloneFilters 递归深拷贝过滤节点树,叶子与分组结构均完整复制(含新 Combination/Filters 字段)。
|
|
func CloneFilters(filters []*ViewFilter) (ret []*ViewFilter) {
|
|
for _, f := range filters {
|
|
if nil == f {
|
|
continue
|
|
}
|
|
cloned := &ViewFilter{
|
|
Column: f.Column,
|
|
Qualifier: f.Qualifier,
|
|
Operator: f.Operator,
|
|
Value: f.Value,
|
|
RelativeDate: f.RelativeDate,
|
|
RelativeDate2: f.RelativeDate2,
|
|
Combination: f.Combination,
|
|
}
|
|
if 0 < len(f.Filters) {
|
|
cloned.Filters = CloneFilters(f.Filters)
|
|
}
|
|
ret = append(ret, cloned)
|
|
}
|
|
return
|
|
}
|
|
|
|
// RemoveFiltersByColumn 递归移除所有引用指定列的叶子节点,并裁剪变空的分组节点。
|
|
// 返回处理后的节点列表(原列表不被修改,返回新列表)。
|
|
func RemoveFiltersByColumn(filters []*ViewFilter, column string) (ret []*ViewFilter) {
|
|
for _, f := range filters {
|
|
if nil == f {
|
|
continue
|
|
}
|
|
if f.IsGroup() {
|
|
children := RemoveFiltersByColumn(f.Filters, column)
|
|
// 子节点全部被移除时丢弃空分组
|
|
if 0 < len(children) {
|
|
f.Filters = children
|
|
ret = append(ret, f)
|
|
}
|
|
continue
|
|
}
|
|
if f.Column == column {
|
|
continue
|
|
}
|
|
ret = append(ret, f)
|
|
}
|
|
return
|
|
}
|
|
|
|
// RemoveSelectOptionFromFilters 递归从引用指定列的 select/mSelect 叶子中移除给定选项值。
|
|
// 若叶子移除后选项为空(且非 Is empty/Is not empty 操作符),则整体移除该叶子;并裁剪变空的分组。
|
|
// 返回处理后的节点列表。
|
|
func RemoveSelectOptionFromFilters(filters []*ViewFilter, column, optionContent string) (ret []*ViewFilter) {
|
|
for _, f := range filters {
|
|
if nil == f {
|
|
continue
|
|
}
|
|
if f.IsGroup() {
|
|
children := RemoveSelectOptionFromFilters(f.Filters, column, optionContent)
|
|
if 0 < len(children) {
|
|
f.Filters = children
|
|
ret = append(ret, f)
|
|
}
|
|
continue
|
|
}
|
|
if f.Column != column {
|
|
ret = append(ret, f)
|
|
continue
|
|
}
|
|
if nil == f.Value || (KeyTypeSelect != f.Value.Type && KeyTypeMSelect != f.Value.Type) {
|
|
ret = append(ret, f)
|
|
continue
|
|
}
|
|
if FilterOperatorIsEmpty == f.Operator || FilterOperatorIsNotEmpty == f.Operator {
|
|
ret = append(ret, f)
|
|
continue
|
|
}
|
|
for i, opt := range f.Value.MSelect {
|
|
if optionContent == opt.Content {
|
|
f.Value.MSelect = append(f.Value.MSelect[:i], f.Value.MSelect[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
if 1 > len(f.Value.MSelect) {
|
|
continue // 选项删空则移除该过滤条件
|
|
}
|
|
ret = append(ret, f)
|
|
}
|
|
return
|
|
}
|
|
|
|
// RenameSelectOptionInFilters 递归将引用指定列的 select/mSelect 叶子中的旧选项名改为新名与新颜色。
|
|
func RenameSelectOptionInFilters(filters []*ViewFilter, column, oldContent, newContent, newColor string) {
|
|
for _, f := range filters {
|
|
if nil == f {
|
|
continue
|
|
}
|
|
if f.IsGroup() {
|
|
RenameSelectOptionInFilters(f.Filters, column, oldContent, newContent, newColor)
|
|
continue
|
|
}
|
|
if f.Column != column || nil == f.Value {
|
|
continue
|
|
}
|
|
if KeyTypeSelect != f.Value.Type && KeyTypeMSelect != f.Value.Type {
|
|
continue
|
|
}
|
|
for _, opt := range f.Value.MSelect {
|
|
if oldContent == opt.Content {
|
|
opt.Content = newContent
|
|
opt.Color = newColor
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// PruneInvalidColumnFilters 递归移除引用了不存在列的叶子节点,并裁剪变空的分组节点。
|
|
// validColumns 为当前仍存在的列 ID 集合。返回处理后的节点列表及是否有改动。
|
|
func PruneInvalidColumnFilters(filters []*ViewFilter, validColumns map[string]bool) (ret []*ViewFilter, changed bool) {
|
|
for _, f := range filters {
|
|
if nil == f {
|
|
continue
|
|
}
|
|
if f.IsGroup() {
|
|
children, childChanged := PruneInvalidColumnFilters(f.Filters, validColumns)
|
|
if 0 < len(children) {
|
|
f.Filters = children
|
|
ret = append(ret, f)
|
|
} else if 0 < len(f.Filters) {
|
|
// 仅当分组原本有子节点却被裁空时才算改动;原本就是空的分组丢弃不算改动,
|
|
// 否则无筛选条件的视图每次渲染都会误判为已改动而触发保存,干扰数据同步判断
|
|
changed = true // 分组变为空被丢弃
|
|
}
|
|
if childChanged {
|
|
changed = true
|
|
}
|
|
continue
|
|
}
|
|
if validColumns[f.Column] {
|
|
ret = append(ret, f)
|
|
} else {
|
|
changed = true
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (value *Value) Filter(filter *ViewFilter, attrView *AttributeView, itemID string, rollupFurtherCollections map[string]Collection, cachedAttrViews map[string]*AttributeView) bool {
|
|
if nil == filter || (nil == filter.Value && nil == filter.RelativeDate) {
|
|
return true
|
|
}
|
|
|
|
if nil != filter.Value && value.Type != filter.Value.Type {
|
|
// 由于字段类型被用户编辑过导致和过滤规则值类型不匹配,该情况下不过滤
|
|
return true
|
|
}
|
|
|
|
if "" == filter.Qualifier {
|
|
switch filter.Operator {
|
|
case FilterOperatorIsEmpty:
|
|
return value.IsEmpty()
|
|
case FilterOperatorIsNotEmpty:
|
|
return !value.IsEmpty()
|
|
}
|
|
}
|
|
|
|
// 单独处理汇总
|
|
if nil != value.Rollup && KeyTypeRollup == value.Type && nil != filter.Value && KeyTypeRollup == filter.Value.Type && nil != filter.Value.Rollup {
|
|
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, itemID)
|
|
if nil == relVal || nil == relVal.Relation {
|
|
return false
|
|
}
|
|
|
|
destAv := cachedAttrViews[relKey.Relation.AvID]
|
|
if nil == destAv {
|
|
destAv, _ = ParseAttributeView(relKey.Relation.AvID)
|
|
if nil != destAv {
|
|
cachedAttrViews[relKey.Relation.AvID] = destAv
|
|
}
|
|
}
|
|
if nil == destAv {
|
|
return false
|
|
}
|
|
|
|
destKey, _ := destAv.GetKey(key.Rollup.KeyID)
|
|
if nil == destKey {
|
|
return false
|
|
}
|
|
|
|
value.Rollup.BuildContents(destAv.KeyValues, destKey, relVal, key.Rollup.Calc, rollupFurtherCollections[key.ID])
|
|
|
|
switch filter.Qualifier {
|
|
case FilterQuantifierUndefined, FilterQuantifierAny:
|
|
if FilterOperatorIsEmpty == filter.Operator {
|
|
if 1 > len(value.Rollup.Contents) {
|
|
return true
|
|
}
|
|
|
|
if len(value.Rollup.Contents) < len(relVal.Relation.Contents) { // 说明汇总的目标字段存在空值
|
|
return true
|
|
}
|
|
|
|
for _, c := range value.Rollup.Contents {
|
|
if v := c.GetValByType(c.Type); nil == v || reflect.ValueOf(v).IsNil() {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
} else if FilterOperatorIsNotEmpty == filter.Operator {
|
|
if 1 > len(value.Rollup.Contents) {
|
|
return false
|
|
}
|
|
|
|
for _, c := range value.Rollup.Contents {
|
|
if v := c.GetValByType(c.Type); nil != v && !reflect.ValueOf(v).IsNil() {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
if 1 > len(filter.Value.Rollup.Contents) {
|
|
return true
|
|
}
|
|
|
|
if v := filter.Value.GetValByType(filter.Value.Rollup.Contents[0].Type); nil == v || reflect.ValueOf(v).IsNil() {
|
|
return true
|
|
}
|
|
|
|
for _, content := range value.Rollup.Contents {
|
|
if content.filter(filter.Value.Rollup.Contents[0], filter.RelativeDate, filter.RelativeDate2, filter.Operator) {
|
|
return true
|
|
}
|
|
}
|
|
case FilterQuantifierAll:
|
|
if FilterOperatorIsEmpty == filter.Operator {
|
|
if 1 > len(value.Rollup.Contents) {
|
|
return true
|
|
}
|
|
|
|
if len(value.Rollup.Contents) < len(relVal.Relation.Contents) {
|
|
return false
|
|
}
|
|
|
|
for _, c := range value.Rollup.Contents {
|
|
if v := c.GetValByType(c.Type); nil != v && !reflect.ValueOf(v).IsNil() {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
} else if FilterOperatorIsNotEmpty == filter.Operator {
|
|
if 1 > len(value.Rollup.Contents) {
|
|
return false
|
|
}
|
|
|
|
if len(value.Rollup.Contents) < len(relVal.Relation.Contents) {
|
|
return false
|
|
}
|
|
|
|
for _, c := range value.Rollup.Contents {
|
|
if v := c.GetValByType(c.Type); nil == v || reflect.ValueOf(v).IsNil() {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
if 1 > len(filter.Value.Rollup.Contents) {
|
|
return true
|
|
}
|
|
|
|
if v := filter.Value.GetValByType(filter.Value.Rollup.Contents[0].Type); nil == v || reflect.ValueOf(v).IsNil() {
|
|
return true
|
|
}
|
|
|
|
for _, content := range value.Rollup.Contents {
|
|
if !content.filter(filter.Value.Rollup.Contents[0], filter.RelativeDate, filter.RelativeDate2, filter.Operator) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
case FilterQuantifierNone:
|
|
if FilterOperatorIsEmpty == filter.Operator {
|
|
if 1 > len(value.Rollup.Contents) {
|
|
return false
|
|
}
|
|
|
|
if len(value.Rollup.Contents) < len(relVal.Relation.Contents) {
|
|
return true
|
|
}
|
|
|
|
for _, c := range value.Rollup.Contents {
|
|
if v := c.GetValByType(c.Type); nil == v || reflect.ValueOf(v).IsNil() {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
} else if FilterOperatorIsNotEmpty == filter.Operator {
|
|
if 1 > len(value.Rollup.Contents) {
|
|
return true
|
|
}
|
|
|
|
for _, c := range value.Rollup.Contents {
|
|
if v := c.GetValByType(c.Type); nil != v && !reflect.ValueOf(v).IsNil() {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
if 1 > len(filter.Value.Rollup.Contents) {
|
|
return true
|
|
}
|
|
|
|
if v := filter.Value.GetValByType(filter.Value.Rollup.Contents[0].Type); nil == v || reflect.ValueOf(v).IsNil() {
|
|
return true
|
|
}
|
|
|
|
for _, content := range value.Rollup.Contents {
|
|
if content.filter(filter.Value.Rollup.Contents[0], filter.RelativeDate, filter.RelativeDate2, filter.Operator) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
// 单独处理关联
|
|
if nil != value.Relation && KeyTypeRelation == value.Type && nil != filter.Value && KeyTypeRelation == filter.Value.Type && nil != filter.Value.Relation {
|
|
if 1 > len(filter.Value.Relation.BlockIDs) {
|
|
return true
|
|
}
|
|
|
|
for _, relationValue := range value.Relation.Contents {
|
|
filterValue := &Value{Type: KeyTypeBlock, Block: &ValueBlock{Content: filter.Value.Relation.BlockIDs[0]}}
|
|
|
|
switch filter.Operator {
|
|
case FilterOperatorContains:
|
|
if relationValue.filter(filterValue, filter.RelativeDate, filter.RelativeDate2, filter.Operator) {
|
|
return true
|
|
}
|
|
case FilterOperatorDoesNotContain:
|
|
ret := relationValue.filter(filterValue, filter.RelativeDate, filter.RelativeDate2, filter.Operator)
|
|
if !ret {
|
|
return false
|
|
}
|
|
default:
|
|
if relationValue.filter(filterValue, filter.RelativeDate, filter.RelativeDate2, filter.Operator) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
switch filter.Operator {
|
|
case FilterOperatorContains:
|
|
return false
|
|
case FilterOperatorDoesNotContain:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// 单独处理资源
|
|
if nil != value.MAsset && KeyTypeMAsset == value.Type && nil != filter.Value && KeyTypeMAsset == filter.Value.Type {
|
|
key, _ := attrView.GetKey(value.KeyID)
|
|
if nil == key {
|
|
return false
|
|
}
|
|
|
|
var filterContent string
|
|
if 1 <= len(filter.Value.MAsset) {
|
|
filterContent = filter.Value.MAsset[0].Content
|
|
}
|
|
|
|
switch filter.Qualifier {
|
|
case FilterQuantifierUndefined, FilterQuantifierAny:
|
|
if FilterOperatorIsEmpty == filter.Operator {
|
|
if 1 > len(value.MAsset) {
|
|
return true
|
|
}
|
|
|
|
for _, asset := range value.MAsset {
|
|
if "" == strings.TrimSpace(asset.Name) && "" == strings.TrimSpace(asset.Content) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
} else if FilterOperatorIsNotEmpty == filter.Operator {
|
|
if 1 > len(value.MAsset) {
|
|
return false
|
|
}
|
|
|
|
for _, asset := range value.MAsset {
|
|
if "" != strings.TrimSpace(asset.Name) || "" != strings.TrimSpace(asset.Content) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
if nil == filter.Value || 1 > len(filter.Value.MAsset) {
|
|
return true
|
|
}
|
|
|
|
for _, asset := range value.MAsset {
|
|
switch asset.Type {
|
|
case AssetTypeFile:
|
|
if filterTextContent(filter.Operator, asset.Name, filterContent) ||
|
|
filterTextContent(filter.Operator, asset.Content, filterContent) {
|
|
return true
|
|
}
|
|
case AssetTypeImage:
|
|
if filterTextContent(filter.Operator, asset.Content, filterContent) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
case FilterQuantifierAll:
|
|
if FilterOperatorIsEmpty == filter.Operator {
|
|
if 1 > len(value.MAsset) {
|
|
return true
|
|
}
|
|
|
|
for _, asset := range value.MAsset {
|
|
if "" != strings.TrimSpace(asset.Name) || "" != strings.TrimSpace(asset.Content) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
} else if FilterOperatorIsNotEmpty == filter.Operator {
|
|
if 1 > len(value.MAsset) {
|
|
return false
|
|
}
|
|
|
|
for _, asset := range value.MAsset {
|
|
if "" == strings.TrimSpace(asset.Name) && "" == strings.TrimSpace(asset.Content) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
if nil == filter.Value || 1 > len(filter.Value.MAsset) {
|
|
return true
|
|
}
|
|
|
|
for _, asset := range value.MAsset {
|
|
switch asset.Type {
|
|
case AssetTypeFile:
|
|
if !filterTextContent(filter.Operator, asset.Name, filterContent) &&
|
|
!filterTextContent(filter.Operator, asset.Content, filterContent) {
|
|
return false
|
|
}
|
|
case AssetTypeImage:
|
|
if !filterTextContent(filter.Operator, asset.Content, filterContent) {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
case FilterQuantifierNone:
|
|
if FilterOperatorIsEmpty == filter.Operator {
|
|
if 1 > len(value.MAsset) {
|
|
return false
|
|
}
|
|
|
|
for _, asset := range value.MAsset {
|
|
if "" == strings.TrimSpace(asset.Name) && "" == strings.TrimSpace(asset.Content) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
} else if FilterOperatorIsNotEmpty == filter.Operator {
|
|
if 1 > len(value.MAsset) {
|
|
return true
|
|
}
|
|
|
|
for _, asset := range value.MAsset {
|
|
if "" != strings.TrimSpace(asset.Name) || "" != strings.TrimSpace(asset.Content) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
if nil == filter.Value || 1 > len(filter.Value.MAsset) {
|
|
return true
|
|
}
|
|
|
|
for _, asset := range value.MAsset {
|
|
switch asset.Type {
|
|
case AssetTypeFile:
|
|
if filterTextContent(filter.Operator, asset.Name, filterContent) {
|
|
return false
|
|
}
|
|
if filterTextContent(filter.Operator, asset.Content, filterContent) {
|
|
return false
|
|
}
|
|
case AssetTypeImage:
|
|
if filterTextContent(filter.Operator, asset.Content, filterContent) {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
return value.filter(filter.Value, filter.RelativeDate, filter.RelativeDate2, filter.Operator)
|
|
}
|
|
|
|
func (value *Value) filter(other *Value, relativeDate, relativeDate2 *RelativeDate, operator FilterOperator) bool {
|
|
switch operator {
|
|
case FilterOperatorIsEmpty:
|
|
return value.IsEmpty()
|
|
case FilterOperatorIsNotEmpty:
|
|
return !value.IsEmpty()
|
|
}
|
|
|
|
switch value.Type {
|
|
case KeyTypeBlock:
|
|
if nil != value.Block && nil != other && nil != other.Block {
|
|
return filterTextContent(operator, value.Block.Content, other.Block.Content)
|
|
}
|
|
case KeyTypeText:
|
|
if nil != value.Text && nil != other && nil != other.Text {
|
|
return filterTextContent(operator, value.Text.Content, other.Text.Content)
|
|
}
|
|
case KeyTypeNumber:
|
|
if nil != value.Number && nil != other && nil != other.Number {
|
|
if !other.Number.IsNotEmpty {
|
|
return true
|
|
}
|
|
|
|
switch operator {
|
|
case FilterOperatorIsEqual:
|
|
return value.Number.Content == other.Number.Content && value.Number.IsNotEmpty == other.Number.IsNotEmpty
|
|
case FilterOperatorIsNotEqual:
|
|
return value.Number.Content != other.Number.Content || value.Number.IsNotEmpty != other.Number.IsNotEmpty
|
|
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 KeyTypeDate:
|
|
if nil != value.Date && nil != other && nil != other.Date && nil == relativeDate && !other.Date.IsNotEmpty {
|
|
return true
|
|
}
|
|
|
|
if nil != value.Date {
|
|
if !value.Date.IsNotEmpty {
|
|
// 空值不进行比较,直接排除
|
|
// Database date filter excludes empty values https://github.com/siyuan-note/siyuan/issues/11061
|
|
return false
|
|
}
|
|
|
|
if nil != relativeDate { // 使用相对时间比较
|
|
// 非 Is between 时前端不会下发 relativeDate2,为 nil 时复用第一段作为安全默认,避免空指针
|
|
secondRelativeDate := relativeDate2
|
|
if nil == secondRelativeDate {
|
|
secondRelativeDate = relativeDate
|
|
}
|
|
relativeTimeStart, relativeTimeEnd := calcRelativeTimeRegion(relativeDate.Count, relativeDate.Unit, relativeDate.Direction)
|
|
relativeTimeStart2, relativeTimeEnd2 := calcRelativeTimeRegion(secondRelativeDate.Count, secondRelativeDate.Unit, secondRelativeDate.Direction)
|
|
return filterRelativeTime(value.Date.Content, value.Date.IsNotEmpty, operator, relativeTimeStart, relativeTimeEnd, relativeDate.Direction, relativeTimeStart2, relativeTimeEnd2, secondRelativeDate.Direction)
|
|
}
|
|
// 使用具体时间比较
|
|
if nil == other.Date {
|
|
return true
|
|
}
|
|
return filterTime(value.Date.Content, value.Date.IsNotEmpty, other.Date.Content, other.Date.Content2, operator)
|
|
}
|
|
case KeyTypeCreated:
|
|
if nil != value.Created {
|
|
if nil != relativeDate { // 使用相对时间比较
|
|
// 非 Is between 时前端不会下发 relativeDate2,为 nil 时复用第一段作为安全默认,避免空指针
|
|
secondRelativeDate := relativeDate2
|
|
if nil == secondRelativeDate {
|
|
secondRelativeDate = relativeDate
|
|
}
|
|
relativeTimeStart, relativeTimeEnd := calcRelativeTimeRegion(relativeDate.Count, relativeDate.Unit, relativeDate.Direction)
|
|
relativeTimeStart2, relativeTimeEnd2 := calcRelativeTimeRegion(secondRelativeDate.Count, secondRelativeDate.Unit, secondRelativeDate.Direction)
|
|
return filterRelativeTime(value.Created.Content, true, operator, relativeTimeStart, relativeTimeEnd, relativeDate.Direction, relativeTimeStart2, relativeTimeEnd2, secondRelativeDate.Direction)
|
|
}
|
|
// 使用具体时间比较
|
|
if nil == other.Created {
|
|
return true
|
|
}
|
|
return filterTime(value.Created.Content, value.Created.IsNotEmpty, other.Created.Content, other.Created.Content2, operator)
|
|
}
|
|
case KeyTypeUpdated:
|
|
if nil != value.Updated {
|
|
if nil != relativeDate { // 使用相对时间比较
|
|
// 非 Is between 时前端不会下发 relativeDate2,为 nil 时复用第一段作为安全默认,避免空指针
|
|
secondRelativeDate := relativeDate2
|
|
if nil == secondRelativeDate {
|
|
secondRelativeDate = relativeDate
|
|
}
|
|
relativeTimeStart, relativeTimeEnd := calcRelativeTimeRegion(relativeDate.Count, relativeDate.Unit, relativeDate.Direction)
|
|
relativeTimeStart2, relativeTimeEnd2 := calcRelativeTimeRegion(secondRelativeDate.Count, secondRelativeDate.Unit, secondRelativeDate.Direction)
|
|
return filterRelativeTime(value.Updated.Content, true, operator, relativeTimeStart, relativeTimeEnd, relativeDate.Direction, relativeTimeStart2, relativeTimeEnd2, secondRelativeDate.Direction)
|
|
}
|
|
// 使用具体时间比较
|
|
if nil == other.Updated {
|
|
return true
|
|
}
|
|
|
|
return filterTime(value.Updated.Content, value.Updated.IsNotEmpty, other.Updated.Content, other.Updated.Content2, operator)
|
|
}
|
|
case KeyTypeSelect, KeyTypeMSelect:
|
|
if nil != value.MSelect {
|
|
if 1 > len(other.MSelect) {
|
|
return true
|
|
}
|
|
|
|
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 KeyTypeURL:
|
|
if nil != value.URL && nil != other && nil != other.URL {
|
|
return filterTextContent(operator, value.URL.Content, other.URL.Content)
|
|
}
|
|
case KeyTypeEmail:
|
|
if nil != value.Email && nil != other && nil != other.Email {
|
|
return filterTextContent(operator, value.Email.Content, other.Email.Content)
|
|
}
|
|
case KeyTypePhone:
|
|
if nil != value.Phone && nil != other && nil != other.Phone {
|
|
return filterTextContent(operator, value.Phone.Content, other.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 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 KeyTypeCheckbox:
|
|
if nil != value.Checkbox {
|
|
switch operator {
|
|
case FilterOperatorIsTrue, FilterOperatorIsEqual:
|
|
if nil != other && nil != other.Checkbox {
|
|
return value.Checkbox.Checked == other.Checkbox.Checked
|
|
}
|
|
return value.Checkbox.Checked
|
|
case FilterOperatorIsFalse:
|
|
return !value.Checkbox.Checked
|
|
case FilterOperatorIsNotEqual:
|
|
if nil != other && nil != other.Checkbox {
|
|
return value.Checkbox.Checked != other.Checkbox.Checked
|
|
}
|
|
return !value.Checkbox.Checked
|
|
}
|
|
}
|
|
case KeyTypeRelation: // 过滤汇总字段,并且汇总目标是关联字段时才会进入该分支
|
|
if nil != value.Relation && 0 < len(value.Relation.Contents) && nil != value.Relation.Contents[0].Block &&
|
|
nil != other && nil != other.Relation && 0 < len(other.Relation.BlockIDs) {
|
|
filterValue := &Value{Type: KeyTypeBlock, Block: &ValueBlock{Content: other.Relation.BlockIDs[0]}}
|
|
return filterTextContent(operator, value.Relation.Contents[0].Block.Content, filterValue.Block.Content)
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func filterTextContent(operator FilterOperator, valueContent, otherValueContent string) bool {
|
|
switch operator {
|
|
case FilterOperatorIsEqual:
|
|
if "" == strings.TrimSpace(otherValueContent) {
|
|
return true
|
|
}
|
|
if util.SearchCaseSensitive {
|
|
return valueContent == otherValueContent
|
|
}
|
|
return strings.EqualFold(valueContent, otherValueContent)
|
|
case FilterOperatorIsNotEqual:
|
|
if "" == strings.TrimSpace(otherValueContent) {
|
|
return true
|
|
}
|
|
if util.SearchCaseSensitive {
|
|
return valueContent != otherValueContent
|
|
}
|
|
return !strings.EqualFold(valueContent, otherValueContent)
|
|
case FilterOperatorContains:
|
|
if "" == strings.TrimSpace(otherValueContent) {
|
|
return true
|
|
}
|
|
if util.SearchCaseSensitive {
|
|
return strings.Contains(valueContent, otherValueContent)
|
|
}
|
|
return strings.Contains(strings.ToLower(valueContent), strings.ToLower(otherValueContent))
|
|
case FilterOperatorDoesNotContain:
|
|
if "" == strings.TrimSpace(otherValueContent) {
|
|
return true
|
|
}
|
|
if util.SearchCaseSensitive {
|
|
return !strings.Contains(valueContent, otherValueContent)
|
|
}
|
|
return !strings.Contains(strings.ToLower(valueContent), strings.ToLower(otherValueContent))
|
|
case FilterOperatorStartsWith:
|
|
if "" == strings.TrimSpace(otherValueContent) {
|
|
return true
|
|
}
|
|
if util.SearchCaseSensitive {
|
|
return strings.HasPrefix(valueContent, otherValueContent)
|
|
}
|
|
return strings.HasPrefix(strings.ToLower(valueContent), strings.ToLower(otherValueContent))
|
|
case FilterOperatorEndsWith:
|
|
if "" == strings.TrimSpace(otherValueContent) {
|
|
return true
|
|
}
|
|
if util.SearchCaseSensitive {
|
|
return strings.HasSuffix(valueContent, otherValueContent)
|
|
}
|
|
return strings.HasSuffix(strings.ToLower(valueContent), strings.ToLower(otherValueContent))
|
|
case FilterOperatorIsEmpty:
|
|
return "" == strings.TrimSpace(valueContent)
|
|
case FilterOperatorIsNotEmpty:
|
|
return "" != strings.TrimSpace(valueContent)
|
|
}
|
|
return false
|
|
}
|
|
|
|
func filterRelativeTime(valueMills int64, valueIsNotEmpty bool, operator FilterOperator, otherValueStart, otherValueEnd time.Time, direction RelativeDateDirection, otherValueStart2, otherValueEnd2 time.Time, direction2 RelativeDateDirection) bool {
|
|
valueTime := time.UnixMilli(valueMills)
|
|
|
|
if otherValueStart.After(otherValueStart2) && FilterOperatorIsBetween == operator {
|
|
tmpStart, tmpEnd := otherValueStart2, otherValueEnd2
|
|
otherValueStart2, otherValueEnd2 = otherValueStart, otherValueEnd
|
|
otherValueStart, otherValueEnd = tmpStart, tmpEnd
|
|
}
|
|
|
|
switch operator {
|
|
case FilterOperatorIsEqual:
|
|
return (valueTime.After(otherValueStart) || valueTime.Equal(otherValueStart)) && (valueTime.Before(otherValueEnd) || valueTime.Equal(otherValueEnd))
|
|
case FilterOperatorIsNotEqual:
|
|
return valueTime.Before(otherValueStart) || valueTime.After(otherValueEnd)
|
|
case FilterOperatorIsGreater:
|
|
return valueTime.After(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:
|
|
if RelativeDateDirectionBefore == direction {
|
|
if RelativeDateDirectionBefore == direction2 {
|
|
var leftStart, rightEnd time.Time
|
|
if otherValueStart.Before(otherValueStart2) {
|
|
leftStart = otherValueStart
|
|
} else {
|
|
leftStart = otherValueStart2
|
|
}
|
|
if otherValueEnd.Before(otherValueStart2) {
|
|
rightEnd = otherValueEnd
|
|
} else {
|
|
rightEnd = otherValueStart2
|
|
}
|
|
return (valueTime.After(leftStart) || valueTime.Equal(leftStart)) && (valueTime.Before(rightEnd) || valueTime.Equal(rightEnd))
|
|
} else if RelativeDateDirectionThis == direction2 {
|
|
return ((valueTime.After(otherValueStart) || valueTime.Equal(otherValueStart)) && (valueTime.Before(otherValueEnd) || valueTime.Equal(otherValueEnd))) ||
|
|
((valueTime.After(otherValueStart2) || valueTime.Equal(otherValueStart2)) && (valueTime.Before(otherValueEnd2) || valueTime.Equal(otherValueEnd2)))
|
|
} else if RelativeDateDirectionAfter == direction2 {
|
|
var leftStart, rightEnd time.Time
|
|
if otherValueStart.Before(otherValueStart2) {
|
|
leftStart = otherValueStart
|
|
} else {
|
|
leftStart = otherValueStart2
|
|
}
|
|
if otherValueEnd.Before(otherValueEnd2) {
|
|
rightEnd = otherValueEnd2
|
|
} else {
|
|
rightEnd = otherValueEnd
|
|
}
|
|
return (valueTime.After(leftStart) || valueTime.Equal(leftStart)) && (valueTime.Before(rightEnd) || valueTime.Equal(rightEnd))
|
|
}
|
|
} else if RelativeDateDirectionThis == direction {
|
|
return ((valueTime.After(otherValueStart) || valueTime.Equal(otherValueStart)) && (valueTime.Before(otherValueEnd) || valueTime.Equal(otherValueEnd))) ||
|
|
((valueTime.After(otherValueStart2) || valueTime.Equal(otherValueStart2)) && (valueTime.Before(otherValueEnd2) || valueTime.Equal(otherValueEnd2)))
|
|
} else if RelativeDateDirectionAfter == direction {
|
|
if RelativeDateDirectionBefore == direction2 {
|
|
var leftStart, rightEnd time.Time
|
|
if otherValueStart.Before(otherValueStart2) {
|
|
leftStart = otherValueStart
|
|
} else {
|
|
leftStart = otherValueStart2
|
|
}
|
|
if otherValueEnd.Before(otherValueEnd2) {
|
|
rightEnd = otherValueEnd2
|
|
} else {
|
|
rightEnd = otherValueEnd
|
|
}
|
|
return (valueTime.After(leftStart) || valueTime.Equal(leftStart)) && (valueTime.Before(rightEnd) || valueTime.Equal(rightEnd))
|
|
} else if RelativeDateDirectionThis == direction2 {
|
|
return ((valueTime.After(otherValueStart) || valueTime.Equal(otherValueStart)) && (valueTime.Before(otherValueEnd) || valueTime.Equal(otherValueEnd))) ||
|
|
((valueTime.After(otherValueStart2) || valueTime.Equal(otherValueStart2)) && (valueTime.Before(otherValueEnd2) || valueTime.Equal(otherValueEnd2)))
|
|
} else if RelativeDateDirectionAfter == direction2 {
|
|
var leftStart, rightEnd time.Time
|
|
if otherValueStart.Before(otherValueStart2) {
|
|
leftStart = otherValueStart
|
|
} else {
|
|
leftStart = otherValueStart2
|
|
}
|
|
if otherValueEnd.After(otherValueEnd2) {
|
|
rightEnd = otherValueEnd
|
|
} else {
|
|
rightEnd = otherValueEnd2
|
|
}
|
|
return (valueTime.After(leftStart) || valueTime.Equal(leftStart)) && (valueTime.Before(rightEnd) || valueTime.Equal(rightEnd))
|
|
}
|
|
}
|
|
return false
|
|
case FilterOperatorIsEmpty:
|
|
return !valueIsNotEmpty
|
|
case FilterOperatorIsNotEmpty:
|
|
return valueIsNotEmpty
|
|
}
|
|
return false
|
|
}
|
|
|
|
func filterTime(valueMills int64, valueIsNotEmpty bool, otherValueMills, otherValueMills2 int64, operator FilterOperator) bool {
|
|
valueTime := time.UnixMilli(valueMills)
|
|
|
|
if 0 != otherValueMills2 && otherValueMills > otherValueMills2 && FilterOperatorIsBetween == operator {
|
|
tmp := otherValueMills2
|
|
otherValueMills2 = otherValueMills
|
|
otherValueMills = tmp
|
|
}
|
|
|
|
otherValueTime := time.UnixMilli(otherValueMills)
|
|
otherValueStart := time.Date(otherValueTime.Year(), otherValueTime.Month(), otherValueTime.Day(), 0, 0, 0, 0, otherValueTime.Location())
|
|
otherValueEnd := time.Date(otherValueTime.Year(), otherValueTime.Month(), otherValueTime.Day(), 23, 59, 59, 999999999, otherValueTime.Location())
|
|
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:
|
|
if 0 == otherValueMills || 0 == otherValueMills2 {
|
|
return true
|
|
}
|
|
otherValueTime2 := time.UnixMilli(otherValueMills2)
|
|
otherValueEnd2 := time.Date(otherValueTime2.Year(), otherValueTime2.Month(), otherValueTime2.Day(), 23, 59, 59, 999999999, otherValueTime2.Location())
|
|
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:
|
|
// 结束时间:今天的 0 点
|
|
end = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
|
// 开始时间:结束时间减去 count 天
|
|
start = end.AddDate(0, 0, -count)
|
|
case RelativeDateDirectionThis:
|
|
// 开始时间:今天的 0 点
|
|
start = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
|
// 结束时间:今天的 23:59:59.999999999
|
|
end = time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 999999999, now.Location())
|
|
case RelativeDateDirectionAfter:
|
|
// 开始时间:今天的 0 点加上 count 天
|
|
start = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).AddDate(0, 0, count)
|
|
// 结束时间:开始时间的 23:59:59.999999999
|
|
end = time.Date(start.Year(), start.Month(), start.Day(), 23, 59, 59, 999999999, now.Location())
|
|
}
|
|
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+1, 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+1, 0, 0, 0, 0, now.Location())
|
|
// 结束时间:本周的周日
|
|
end = time.Date(now.Year(), now.Month(), now.Day()-weekday+7, 23, 59, 59, 999999999, now.Location())
|
|
case RelativeDateDirectionAfter:
|
|
// 开始时间:本周的周一加上 count*7 天
|
|
start = time.Date(now.Year(), now.Month(), now.Day()-weekday+1, 0, 0, 0, 0, now.Location()).AddDate(0, 0, count*7)
|
|
// 结束时间:开始时间的周日
|
|
end = time.Date(start.Year(), start.Month(), start.Day()-int(start.Weekday())+7, 23, 59, 59, 999999999, now.Location())
|
|
}
|
|
case RelativeDateUnitMonth:
|
|
switch direction {
|
|
case RelativeDateDirectionBefore:
|
|
// 结束时间:本月的 1 号
|
|
end = time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
|
|
// 开始时间:结束时间减去 count 个月
|
|
start = end.AddDate(0, -count, 0)
|
|
case RelativeDateDirectionThis:
|
|
// 开始时间:本月的 1 号
|
|
start = time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
|
|
// 结束时间:下个月的 1 号减去 1 纳秒
|
|
end = time.Date(now.Year(), now.Month()+1, 1, 0, 0, 0, 0, now.Location()).Add(-time.Nanosecond)
|
|
case RelativeDateDirectionAfter:
|
|
// 开始时间:count 个月后的 1 号
|
|
start = time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()).AddDate(0, count, 0)
|
|
// 结束时间:开始时间的下个月的 1 号减去 1 纳秒
|
|
end = time.Date(start.Year(), start.Month()+1, 1, 0, 0, 0, 0, now.Location()).Add(-time.Nanosecond)
|
|
}
|
|
case RelativeDateUnitYear:
|
|
switch direction {
|
|
case RelativeDateDirectionBefore:
|
|
// 结束时间:今年的 1 月 1 号
|
|
end = time.Date(now.Year(), 1, 1, 0, 0, 0, 0, now.Location())
|
|
// 开始时间:结束时间减去 count 年
|
|
start = end.AddDate(-count, 0, 0)
|
|
case RelativeDateDirectionThis:
|
|
// 开始时间:今年的 1 月 1 号
|
|
start = time.Date(now.Year(), 1, 1, 0, 0, 0, 0, now.Location())
|
|
// 结束时间:明年的 1 月 1 号减去 1 纳秒
|
|
end = time.Date(now.Year()+1, 1, 1, 0, 0, 0, 0, now.Location()).Add(-time.Nanosecond)
|
|
case RelativeDateDirectionAfter:
|
|
// 开始时间:count 年后的 1 月 1 号
|
|
start = time.Date(now.Year(), 1, 1, 0, 0, 0, 0, now.Location()).AddDate(count, 0, 0)
|
|
// 结束时间:开始时间的 count+1 年的 1 月 1 号减去 1 纳秒
|
|
end = time.Date(start.Year()+1, 1, 1, 0, 0, 0, 0, now.Location()).Add(-time.Nanosecond)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// IsValid 判断叶子过滤节点是否可参与「新增行默认值」计算。仅对叶子节点调用;
|
|
// 分组节点 filter.Value 为 nil 会返回 false(视为不可参与),调用方应先递归到叶子。
|
|
func (filter *ViewFilter) IsValid() bool {
|
|
if nil == filter || nil == filter.Value {
|
|
return false
|
|
}
|
|
|
|
if FilterOperatorIsEmpty != filter.Operator && FilterOperatorIsNotEmpty != filter.Operator {
|
|
if filter.Value.IsEmpty() && nil == filter.RelativeDate {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (filter *ViewFilter) GetAffectValue(key *Key, addingBlockID string) (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 {
|
|
// 所有生成的数据都不设置默认值
|
|
return nil
|
|
}
|
|
}
|
|
|
|
if nil == filter.Value {
|
|
return nil
|
|
}
|
|
|
|
if FilterOperatorIsEmpty != filter.Operator && FilterOperatorIsNotEmpty != filter.Operator {
|
|
if filter.Value.IsEmpty() && nil == filter.RelativeDate {
|
|
// 在不是过滤空值和非空值的情况下,空值不设置默认值 https://github.com/siyuan-note/siyuan/issues/11297
|
|
return nil
|
|
}
|
|
}
|
|
|
|
ret = filter.Value.Clone()
|
|
ret.ID = ast.NewNodeID()
|
|
ret.KeyID = key.ID
|
|
ret.BlockID = addingBlockID
|
|
ret.CreatedAt = util.CurrentTimeMillis()
|
|
ret.UpdatedAt = ret.CreatedAt + 1000
|
|
|
|
switch filter.Value.Type {
|
|
case KeyTypeBlock:
|
|
switch filter.Operator {
|
|
case FilterOperatorIsEqual:
|
|
ret.Block = &ValueBlock{Content: filter.Value.Block.Content, Created: ret.CreatedAt, Updated: ret.UpdatedAt}
|
|
case FilterOperatorIsNotEqual:
|
|
ret.Block = &ValueBlock{Content: "", Created: ret.CreatedAt, Updated: ret.UpdatedAt}
|
|
case FilterOperatorContains:
|
|
ret.Block = &ValueBlock{Content: filter.Value.Block.Content, Created: ret.CreatedAt, Updated: ret.UpdatedAt}
|
|
case FilterOperatorDoesNotContain:
|
|
ret.Block = &ValueBlock{Content: "", Created: ret.CreatedAt, Updated: ret.UpdatedAt}
|
|
case FilterOperatorStartsWith:
|
|
ret.Block = &ValueBlock{Content: filter.Value.Block.Content, Created: ret.CreatedAt, Updated: ret.UpdatedAt}
|
|
case FilterOperatorEndsWith:
|
|
ret.Block = &ValueBlock{Content: filter.Value.Block.Content, Created: ret.CreatedAt, Updated: ret.UpdatedAt}
|
|
case FilterOperatorIsEmpty:
|
|
ret.Block = &ValueBlock{Content: "", Created: ret.CreatedAt, Updated: ret.UpdatedAt}
|
|
case FilterOperatorIsNotEmpty:
|
|
return nil
|
|
}
|
|
case KeyTypeText:
|
|
switch filter.Operator {
|
|
case FilterOperatorIsEqual:
|
|
ret.Text = &ValueText{Content: filter.Value.Text.Content}
|
|
case FilterOperatorIsNotEqual:
|
|
ret.Text = &ValueText{Content: ""}
|
|
case FilterOperatorContains:
|
|
ret.Text = &ValueText{Content: filter.Value.Text.Content}
|
|
case FilterOperatorDoesNotContain:
|
|
ret.Text = &ValueText{Content: ""}
|
|
case FilterOperatorStartsWith:
|
|
ret.Text = &ValueText{Content: filter.Value.Text.Content}
|
|
case FilterOperatorEndsWith:
|
|
ret.Text = &ValueText{Content: filter.Value.Text.Content}
|
|
case FilterOperatorIsEmpty:
|
|
ret.Text = &ValueText{Content: ""}
|
|
case FilterOperatorIsNotEmpty:
|
|
return nil
|
|
}
|
|
case KeyTypeNumber:
|
|
switch filter.Operator {
|
|
case FilterOperatorIsEqual:
|
|
ret.Number = &ValueNumber{Content: filter.Value.Number.Content, IsNotEmpty: true}
|
|
case FilterOperatorIsNotEqual:
|
|
ret.Number = &ValueNumber{Content: 0, IsNotEmpty: false}
|
|
case FilterOperatorIsGreater:
|
|
ret.Number = &ValueNumber{Content: filter.Value.Number.Content + 1, IsNotEmpty: true}
|
|
case FilterOperatorIsGreaterOrEqual:
|
|
ret.Number = &ValueNumber{Content: filter.Value.Number.Content, IsNotEmpty: true}
|
|
case FilterOperatorIsLess:
|
|
ret.Number = &ValueNumber{Content: filter.Value.Number.Content - 1, IsNotEmpty: true}
|
|
case FilterOperatorIsLessOrEqual:
|
|
ret.Number = &ValueNumber{Content: filter.Value.Number.Content, IsNotEmpty: true}
|
|
case FilterOperatorIsEmpty:
|
|
ret.Number = &ValueNumber{Content: 0, IsNotEmpty: false}
|
|
case FilterOperatorIsNotEmpty:
|
|
ret.Number = &ValueNumber{Content: 0, IsNotEmpty: true}
|
|
}
|
|
case KeyTypeDate:
|
|
start := time.Now()
|
|
end := start
|
|
if nil != filter.Value.Date {
|
|
start = time.UnixMilli(filter.Value.Date.Content)
|
|
end = time.UnixMilli(filter.Value.Date.Content2)
|
|
}
|
|
if nil != filter.RelativeDate {
|
|
start, end = calcRelativeTimeRegion(filter.RelativeDate.Count, filter.RelativeDate.Unit, filter.RelativeDate.Direction)
|
|
}
|
|
|
|
switch filter.Operator {
|
|
case FilterOperatorIsEqual:
|
|
ret.Date = &ValueDate{Content: start.UnixMilli(), IsNotEmpty: true}
|
|
case FilterOperatorIsGreater:
|
|
ret.Date = &ValueDate{Content: end.Add(24 * time.Hour).UnixMilli(), IsNotEmpty: true}
|
|
case FilterOperatorIsGreaterOrEqual:
|
|
ret.Date = &ValueDate{Content: start.UnixMilli(), IsNotEmpty: true}
|
|
case FilterOperatorIsLess:
|
|
ret.Date = &ValueDate{Content: start.Add(-24 * time.Hour).UnixMilli(), IsNotEmpty: true}
|
|
case FilterOperatorIsLessOrEqual:
|
|
ret.Date = &ValueDate{Content: start.UnixMilli(), IsNotEmpty: true}
|
|
case FilterOperatorIsBetween:
|
|
start2, end2 := start, end
|
|
if nil != filter.RelativeDate2 {
|
|
start2, end2 = calcRelativeTimeRegion(filter.RelativeDate2.Count, filter.RelativeDate2.Unit, filter.RelativeDate2.Direction)
|
|
if start.After(start2) {
|
|
tmp := start
|
|
start = start2
|
|
start2 = tmp
|
|
}
|
|
if end.Before(end2) {
|
|
tmp := end
|
|
end = end2
|
|
end2 = tmp
|
|
}
|
|
} else {
|
|
if start.After(end) {
|
|
tmp := start
|
|
start = end
|
|
end = tmp
|
|
}
|
|
}
|
|
|
|
now := time.Now()
|
|
if start.Before(now) && now.Before(end) {
|
|
ret.Date = &ValueDate{Content: now.UnixMilli(), IsNotEmpty: true}
|
|
return
|
|
}
|
|
ret.Date = &ValueDate{Content: start.UnixMilli(), IsNotEmpty: true}
|
|
case FilterOperatorIsEmpty:
|
|
ret.Date = &ValueDate{Content: 0, IsNotEmpty: false}
|
|
case FilterOperatorIsNotEmpty:
|
|
ret.Date = &ValueDate{Content: util.CurrentTimeMillis(), IsNotEmpty: true}
|
|
}
|
|
case KeyTypeSelect, KeyTypeMSelect:
|
|
switch filter.Operator {
|
|
case FilterOperatorIsEqual:
|
|
valueSelect := &ValueSelect{Content: "", Color: "1"}
|
|
if 0 < len(key.Options) {
|
|
valueSelect.Color = key.Options[0].Color
|
|
}
|
|
if 0 < len(filter.Value.MSelect) {
|
|
valueSelect.Content = filter.Value.MSelect[0].Content
|
|
valueSelect.Color = filter.Value.MSelect[0].Color
|
|
}
|
|
ret.MSelect = []*ValueSelect{valueSelect}
|
|
case FilterOperatorIsNotEqual:
|
|
return nil
|
|
case FilterOperatorContains:
|
|
if 0 < len(filter.Value.MSelect) {
|
|
ret.MSelect = []*ValueSelect{{Content: filter.Value.MSelect[0].Content, Color: filter.Value.MSelect[0].Color}}
|
|
}
|
|
case FilterOperatorDoesNotContain:
|
|
return nil
|
|
case FilterOperatorIsEmpty:
|
|
ret.MSelect = []*ValueSelect{}
|
|
case FilterOperatorIsNotEmpty:
|
|
if 0 < len(key.Options) {
|
|
ret.MSelect = []*ValueSelect{{Content: key.Options[0].Name, Color: key.Options[0].Color}}
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
case KeyTypeURL:
|
|
switch filter.Operator {
|
|
case FilterOperatorIsEqual:
|
|
ret.URL = &ValueURL{Content: filter.Value.URL.Content}
|
|
case FilterOperatorIsNotEqual:
|
|
ret.URL = &ValueURL{Content: ""}
|
|
case FilterOperatorContains:
|
|
ret.URL = &ValueURL{Content: filter.Value.URL.Content}
|
|
case FilterOperatorDoesNotContain:
|
|
ret.URL = &ValueURL{Content: ""}
|
|
case FilterOperatorStartsWith:
|
|
ret.URL = &ValueURL{Content: filter.Value.URL.Content}
|
|
case FilterOperatorEndsWith:
|
|
ret.URL = &ValueURL{Content: filter.Value.URL.Content}
|
|
case FilterOperatorIsEmpty:
|
|
ret.URL = &ValueURL{Content: ""}
|
|
case FilterOperatorIsNotEmpty:
|
|
return nil
|
|
}
|
|
case KeyTypeEmail:
|
|
switch filter.Operator {
|
|
case FilterOperatorIsEqual:
|
|
ret.Email = &ValueEmail{Content: filter.Value.Email.Content}
|
|
case FilterOperatorIsNotEqual:
|
|
ret.Email = &ValueEmail{Content: ""}
|
|
case FilterOperatorContains:
|
|
ret.Email = &ValueEmail{Content: filter.Value.Email.Content}
|
|
case FilterOperatorDoesNotContain:
|
|
ret.Email = &ValueEmail{Content: ""}
|
|
case FilterOperatorStartsWith:
|
|
ret.Email = &ValueEmail{Content: filter.Value.Email.Content}
|
|
case FilterOperatorEndsWith:
|
|
ret.Email = &ValueEmail{Content: filter.Value.Email.Content}
|
|
case FilterOperatorIsEmpty:
|
|
ret.Email = &ValueEmail{Content: ""}
|
|
case FilterOperatorIsNotEmpty:
|
|
return nil
|
|
}
|
|
case KeyTypePhone:
|
|
switch filter.Operator {
|
|
case FilterOperatorIsEqual:
|
|
ret.Phone = &ValuePhone{Content: filter.Value.Phone.Content}
|
|
case FilterOperatorIsNotEqual:
|
|
ret.Phone = &ValuePhone{Content: ""}
|
|
case FilterOperatorContains:
|
|
ret.Phone = &ValuePhone{Content: filter.Value.Phone.Content}
|
|
case FilterOperatorDoesNotContain:
|
|
ret.Phone = &ValuePhone{Content: ""}
|
|
case FilterOperatorStartsWith:
|
|
ret.Phone = &ValuePhone{Content: filter.Value.Phone.Content}
|
|
case FilterOperatorEndsWith:
|
|
ret.Phone = &ValuePhone{Content: filter.Value.Phone.Content}
|
|
case FilterOperatorIsEmpty:
|
|
ret.Phone = &ValuePhone{Content: ""}
|
|
case FilterOperatorIsNotEmpty:
|
|
return nil
|
|
}
|
|
case KeyTypeMAsset:
|
|
switch filter.Operator {
|
|
case FilterOperatorIsEqual, FilterOperatorContains:
|
|
if 0 < len(filter.Value.MAsset) {
|
|
ret.MAsset = []*ValueAsset{{Type: filter.Value.MAsset[0].Type, Name: filter.Value.MAsset[0].Name, Content: filter.Value.MAsset[0].Content}}
|
|
}
|
|
case FilterOperatorIsNotEqual, FilterOperatorDoesNotContain:
|
|
case FilterOperatorIsEmpty:
|
|
ret.MAsset = []*ValueAsset{}
|
|
case FilterOperatorIsNotEmpty:
|
|
}
|
|
case KeyTypeCheckbox:
|
|
switch filter.Operator {
|
|
case FilterOperatorIsTrue:
|
|
ret.Checkbox = &ValueCheckbox{Checked: true}
|
|
case FilterOperatorIsFalse:
|
|
ret.Checkbox = &ValueCheckbox{Checked: false}
|
|
}
|
|
case KeyTypeRelation:
|
|
switch filter.Operator {
|
|
case FilterOperatorContains:
|
|
if 0 < len(filter.Value.Relation.Contents) {
|
|
ret.Relation = &ValueRelation{Contents: filter.Value.Relation.Contents}
|
|
}
|
|
case FilterOperatorDoesNotContain:
|
|
case FilterOperatorIsEmpty:
|
|
ret.Relation = &ValueRelation{Contents: []*Value{}}
|
|
case FilterOperatorIsNotEmpty:
|
|
}
|
|
}
|
|
return
|
|
}
|