Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
Vanessa 2025-08-03 10:04:48 +08:00
commit 25678e401f
11 changed files with 94 additions and 154 deletions

View file

@ -4,7 +4,8 @@ import {transaction} from "../../wysiwyg/transaction";
import {openEditorTab} from "../../../menus/util";
import {openFileAttr} from "../../../menus/commonMenuItem";
import {
addDragFill, cellValueIsEmpty,
addDragFill,
cellValueIsEmpty,
genCellValueByElement,
getCellText,
getTypeByCellElement,
@ -603,7 +604,6 @@ export const avContextmenu = (protyle: IProtyle, rowElement: HTMLElement, positi
transaction(protyle, [{
action: "insertAttrViewBlock",
avID,
ignoreFillFilter: true,
srcs,
blockID: listItemElement.dataset.blockId,
groupID: rowElement.parentElement.getAttribute("data-group-id")

View file

@ -21,7 +21,6 @@ export const addFilesToDatabase = (fileLiElements: Element[]) => {
transaction(undefined, [{
action: "insertAttrViewBlock",
avID,
ignoreFillFilter: true,
srcs,
blockID: listItemElement.dataset.blockId
}, {
@ -40,7 +39,6 @@ export const addEditorToDatabase = (protyle: IProtyle, range: Range, type?: stri
transaction(protyle, [{
action: "insertAttrViewBlock",
avID,
ignoreFillFilter: true,
srcs: [{
id: protyle.block.rootID,
isDetached: false
@ -91,7 +89,6 @@ export const addEditorToDatabase = (protyle: IProtyle, range: Range, type?: stri
transaction(protyle, [{
action: "insertAttrViewBlock",
avID,
ignoreFillFilter: true,
srcs,
blockID: listItemElement.dataset.blockId
}, {

View file

@ -423,7 +423,6 @@ draggable="true">${genSelectItemHTML("selected", targetId, !target.querySelector
const bodyElement = hasClosestByClassName(cellElements[0], "av__body");
transaction(protyle, [{
action: "insertAttrViewBlock",
ignoreFillFilter: true,
avID: menuElement.firstElementChild.getAttribute("data-av-id"),
srcs: [{
id: rowId,

View file

@ -543,7 +543,6 @@ interface IOperation {
retData?: any
nextID?: string // insert 专享
isDetached?: boolean // insertAttrViewBlock 专享
ignoreFillFilter?: boolean // insertAttrViewBlock 专享
srcIDs?: string[] // removeAttrViewBlock 专享
srcs?: IOperationSrcs[] // insertAttrViewBlock 专享
name?: string // addAttrViewCol 专享

View file

@ -48,8 +48,15 @@ func getAttributeViewAddingBlockDefaultValues(c *gin.Context) {
if nil != arg["previousID"] {
previousID = arg["previousID"].(string)
}
var addingBlockID string
if nil != arg["addingBlockID"] {
addingBlockID = arg["addingBlockID"].(string)
}
ret.Data = model.GetAttrViewAddingBlockDefaultValues(avID, viewID, groupID, previousID)
values := model.GetAttrViewAddingBlockDefaultValues(avID, viewID, groupID, previousID, addingBlockID)
ret.Data = map[string]interface{}{
"values": values,
}
}
func batchReplaceAttributeViewBlocks(c *gin.Context) {
@ -329,17 +336,13 @@ func addAttributeViewBlocks(c *gin.Context) {
if nil != arg["previousID"] {
previousID = arg["previousID"].(string)
}
ignoreFillFilter := true
if nil != arg["ignoreFillFilter"] {
ignoreFillFilter = arg["ignoreFillFilter"].(bool)
}
var srcs []map[string]interface{}
for _, v := range arg["srcs"].([]interface{}) {
src := v.(map[string]interface{})
srcs = append(srcs, src)
}
err := model.AddAttributeViewBlock(nil, srcs, avID, blockID, groupID, previousID, ignoreFillFilter)
err := model.AddAttributeViewBlock(nil, srcs, avID, blockID, groupID, previousID)
if err != nil {
ret.Code = -1
ret.Msg = err.Error()

View file

@ -829,7 +829,7 @@ func calcRelativeTimeRegion(count int, unit RelativeDateUnit, direction Relative
return
}
func (filter *ViewFilter) GetAffectValue(key *Key, defaultVal *Value) (ret *Value) {
func (filter *ViewFilter) GetAffectValue(key *Key, defaultVal *Value, 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 {
// 所有生成的数据都不设置默认值
@ -854,6 +854,9 @@ func (filter *ViewFilter) GetAffectValue(key *Key, defaultVal *Value) (ret *Valu
}
ret = filter.Value.Clone()
ret.ID = ast.NewNodeID()
ret.KeyID = key.ID
ret.BlockID = addingBlockID
ret.CreatedAt = util.CurrentTimeMillis()
ret.UpdatedAt = ret.CreatedAt + 1000
@ -872,21 +875,21 @@ func (filter *ViewFilter) GetAffectValue(key *Key, defaultVal *Value) (ret *Valu
case KeyTypeBlock:
switch filter.Operator {
case FilterOperatorIsEqual:
ret.Block = &ValueBlock{ID: filter.Value.Block.ID, Content: filter.Value.Block.Content}
ret.Block = &ValueBlock{ID: addingBlockID, Content: filter.Value.Block.Content, Created: ret.CreatedAt, Updated: ret.UpdatedAt}
case FilterOperatorIsNotEqual:
ret.Block = &ValueBlock{ID: filter.Value.Block.ID, Content: ""}
ret.Block = &ValueBlock{ID: addingBlockID, Content: "", Created: ret.CreatedAt, Updated: ret.UpdatedAt}
case FilterOperatorContains:
ret.Block = &ValueBlock{ID: filter.Value.Block.ID, Content: filter.Value.Block.Content}
ret.Block = &ValueBlock{ID: addingBlockID, Content: filter.Value.Block.Content, Created: ret.CreatedAt, Updated: ret.UpdatedAt}
case FilterOperatorDoesNotContain:
ret.Block = &ValueBlock{ID: filter.Value.Block.ID, Content: ""}
ret.Block = &ValueBlock{ID: addingBlockID, Content: "", Created: ret.CreatedAt, Updated: ret.UpdatedAt}
case FilterOperatorStartsWith:
ret.Block = &ValueBlock{ID: filter.Value.Block.ID, Content: filter.Value.Block.Content}
ret.Block = &ValueBlock{ID: addingBlockID, Content: filter.Value.Block.Content, Created: ret.CreatedAt, Updated: ret.UpdatedAt}
case FilterOperatorEndsWith:
ret.Block = &ValueBlock{ID: filter.Value.Block.ID, Content: filter.Value.Block.Content}
ret.Block = &ValueBlock{ID: addingBlockID, Content: filter.Value.Block.Content, Created: ret.CreatedAt, Updated: ret.UpdatedAt}
case FilterOperatorIsEmpty:
ret.Block = &ValueBlock{ID: filter.Value.Block.ID, Content: ""}
ret.Block = &ValueBlock{ID: addingBlockID, Content: "", Created: ret.CreatedAt, Updated: ret.UpdatedAt}
case FilterOperatorIsNotEmpty:
ret.Block = &ValueBlock{ID: filter.Value.Block.ID, Content: ""}
ret.Block = &ValueBlock{ID: addingBlockID, Content: "", Created: ret.CreatedAt, Updated: ret.UpdatedAt}
}
case KeyTypeText:
switch filter.Operator {

View file

@ -36,7 +36,7 @@ type Value struct {
KeyID string `json:"keyID,omitempty"`
BlockID string `json:"blockID,omitempty"`
Type KeyType `json:"type,omitempty"`
IsDetached bool `json:"isDetached,omitempty"`
IsDetached bool `json:"isDetached,omitempty"` // 是否为非绑定块注意这个字段只能在主键KeyTypeBlock上使用其他类型的值不要使用
CreatedAt int64 `json:"createdAt,omitempty"`
UpdatedAt int64 `json:"updatedAt,omitempty"`

View file

@ -44,7 +44,7 @@ import (
"github.com/xrash/smetrics"
)
func GetAttrViewAddingBlockDefaultValues(avID, viewID, groupID, previousBlockID string) (ret map[string]*av.Value) {
func GetAttrViewAddingBlockDefaultValues(avID, viewID, groupID, previousBlockID, addingBlockID string) (ret map[string]*av.Value) {
ret = map[string]*av.Value{}
attrView, err := av.ParseAttributeView(avID)
@ -67,10 +67,10 @@ func GetAttrViewAddingBlockDefaultValues(avID, viewID, groupID, previousBlockID
logging.LogErrorf("group [%s] not found in view [%s] of attribute view [%s]", groupID, viewID, avID)
return
}
return getAttrViewAddingBlockDefaultValues(attrView, view, groupView, previousBlockID)
return getAttrViewAddingBlockDefaultValues(attrView, view, groupView, previousBlockID, addingBlockID)
}
func getAttrViewAddingBlockDefaultValues(attrView *av.AttributeView, view, groupView *av.View, previousBlockID string) (ret map[string]*av.Value) {
func getAttrViewAddingBlockDefaultValues(attrView *av.AttributeView, view, groupView *av.View, previousBlockID, addingBlockID string) (ret map[string]*av.Value) {
ret = map[string]*av.Value{}
nearItem := getNearItem(attrView, view, groupView, previousBlockID)
@ -90,7 +90,7 @@ func getAttrViewAddingBlockDefaultValues(attrView *av.AttributeView, view, group
defaultVal = nearItem.GetValue(filter.Column)
}
newValue := filter.GetAffectValue(keyValues.Key, defaultVal)
newValue := filter.GetAffectValue(keyValues.Key, defaultVal, addingBlockID)
if nil != newValue {
ret[keyValues.Key.ID] = newValue
}
@ -99,13 +99,7 @@ func getAttrViewAddingBlockDefaultValues(attrView *av.AttributeView, view, group
groupKey := view.GetGroupKey(attrView)
if nil != groupKey && !filterKeyIDs[groupKey.ID] /* 命中了过滤条件的话就不重复处理了 */ {
if keyValues, _ := attrView.GetKeyValues(groupKey.ID); nil != keyValues {
newValue := getNewValueByNearItem(nearItem, groupKey, ast.NewNodeID())
newValue.ID = ast.NewNodeID()
newValue.CreatedAt = util.CurrentTimeMillis()
newValue.UpdatedAt = newValue.CreatedAt + 1000
newValue.KeyID = keyValues.Key.ID
newValue := getNewValueByNearItem(nearItem, groupKey, addingBlockID)
if av.KeyTypeSelect == groupKey.Type || av.KeyTypeMSelect == groupKey.Type {
// 因为单选或多选只能按选项分组,并且可能存在空白分组(前面可能找不到临近项) ,所以单选或多选类型的分组字段使用分组值内容对应的选项
if opt := groupKey.GetOption(groupView.GroupValue); nil != opt && groupValueDefault != groupView.GroupValue {
@ -1122,17 +1116,6 @@ func SearchAttributeView(keyword string, excludeAvIDs []string) (ret []*SearchAt
continue
}
exist := false
for _, result := range ret {
if result.AvID == avID {
exist = true
break
}
}
if exist {
continue
}
var hPath string
baseBlock := treenode.GetBlockTreeRootByPath(node.Box, node.Path)
if nil != baseBlock {
@ -3034,14 +3017,14 @@ func setAttributeViewColumnCalc(operation *Operation) (err error) {
}
func (tx *Transaction) doInsertAttrViewBlock(operation *Operation) (ret *TxErr) {
err := AddAttributeViewBlock(tx, operation.Srcs, operation.AvID, operation.BlockID, operation.GroupID, operation.PreviousID, operation.IgnoreFillFilterVal)
err := AddAttributeViewBlock(tx, operation.Srcs, operation.AvID, operation.BlockID, operation.GroupID, operation.PreviousID)
if err != nil {
return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
}
return
}
func AddAttributeViewBlock(tx *Transaction, srcs []map[string]interface{}, avID, blockID, groupID, previousBlockID string, ignoreFillFilter bool) (err error) {
func AddAttributeViewBlock(tx *Transaction, srcs []map[string]interface{}, avID, blockID, groupID, previousBlockID string) (err error) {
slices.Reverse(srcs) // https://github.com/siyuan-note/siyuan/issues/11286
now := time.Now().UnixMilli()
@ -3070,14 +3053,14 @@ func AddAttributeViewBlock(tx *Transaction, srcs []map[string]interface{}, avID,
if nil != src["content"] {
srcContent = src["content"].(string)
}
if avErr := addAttributeViewBlock(now, avID, blockID, groupID, previousBlockID, srcID, srcContent, isDetached, ignoreFillFilter, tree, tx); nil != avErr {
if avErr := addAttributeViewBlock(now, avID, blockID, groupID, previousBlockID, srcID, srcContent, isDetached, tree, tx); nil != avErr {
return avErr
}
}
return
}
func addAttributeViewBlock(now int64, avID, blockID, groupID, previousBlockID, addingBlockID, addingBlockContent string, isDetached, ignoreFillFilter bool, tree *parse.Tree, tx *Transaction) (err error) {
func addAttributeViewBlock(now int64, avID, blockID, groupID, previousBlockID, addingBlockID, addingBlockContent string, isDetached bool, tree *parse.Tree, tx *Transaction) (err error) {
var node *ast.Node
if !isDetached {
node = treenode.GetNodeInTree(tree, addingBlockID)
@ -3131,57 +3114,40 @@ func addAttributeViewBlock(now int64, avID, blockID, groupID, previousBlockID, a
Block: &av.ValueBlock{ID: addingBlockID, Icon: blockIcon, Content: addingBlockContent, Created: now, Updated: now}}
blockValues.Values = append(blockValues.Values, blockValue)
view, _ := getAttrViewViewByBlockID(attrView, blockID) // blockID 可能不传,所以这里的 view 可能为空,后面使用需要判空
var nearItem av.Item // 临近项
if nil != view && ((0 < len(view.Filters) && !ignoreFillFilter) || "" != groupID) {
// 存在过滤条件或者指定分组视图时,先获取临近项备用
targetView := view
if "" != groupID {
if groupView := view.GetGroup(groupID); nil != groupView {
targetView = groupView
view, err := getAttrViewViewByBlockID(attrView, blockID)
if nil != err {
logging.LogErrorf("get view by block ID [%s] failed: %s", blockID, err)
return
}
groupView := view
if "" != groupID {
groupView = view.GetGroup(groupID)
}
defaultValues := getAttrViewAddingBlockDefaultValues(attrView, view, groupView, previousBlockID, addingBlockID)
for keyID, newValue := range defaultValues {
keyValues, getErr := attrView.GetKeyValues(keyID)
if nil != getErr {
continue
}
if av.KeyTypeBlock == newValue.Type {
// 如果是主键的话前面已经添加过了,这里仅修改内容
blockValue.Block.Content = newValue.Block.Content
continue
}
if (av.KeyTypeSelect == newValue.Type || av.KeyTypeMSelect == newValue.Type) && 1 > len(newValue.MSelect) {
// 单选或多选类型的值可能需要从分组条件中获取默认值
if opt := keyValues.Key.GetOption(groupView.GroupValue); nil != opt && groupValueDefault != groupView.GroupValue {
newValue.MSelect = append(newValue.MSelect, &av.ValueSelect{Content: opt.Name, Color: opt.Color})
}
}
nearItem = getNearItem(attrView, view, targetView, previousBlockID)
}
filterKeyIDs := map[string]bool{}
if nil != view {
for _, f := range view.Filters {
filterKeyIDs[f.Column] = true
}
}
// 如果存在过滤条件,则将过滤条件应用到新添加的块上
if nil != view && 0 < len(view.Filters) && !ignoreFillFilter {
for _, filter := range view.Filters {
for _, keyValues := range attrView.KeyValues {
if keyValues.Key.ID == filter.Column {
var defaultVal *av.Value
if nil != nearItem {
defaultVal = nearItem.GetValue(filter.Column)
}
newValue := filter.GetAffectValue(keyValues.Key, defaultVal)
if nil == newValue {
continue
}
if av.KeyTypeBlock == newValue.Type {
// 如果是主键的话前面已经添加过了,这里仅修改内容
blockValue.Block.Content = newValue.Block.Content
break
}
newValue.ID = ast.NewNodeID()
newValue.KeyID = keyValues.Key.ID
newValue.BlockID = addingBlockID
newValue.IsDetached = isDetached
keyValues.Values = append(keyValues.Values, newValue)
break
}
}
}
newValue.BlockID = addingBlockID
newValue.IsDetached = isDetached
keyValues.Values = append(keyValues.Values, newValue)
}
// 处理日期字段默认填充当前创建时间
@ -3237,37 +3203,8 @@ func addAttributeViewBlock(now int64, avID, blockID, groupID, previousBlockID, a
}
}
// 如果存在分组条件,则将分组条件应用到新添加的块上
groupKey := view.GetGroupKey(attrView)
if nil != view && nil != groupKey {
if !filterKeyIDs[groupKey.ID] /* 过滤条件应用过的话就不重复处理了 */ && "" != groupID {
if groupView := view.GetGroup(groupID); nil != groupView {
if keyValues, _ := attrView.GetKeyValues(groupKey.ID); nil != keyValues {
newValue := getNewValueByNearItem(nearItem, groupKey, blockID)
if av.KeyTypeBlock == newValue.Type {
// 如果是主键的话前面已经添加过了,这里仅修改内容
blockValue.Block.Content = newValue.Block.Content
} else {
newValue.ID = ast.NewNodeID()
newValue.CreatedAt = util.CurrentTimeMillis()
newValue.UpdatedAt = newValue.CreatedAt + 1000
newValue.KeyID = keyValues.Key.ID
newValue.BlockID = addingBlockID
newValue.IsDetached = isDetached
if av.KeyTypeSelect == groupKey.Type || av.KeyTypeMSelect == groupKey.Type {
// 因为单选或多选只能按选项分组,并且可能存在空白分组(前面可能找不到临近项) ,所以单选或多选类型的分组字段使用分组值内容对应的选项
if opt := groupKey.GetOption(groupView.GroupValue); nil != opt && groupValueDefault != groupView.GroupValue {
newValue.MSelect = append(newValue.MSelect, &av.ValueSelect{Content: opt.Name, Color: opt.Color})
}
}
keyValues.Values = append(keyValues.Values, newValue)
}
}
}
}
if nil != groupKey {
regenAttrViewViewGroups(attrView, groupKey.ID)
}
@ -3275,15 +3212,18 @@ func addAttributeViewBlock(now int64, avID, blockID, groupID, previousBlockID, a
return
}
func getNewValueByNearItem(nearItem av.Item, key *av.Key, blockID string) (ret *av.Value) {
func getNewValueByNearItem(nearItem av.Item, key *av.Key, addingBlockID string) (ret *av.Value) {
if nil != nearItem {
defaultVal := nearItem.GetValue(key.ID)
ret = defaultVal.Clone()
ret.ID = ast.NewNodeID()
ret.KeyID = key.ID
ret.BlockID = addingBlockID
ret.CreatedAt = util.CurrentTimeMillis()
ret.UpdatedAt = ret.CreatedAt + 1000
return
}
if nil == ret {
ret = av.GetAttributeViewDefaultValue(ast.NewNodeID(), key.ID, blockID, key.Type)
}
return
return av.GetAttributeViewDefaultValue(ast.NewNodeID(), key.ID, addingBlockID, key.Type)
}
func getNearItem(attrView *av.AttributeView, view, groupView *av.View, previousItemID string) (ret av.Item) {

View file

@ -986,7 +986,7 @@ func DuplicateDoc(tree *parse.Tree) {
AddAttributeViewBlock(nil, []map[string]interface{}{{
"id": n.ID,
"isDetached": false,
}}, avID, "", "", "", false)
}}, avID, "", "", "")
ReloadAttrView(avID)
}
return ast.WalkContinue

View file

@ -1226,14 +1226,14 @@ func FullTextSearchBlock(query string, boxes, paths []string, types map[string]b
}
if 5 == orderBy { // 按内容顺序(仅在按文档分组时)
sort := 0
sortVal := 0
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
if !entering || !n.IsBlock() {
return ast.WalkContinue
}
contentSorts[n.ID] = sort
sort++
contentSorts[n.ID] = sortVal
sortVal++
return ast.WalkContinue
})
}

View file

@ -1099,7 +1099,7 @@ func (tx *Transaction) doLargeInsert(previousID string) (ret *TxErr) {
AddAttributeViewBlock(tx, []map[string]interface{}{{
"id": insertedNode.ID,
"isDetached": false,
}}, avID, "", "", previousID, false)
}}, avID, "", "", previousID)
ReloadAttrView(avID)
}
@ -1284,7 +1284,7 @@ func (tx *Transaction) doInsert(operation *Operation) (ret *TxErr) {
AddAttributeViewBlock(tx, []map[string]interface{}{{
"id": insertedNode.ID,
"isDetached": false,
}}, avID, "", "", previousID, false)
}}, avID, "", "", previousID)
ReloadAttrView(avID)
}
@ -1712,22 +1712,21 @@ type Operation struct {
DeckID string `json:"deckID"` // 用于添加/删除闪卡
AvID string `json:"avID"` // 属性视图 ID
SrcIDs []string `json:"srcIDs"` // 用于从属性视图中删除行
Srcs []map[string]interface{} `json:"srcs"` // 用于添加属性视图行(包括绑定块){id, content, isDetached}
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
RemoveDest bool `json:"removeDest"` // 属性视图删除关联目标
Layout av.LayoutType `json:"layout"` // 属性视图布局类型
GroupID string `json:"groupID"` // 属性视图分组视图 ID
TargetGroupID string `json:"targetGroupID"` // 属性视图目标分组视图 ID
AvID string `json:"avID"` // 属性视图 ID
SrcIDs []string `json:"srcIDs"` // 用于从属性视图中删除行
Srcs []map[string]interface{} `json:"srcs"` // 用于添加属性视图行(包括绑定块){id, content, isDetached}
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
RemoveDest bool `json:"removeDest"` // 属性视图删除关联目标
Layout av.LayoutType `json:"layout"` // 属性视图布局类型
GroupID string `json:"groupID"` // 属性视图分组视图 ID
TargetGroupID string `json:"targetGroupID"` // 属性视图目标分组视图 ID
}
type Transaction struct {