diff --git a/app/appearance/langs/ar_SA.json b/app/appearance/langs/ar_SA.json index f011b84c0..3aa9fc413 100644 --- a/app/appearance/langs/ar_SA.json +++ b/app/appearance/langs/ar_SA.json @@ -1627,6 +1627,12 @@ "255": "قام الموقع المستهدف بتمكين حماية الروابط المباشرة، لذلك لا يمكن تنزيل [%d] من الموارد", "256": "المسار المحدد [%s] يحتوي على مسار مساحة عمل رئيسي [%s]", "257": "تذكرني لمدة 30 يومًا", - "258": "فشلت العملية، يرجى المحاولة مرة أخرى لاحقًا" + "258": "فشلت العملية، يرجى المحاولة مرة أخرى لاحقًا", + "259": "آخر %d أيام", + "260": "أمس", + "261": "اليوم", + "262": "غدًا", + "263": "التالي %d أيام", + "264": "الحقل [%s] فارغ" } } diff --git a/app/appearance/langs/de_DE.json b/app/appearance/langs/de_DE.json index 6c06c9497..451775985 100644 --- a/app/appearance/langs/de_DE.json +++ b/app/appearance/langs/de_DE.json @@ -1627,6 +1627,12 @@ "255": "Die Zielseite hat den Hotlink-Schutz aktiviert, daher ist es nicht möglich, [%d] Ressourcen herunterzuladen", "256": "Der angegebene Pfad [%s] hat einen übergeordneten Arbeitsbereichspfad [%s]", "257": "Erinnere dich 30 Tage an mich", - "258": "Vorgang fehlgeschlagen, bitte versuchen Sie es später erneut" + "258": "Vorgang fehlgeschlagen, bitte versuchen Sie es später erneut", + "259": "Letzte %d Tage", + "260": "Gestern", + "261": "Heute", + "262": "Morgen", + "263": "Nächste %d Tage", + "264": "Das Feld [%s] ist leer" } } diff --git a/app/appearance/langs/en_US.json b/app/appearance/langs/en_US.json index 5868a6891..8621420c6 100644 --- a/app/appearance/langs/en_US.json +++ b/app/appearance/langs/en_US.json @@ -1627,6 +1627,12 @@ "255": "The target site has enabled hotlink protection, so it is not possible to download [%d] resources", "256": "The specified path [%s] has a parent workspace path [%s]", "257": "Remember me for 30 days", - "258": "Operation failed, please try again later" + "258": "Operation failed, please try again later", + "259": "Last %d days", + "260": "Yesterday", + "261": "Today", + "262": "Tomorrow", + "263": "Next %d days", + "264": "Field [%s] is empty" } } diff --git a/app/appearance/langs/es_ES.json b/app/appearance/langs/es_ES.json index 791c3ac64..ac80a2b70 100644 --- a/app/appearance/langs/es_ES.json +++ b/app/appearance/langs/es_ES.json @@ -1627,6 +1627,12 @@ "255": "El sitio de destino ha activado la protección contra hotlinking, por lo que no es posible descargar [%d] recursos", "256": "La ruta especificada [%s] tiene una ruta de espacio de trabajo padre [%s]", "257": "Recuérdame durante 30 días", - "258": "La operación falló, inténtelo de nuevo más tarde" + "258": "La operación falló, inténtelo de nuevo más tarde", + "259": "Últimos %d días", + "260": "Ayer", + "261": "Hoy", + "262": "Mañana", + "263": "Próximos %d días", + "264": "El campo [%s] está vacío" } } diff --git a/app/appearance/langs/fr_FR.json b/app/appearance/langs/fr_FR.json index a9de3f59b..9ba4be4d2 100644 --- a/app/appearance/langs/fr_FR.json +++ b/app/appearance/langs/fr_FR.json @@ -1627,6 +1627,12 @@ "255": "Le site cible a activé la protection contre le hotlinking, il est donc impossible de télécharger [%d] ressources", "256": "Le chemin spécifié [%s] contient un chemin d'espace de travail parent [%s]", "257": "Souviens-toi de moi pendant 30 jours", - "258": "Échec de l'opération, veuillez réessayer plus tard" + "258": "Échec de l'opération, veuillez réessayer plus tard", + "259": "Les %d derniers jours", + "260": "Hier", + "261": "Aujourd'hui", + "262": "Demain", + "263": "Les %d prochains jours", + "264": "Le champ [%s] est vide" } } diff --git a/app/appearance/langs/he_IL.json b/app/appearance/langs/he_IL.json index a6b5c9605..9f9747bc5 100644 --- a/app/appearance/langs/he_IL.json +++ b/app/appearance/langs/he_IL.json @@ -1627,6 +1627,12 @@ "255": "האתר היעד הפעיל הגנה על קישורים חמים, ולכן לא ניתן להוריד [%d] משאבים", "256": "הנתיב שצוין [%s] מכיל נתיב מרחב עבודה אב [%s]", "257": "זכור אותי למשך 30 יום", - "258": "הפעולה נכשלה, נסה שוב מאוחר יותר" + "258": "הפעולה נכשלה, נסה שוב מאוחר יותר", + "259": "ה-%d ימים האחרונים", + "260": "אתמול", + "261": "היום", + "262": "מחר", + "263": "ה-%d ימים הבאים", + "264": "השדה [%s] ריק" } } diff --git a/app/appearance/langs/it_IT.json b/app/appearance/langs/it_IT.json index 9bbefee47..6e40d7a0e 100644 --- a/app/appearance/langs/it_IT.json +++ b/app/appearance/langs/it_IT.json @@ -1627,6 +1627,12 @@ "255": "Il sito di destinazione ha abilitato la protezione hotlink, quindi non è possibile scaricare [%d] risorse", "256": "Il percorso specificato [%s] ha un percorso di spazio di lavoro padre [%s]", "257": "Ricordati di me per 30 giorni", - "258": "Operazione fallita, riprova più tardi" + "258": "Operazione fallita, riprova più tardi", + "259": "Ultimi %d giorni", + "260": "Ieri", + "261": "Oggi", + "262": "Domani", + "263": "Prossimi %d giorni", + "264": "Il campo [%s] è vuoto" } } diff --git a/app/appearance/langs/ja_JP.json b/app/appearance/langs/ja_JP.json index 3a2d8f7dc..53c33aefd 100644 --- a/app/appearance/langs/ja_JP.json +++ b/app/appearance/langs/ja_JP.json @@ -1627,6 +1627,12 @@ "255": "ターゲットサイトはホットリンク保護を有効にしているため、[%d] 個のリソースをダウンロードできません", "256": "指定されたパス [%s] の親にワークスペースパス [%s] が存在します", "257": "30日間私を覚えていてください", - "258": "操作に失敗しました。後でもう一度お試しください" + "258": "操作に失敗しました。後でもう一度お試しください", + "259": "過去 %d 日間", + "260": "昨日", + "261": "今日", + "262": "明日", + "263": "次の %d 日間", + "264": "フィールド [%s] の値が空です" } } diff --git a/app/appearance/langs/pl_PL.json b/app/appearance/langs/pl_PL.json index c6167b2cb..3367fb288 100644 --- a/app/appearance/langs/pl_PL.json +++ b/app/appearance/langs/pl_PL.json @@ -1627,6 +1627,12 @@ "255": "Docelowa strona włączyła ochronę przed hotlinkowaniem, [%d] zasobów nie można pobrać", "256": "Określona ścieżka [%s] ma nadrzędną ścieżkę przestrzeni roboczej [%s]", "257": "Zapamiętaj mnie na 30 dni", - "258": "Operacja nie powiodła się, spróbuj ponownie później" + "258": "Operacja nie powiodła się, spróbuj ponownie później", + "259": "Ostatnie %d dni", + "260": "Wczoraj", + "261": "Dzisiaj", + "262": "Jutro", + "263": "Następne %d dni", + "264": "Pole [%s] jest puste" } } diff --git a/app/appearance/langs/pt_BR.json b/app/appearance/langs/pt_BR.json index 7f2a7c600..d719a82ca 100644 --- a/app/appearance/langs/pt_BR.json +++ b/app/appearance/langs/pt_BR.json @@ -1627,6 +1627,12 @@ "255": "O site de destino ativou proteção contra hotlinking, portanto não é possível baixar [%d] recursos", "256": "O caminho especificado [%s] tem um caminho de espaço de trabalho pai [%s]", "257": "Lembre-se de mim por 30 dias", - "258": "A operação falhou, tente novamente mais tarde" + "258": "A operação falhou, tente novamente mais tarde", + "259": "Últimos %d dias", + "260": "Ontem", + "261": "Hoje", + "262": "Amanhã", + "263": "Próximos %d dias", + "264": "O campo [%s] está vazio" } } diff --git a/app/appearance/langs/ru_RU.json b/app/appearance/langs/ru_RU.json index b5873e368..cfce581ad 100644 --- a/app/appearance/langs/ru_RU.json +++ b/app/appearance/langs/ru_RU.json @@ -1627,6 +1627,12 @@ "255": "Целевой сайт включил защиту от хотлинков, [%d] ресурсов невозможно скачать", "256": "Указанный путь [%s] имеет родительский путь рабочей области [%s]", "257": "Запомнить меня на 30 дней", - "258": "Операция не удалась, попробуйте позже" + "258": "Операция не удалась, попробуйте позже", + "259": "Прошедшие %d дней", + "260": "Вчера", + "261": "Сегодня", + "262": "Завтра", + "263": "Следующие %d дней", + "264": "Поле [%s] имеет пустое значение" } } diff --git a/app/appearance/langs/zh_CHT.json b/app/appearance/langs/zh_CHT.json index a7366eb0a..1c241541b 100644 --- a/app/appearance/langs/zh_CHT.json +++ b/app/appearance/langs/zh_CHT.json @@ -1627,6 +1627,12 @@ "255": "目標站點啟用了防盜鏈,[%d] 個資源無法下載", "256": "指定的路徑 [%s] 父級存在工作空間路徑 [%s]", "257": "記住我 30 天", - "258": "操作失敗,請稍後再試" + "258": "操作失敗,請稍後再試", + "259": "過去 %d 天", + "260": "昨天", + "261": "今天", + "262": "明天", + "263": "未來 %d 天", + "264": "字段 [%s] 值為空" } } diff --git a/app/appearance/langs/zh_CN.json b/app/appearance/langs/zh_CN.json index dd4ec71ce..d8b51a7b3 100644 --- a/app/appearance/langs/zh_CN.json +++ b/app/appearance/langs/zh_CN.json @@ -1627,6 +1627,12 @@ "255": "目标站点启用了防盗链,[%d] 个资源无法下载", "256": "指定的路径 [%s] 父级存在工作空间路径 [%s]", "257": "记住我 30 天", - "258": "操作失败,请稍后再试" + "258": "操作失败,请稍后再试", + "259": "过去 %d 天", + "260": "昨天", + "261": "今天", + "262": "明天", + "263": "未来 %d 天", + "264": "字段 [%s] 值为空" } } diff --git a/kernel/av/av.go b/kernel/av/av.go index 56fa399c9..b03672875 100644 --- a/kernel/av/av.go +++ b/kernel/av/av.go @@ -185,13 +185,14 @@ type View struct { Desc string `json:"desc"` // 视图描述 Filters []*ViewFilter `json:"filters,omitempty"` // 过滤规则 Sorts []*ViewSort `json:"sorts,omitempty"` // 排序规则 - Group *ViewGroup `json:"group,omitempty"` // 分组规则 PageSize int `json:"pageSize"` // 每页条目数 LayoutType LayoutType `json:"type"` // 当前布局类型 Table *LayoutTable `json:"table,omitempty"` // 表格布局 Gallery *LayoutGallery `json:"gallery,omitempty"` // 卡片布局 ItemIDs []string `json:"itemIds,omitempty"` // 项目 ID 列表,用于维护所有项目 + Group *ViewGroup `json:"group,omitempty"` // 分组规则 + GroupUpdated int64 `json:"groupUpdated"` // 分组规则更新时间戳 Groups []*View `json:"groups,omitempty"` // 分组视图列表 GroupItemIDs []string `json:"groupItemIds,omitempty"` // 分组项目 ID 列表,用于维护分组中的所有项目 GroupCalc *GroupCalc `json:"groupCalc,omitempty"` // 分组计算规则 @@ -199,7 +200,6 @@ type View struct { GroupFolded bool `json:"groupFolded,omitempty"` // 分组是否折叠 GroupHidden bool `json:"groupHidden,omitempty"` // 分组是否隐藏 GroupHideEmpty bool `json:"groupHideEmpty,omitempty"` // 分组是否隐藏空分组 - GroupDefault bool `json:"groupDefault,omitempty"` // 是否为默认分组 } // GroupCalc 描述了分组计算规则和结果的结构。 @@ -291,9 +291,6 @@ type Viewable interface { // SetGroupHidden 设置分组是否隐藏。 SetGroupHidden(hidden bool) - - // SetGroupDefault 设置分组是否为默认分组。 - SetGroupDefault(defaulted bool) } func NewAttributeView(id string) (ret *AttributeView) { diff --git a/kernel/av/layout.go b/kernel/av/layout.go index c81524167..c642dd8d3 100644 --- a/kernel/av/layout.go +++ b/kernel/av/layout.go @@ -66,12 +66,11 @@ type BaseInstance struct { Folded bool `json:"folded,omitempty"` // 是否折叠 Hidden bool `json:"hidden,omitempty"` // 是否隐藏 - Groups []Viewable `json:"groups,omitempty"` // 分组实例列表 - GroupCalc *GroupCalc `json:"groupCalc,omitempty"` // 分组计算规则和结果 - GroupName string `json:"groupName,omitempty"` // 分组名称 - GroupFolded bool `json:"groupFolded,omitempty"` // 分组是否折叠 - GroupHidden bool `json:"groupHidden,omitempty"` // 分组是否隐藏 - GroupDefault bool `json:"groupDefault,omitempty"` // 是否为默认分组 + Groups []Viewable `json:"groups,omitempty"` // 分组实例列表 + GroupCalc *GroupCalc `json:"groupCalc,omitempty"` // 分组计算规则和结果 + GroupName string `json:"groupName,omitempty"` // 分组名称 + GroupFolded bool `json:"groupFolded,omitempty"` // 分组是否折叠 + GroupHidden bool `json:"groupHidden,omitempty"` // 分组是否隐藏 } func NewViewBaseInstance(view *View) *BaseInstance { @@ -97,7 +96,6 @@ func NewViewBaseInstance(view *View) *BaseInstance { GroupName: view.GroupName, GroupFolded: view.GroupFolded, GroupHidden: view.GroupHidden, - GroupDefault: view.GroupDefault, ShowIcon: showIcon, WrapField: wrapField, } @@ -135,10 +133,6 @@ func (baseInstance *BaseInstance) SetGroupHidden(hidden bool) { baseInstance.GroupHidden = hidden } -func (baseInstance *BaseInstance) SetGroupDefault(defaulted bool) { - baseInstance.GroupDefault = defaulted -} - func (baseInstance *BaseInstance) GetID() string { return baseInstance.ID } diff --git a/kernel/model/assets.go b/kernel/model/assets.go index 2cba74ac0..d65e192ba 100644 --- a/kernel/model/assets.go +++ b/kernel/model/assets.go @@ -272,10 +272,10 @@ func NetAssets2LocalAssets(rootID string, onlyImg bool, originalURL string) (err func SearchAssetsByName(keyword string, exts []string) (ret []*cache.Asset) { ret = []*cache.Asset{} - keywords := strings.Split(keyword, " ") - + var keywords []string + keywords = append(keywords, keyword) + keywords = append(keywords, strings.Split(keyword, " ")...) pathHitCount := map[string]int{} - count := 0 filterByExt := 0 < len(exts) for _, asset := range cache.GetAssets() { if filterByExt { @@ -295,8 +295,18 @@ func SearchAssetsByName(keyword string, exts []string) (ret []*cache.Asset) { lowerHName := strings.ToLower(asset.HName) lowerPath := strings.ToLower(asset.Path) var hitNameCount, hitPathCount int - for _, k := range keywords { + for i, k := range keywords { lowerKeyword := strings.ToLower(k) + if 0 == i { + // 第一个是完全匹配,权重最高 + if strings.Contains(lowerHName, lowerKeyword) { + hitNameCount += 64 + } + if strings.Contains(lowerPath, lowerKeyword) { + hitPathCount += 64 + } + } + hitNameCount += strings.Count(lowerHName, lowerKeyword) hitPathCount += strings.Count(lowerPath, lowerKeyword) if 1 > hitNameCount && 1 > hitPathCount { @@ -318,10 +328,6 @@ func SearchAssetsByName(keyword string, exts []string) (ret []*cache.Asset) { Path: asset.Path, Updated: asset.Updated, }) - count++ - if Conf.Search.Limit <= count { - return - } } if 0 < len(pathHitCount) { @@ -333,6 +339,10 @@ func SearchAssetsByName(keyword string, exts []string) (ret []*cache.Asset) { return ret[i].Updated > ret[j].Updated }) } + + if Conf.Search.Limit <= len(ret) { + ret = ret[:Conf.Search.Limit] + } return } diff --git a/kernel/model/attribute_view.go b/kernel/model/attribute_view.go index 88dae2279..826ef3409 100644 --- a/kernel/model/attribute_view.go +++ b/kernel/model/attribute_view.go @@ -143,123 +143,7 @@ func SetAttributeViewGroup(avID, blockID string, group *av.ViewGroup) (err error } view.Group = group - view.Groups = nil - - // 生成分组数据 - const ( - defaultGroupName = "_@default@_" - notInRange = "_@notInRange@_" - ) - var groupName string - viewable := sql.RenderView(attrView, view, "") - - var items []av.Item - for _, item := range viewable.(av.Collection).GetItems() { - items = append(items, item) - } - var rangeStart, rangeEnd float64 - switch group.Method { - case av.GroupMethodValue: - if av.GroupOrderMan != group.Order { - sort.SliceStable(items, func(i, j int) bool { - if av.GroupOrderAsc == group.Order { - return items[i].GetValue(group.Field).String(false) < items[j].GetValue(group.Field).String(false) - } - return items[i].GetValue(group.Field).String(false) > items[j].GetValue(group.Field).String(false) - }) - } - case av.GroupMethodRangeNum: - if nil == group.Range { - logging.LogWarnf("range is nil in av [%s]", avID) - return - } - - rangeStart, rangeEnd = group.Range.NumStart, group.Range.NumStart+group.Range.NumStep - sort.SliceStable(items, func(i, j int) bool { - if av.GroupOrderAsc == group.Order { - return items[i].GetValue(group.Field).Number.Content < items[j].GetValue(group.Field).Number.Content - } - return items[i].GetValue(group.Field).Number.Content > items[j].GetValue(group.Field).Number.Content - }) - case av.GroupMethodDateDay, av.GroupMethodDateWeek, av.GroupMethodDateMonth, av.GroupMethodDateYear, av.GroupMethodDateRelative: - sort.SliceStable(items, func(i, j int) bool { - if av.GroupOrderAsc == group.Order { - return items[i].GetValue(group.Field).Date.Content < items[j].GetValue(group.Field).Date.Content - } - return items[i].GetValue(group.Field).Date.Content > items[j].GetValue(group.Field).Date.Content - }) - } - - groupItemsMap := map[string][]av.Item{} - for _, item := range items { - value := item.GetValue(group.Field) - if value.IsEmpty() { - groupName = defaultGroupName - } else { - switch group.Method { - case av.GroupMethodValue: - groupName = value.String(false) - case av.GroupMethodRangeNum: - if group.Range.NumStart > value.Number.Content || group.Range.NumEnd < value.Number.Content { - groupName = notInRange - break - } - - for rangeEnd <= group.Range.NumEnd && rangeEnd < value.Number.Content { - rangeStart += group.Range.NumStep - rangeEnd += group.Range.NumStep - } - - if rangeStart <= value.Number.Content && rangeEnd >= value.Number.Content { - groupName = fmt.Sprintf("%s - %s", strconv.FormatFloat(rangeStart, 'f', -1, 64), strconv.FormatFloat(rangeEnd, 'f', -1, 64)) - } - case av.GroupMethodDateDay, av.GroupMethodDateWeek, av.GroupMethodDateMonth, av.GroupMethodDateYear, av.GroupMethodDateRelative: - var contentTime time.Time - switch value.Type { - case av.KeyTypeDate: - contentTime = time.UnixMilli(value.Date.Content) - case av.KeyTypeCreated: - contentTime = time.UnixMilli(value.Created.Content) - case av.KeyTypeUpdated: - contentTime = time.UnixMilli(value.Updated.Content) - } - switch group.Method { - case av.GroupMethodDateDay: - groupName = contentTime.Format("2006-01-02") - case av.GroupMethodDateWeek: - year, week := contentTime.ISOWeek() - groupName = fmt.Sprintf("%d-W%02d", year, week) - case av.GroupMethodDateMonth: - groupName = contentTime.Format("2006-01") - case av.GroupMethodDateYear: - groupName = contentTime.Format("2006") - case av.GroupMethodDateRelative: - - } - } - } - groupItemsMap[groupName] = append(groupItemsMap[groupName], item) - } - - for name, groupItems := range groupItemsMap { - var v *av.View - switch view.LayoutType { - case av.LayoutTypeTable: - v = av.NewTableView() - v.Table = av.NewLayoutTable() - case av.LayoutTypeGallery: - v = av.NewGalleryView() - v.Gallery = av.NewLayoutGallery() - } - for _, item := range groupItems { - v.GroupItemIDs = append(v.GroupItemIDs, item.GetID()) - } - v.Name = name - view.Groups = append(view.Groups, v) - view.GroupDefault = name == defaultGroupName - } - - err = av.SaveAttributeView(attrView) + genGroup(view, attrView) return err } @@ -370,13 +254,26 @@ func ChangeAttrViewLayout(blockID, avID string, layout av.LayoutType) (err error continue } - node.AttributeViewType = string(view.LayoutType) + changed := false attrs := parse.IAL2Map(node.KramdownIAL) - attrs[av.NodeAttrView] = view.ID - err = setNodeAttrs(node, tree, attrs) - if err != nil { - logging.LogWarnf("set node [%s] attrs failed: %s", bID, err) - return + if blockID == bID { // 当前操作的镜像库 + attrs[av.NodeAttrView] = view.ID + node.AttributeViewType = string(view.LayoutType) + changed = true + } else { + if view.ID == attrs[av.NodeAttrView] { + // 仅更新和当前操作的镜像库指定的视图相同的镜像库 + node.AttributeViewType = string(view.LayoutType) + changed = true + } + } + + if changed { + err = setNodeAttrs(node, tree, attrs) + if err != nil { + logging.LogWarnf("set node [%s] attrs failed: %s", bID, err) + return + } } } @@ -1405,6 +1302,12 @@ func RenderAttributeView(avID, viewID, query string, page, pageSize int) (viewab return } +const ( + groupNameLast30Days, groupNameLast7Days = "_@last30Days@_", "_@last7Days@_" + groupNameYesterday, groupNameToday, groupNameTomorrow = "_@yesterday@_", "_@today@_", "_@tomorrow@_" + groupNameNext7Days, groupNameNext30Days = "_@next7Days@_", "_@next30Days@_" +) + func renderAttributeView(attrView *av.AttributeView, viewID, query string, page, pageSize int) (viewable av.Viewable, err error) { if 1 > len(attrView.Views) { view, _, _ := av.NewTableViewWithBlockKey(ast.NewNodeID()) @@ -1444,6 +1347,33 @@ func renderAttributeView(attrView *av.AttributeView, viewID, query string, page, return } + // 当前日期可能会变,所以如果是按日期分组则需要重新生成分组 + if isGroupByDate(view) { + updatedDate := time.UnixMilli(view.GroupUpdated).Format("2006-01-02") + if time.Now().Format("2006-01-02") != updatedDate { + genGroup(view, attrView) + } + + for _, groupView := range view.Groups { + switch groupView.Name { + case groupNameLast30Days: + groupView.Name = fmt.Sprintf(Conf.language(259), 30) + case groupNameLast7Days: + groupView.Name = fmt.Sprintf(Conf.language(259), 7) + case groupNameYesterday: + groupView.Name = Conf.language(260) + case groupNameToday: + groupView.Name = Conf.language(261) + case groupNameTomorrow: + groupView.Name = Conf.language(262) + case groupNameNext7Days: + groupView.Name = fmt.Sprintf(Conf.language(263), 7) + case groupNameNext30Days: + groupView.Name = fmt.Sprintf(Conf.language(263), 30) + } + } + } + // 如果存在分组的话渲染分组视图视图 var groups []av.Viewable for _, groupView := range view.Groups { @@ -1465,6 +1395,171 @@ func renderAttributeView(attrView *av.AttributeView, viewID, query string, page, return } +func genGroup(view *av.View, attrView *av.AttributeView) { + if nil == view.Group { + return + } + + group := view.Group + view.Groups = nil + viewable := sql.RenderView(attrView, view, "") + var items []av.Item + for _, item := range viewable.(av.Collection).GetItems() { + items = append(items, item) + } + + groupKey, _ := attrView.GetKey(group.Field) + if nil == groupKey { + return + } + + var rangeStart, rangeEnd float64 + switch group.Method { + case av.GroupMethodValue: + if av.GroupOrderMan != group.Order { + sort.SliceStable(items, func(i, j int) bool { + if av.GroupOrderAsc == group.Order { + return items[i].GetValue(group.Field).String(false) < items[j].GetValue(group.Field).String(false) + } + return items[i].GetValue(group.Field).String(false) > items[j].GetValue(group.Field).String(false) + }) + } + case av.GroupMethodRangeNum: + if nil == group.Range { + return + } + + rangeStart, rangeEnd = group.Range.NumStart, group.Range.NumStart+group.Range.NumStep + sort.SliceStable(items, func(i, j int) bool { + if av.GroupOrderAsc == group.Order { + return items[i].GetValue(group.Field).Number.Content < items[j].GetValue(group.Field).Number.Content + } + return items[i].GetValue(group.Field).Number.Content > items[j].GetValue(group.Field).Number.Content + }) + case av.GroupMethodDateDay, av.GroupMethodDateWeek, av.GroupMethodDateMonth, av.GroupMethodDateYear, av.GroupMethodDateRelative: + sort.SliceStable(items, func(i, j int) bool { + if av.GroupOrderAsc == group.Order { + return items[i].GetValue(group.Field).Date.Content < items[j].GetValue(group.Field).Date.Content + } + return items[i].GetValue(group.Field).Date.Content > items[j].GetValue(group.Field).Date.Content + }) + } + + const defaultGroupName, notInRange = "_@default@_", "_@notInRange@_" + var groupName string + groupItemsMap := map[string][]av.Item{} + for _, item := range items { + value := item.GetValue(group.Field) + if value.IsEmpty() { + groupName = defaultGroupName + groupItemsMap[groupName] = append(groupItemsMap[groupName], item) + continue + } + + switch group.Method { + case av.GroupMethodValue: + groupName = value.String(false) + case av.GroupMethodRangeNum: + if group.Range.NumStart > value.Number.Content || group.Range.NumEnd < value.Number.Content { + groupName = notInRange + break + } + + for rangeEnd <= group.Range.NumEnd && rangeEnd < value.Number.Content { + rangeStart += group.Range.NumStep + rangeEnd += group.Range.NumStep + } + + if rangeStart <= value.Number.Content && rangeEnd >= value.Number.Content { + groupName = fmt.Sprintf("%s - %s", strconv.FormatFloat(rangeStart, 'f', -1, 64), strconv.FormatFloat(rangeEnd, 'f', -1, 64)) + } + case av.GroupMethodDateDay, av.GroupMethodDateWeek, av.GroupMethodDateMonth, av.GroupMethodDateYear, av.GroupMethodDateRelative: + var contentTime time.Time + switch value.Type { + case av.KeyTypeDate: + contentTime = time.UnixMilli(value.Date.Content) + case av.KeyTypeCreated: + contentTime = time.UnixMilli(value.Created.Content) + case av.KeyTypeUpdated: + contentTime = time.UnixMilli(value.Updated.Content) + } + switch group.Method { + case av.GroupMethodDateDay: + groupName = contentTime.Format("2006-01-02") + case av.GroupMethodDateWeek: + year, week := contentTime.ISOWeek() + groupName = fmt.Sprintf("%d-W%02d", year, week) + case av.GroupMethodDateMonth: + groupName = contentTime.Format("2006-01") + case av.GroupMethodDateYear: + groupName = contentTime.Format("2006") + case av.GroupMethodDateRelative: + // 过去 30 天之前的按月分组 + // 过去 30 天、过去 7 天、昨天、今天、明天、未来 7 天、未来 30 天 + // 未来 30 天之后的按月分组 + now := time.Now() + if contentTime.Before(now.AddDate(0, 0, -30)) { + groupName = contentTime.Format("2006-01") + } else if contentTime.Before(now.AddDate(0, 0, -7)) { + groupName = groupNameLast30Days + } else if contentTime.Before(now.AddDate(0, 0, -1)) { + groupName = groupNameLast7Days + } else if contentTime.Equal(now.AddDate(0, 0, -1)) { + groupName = groupNameYesterday + } else if contentTime.Equal(now) { + groupName = groupNameToday + } else if contentTime.Equal(now.AddDate(0, 0, 1)) { + groupName = groupNameTomorrow + } else if contentTime.Before(now.AddDate(0, 0, 7)) { + groupName = groupNameNext7Days + } else if contentTime.Before(now.AddDate(0, 0, 30)) { + groupName = groupNameNext30Days + } else { + groupName = contentTime.Format("2006-01") + } + } + } + groupItemsMap[groupName] = append(groupItemsMap[groupName], item) + } + + for name, groupItems := range groupItemsMap { + var v *av.View + switch view.LayoutType { + case av.LayoutTypeTable: + v = av.NewTableView() + v.Table = av.NewLayoutTable() + case av.LayoutTypeGallery: + v = av.NewGalleryView() + v.Gallery = av.NewLayoutGallery() + default: + logging.LogWarnf("unknown layout type [%s] for group view", view.LayoutType) + return + } + for _, item := range groupItems { + v.GroupItemIDs = append(v.GroupItemIDs, item.GetID()) + } + + if defaultGroupName == name { + name = fmt.Sprintf(Conf.language(264), groupKey.Name) + } + v.Name = name + view.Groups = append(view.Groups, v) + } + + if isGroupByDate(view) { + view.GroupUpdated = time.Now().UnixMilli() + } + + av.SaveAttributeView(attrView) +} + +func isGroupByDate(view *av.View) bool { + if nil == view.Group { + return false + } + return av.GroupMethodDateDay == view.Group.Method || av.GroupMethodDateWeek == view.Group.Method || av.GroupMethodDateMonth == view.Group.Method || av.GroupMethodDateYear == view.Group.Method || av.GroupMethodDateRelative == view.Group.Method +} + func renderViewableInstance(viewable av.Viewable, view *av.View, attrView *av.AttributeView, page, pageSize int) (err error) { if nil == viewable { err = av.ErrViewNotFound diff --git a/kernel/model/virutalref.go b/kernel/model/virutalref.go index 88f7319b3..67a899028 100644 --- a/kernel/model/virutalref.go +++ b/kernel/model/virutalref.go @@ -252,12 +252,16 @@ func getVirtualRefKeywords(root *ast.Node) (ret []string) { if 0 < len(regexps) { tmp = nil for _, str := range ret { + matchExclude := false for _, re := range regexps { - if ok, regErr := regexp.MatchString(re, str); !ok && nil == regErr { - tmp = append(tmp, str) + if ok, _ := regexp.MatchString(re, str); ok { + matchExclude = true break } } + if !matchExclude { + tmp = append(tmp, str) + } } ret = tmp }