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

This commit is contained in:
Vanessa 2025-07-08 21:08:44 +08:00
commit 2657275643
18 changed files with 340 additions and 162 deletions

View file

@ -1627,6 +1627,12 @@
"255": "قام الموقع المستهدف بتمكين حماية الروابط المباشرة، لذلك لا يمكن تنزيل [%d] من الموارد",
"256": "المسار المحدد [%s] يحتوي على مسار مساحة عمل رئيسي [%s]",
"257": "تذكرني لمدة 30 يومًا",
"258": "فشلت العملية، يرجى المحاولة مرة أخرى لاحقًا"
"258": "فشلت العملية، يرجى المحاولة مرة أخرى لاحقًا",
"259": "آخر %d أيام",
"260": "أمس",
"261": "اليوم",
"262": "غدًا",
"263": "التالي %d أيام",
"264": "الحقل [%s] فارغ"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -1627,6 +1627,12 @@
"255": "האתר היעד הפעיל הגנה על קישורים חמים, ולכן לא ניתן להוריד [%d] משאבים",
"256": "הנתיב שצוין [%s] מכיל נתיב מרחב עבודה אב [%s]",
"257": "זכור אותי למשך 30 יום",
"258": "הפעולה נכשלה, נסה שוב מאוחר יותר"
"258": "הפעולה נכשלה, נסה שוב מאוחר יותר",
"259": "ה-%d ימים האחרונים",
"260": "אתמול",
"261": "היום",
"262": "מחר",
"263": "ה-%d ימים הבאים",
"264": "השדה [%s] ריק"
}
}

View file

@ -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"
}
}

View file

@ -1627,6 +1627,12 @@
"255": "ターゲットサイトはホットリンク保護を有効にしているため、[%d] 個のリソースをダウンロードできません",
"256": "指定されたパス [%s] の親にワークスペースパス [%s] が存在します",
"257": "30日間私を覚えていてください",
"258": "操作に失敗しました。後でもう一度お試しください"
"258": "操作に失敗しました。後でもう一度お試しください",
"259": "過去 %d 日間",
"260": "昨日",
"261": "今日",
"262": "明日",
"263": "次の %d 日間",
"264": "フィールド [%s] の値が空です"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -1627,6 +1627,12 @@
"255": "Целевой сайт включил защиту от хотлинков, [%d] ресурсов невозможно скачать",
"256": "Указанный путь [%s] имеет родительский путь рабочей области [%s]",
"257": "Запомнить меня на 30 дней",
"258": "Операция не удалась, попробуйте позже"
"258": "Операция не удалась, попробуйте позже",
"259": "Прошедшие %d дней",
"260": "Вчера",
"261": "Сегодня",
"262": "Завтра",
"263": "Следующие %d дней",
"264": "Поле [%s] имеет пустое значение"
}
}

View file

@ -1627,6 +1627,12 @@
"255": "目標站點啟用了防盜鏈,[%d] 個資源無法下載",
"256": "指定的路徑 [%s] 父級存在工作空間路徑 [%s]",
"257": "記住我 30 天",
"258": "操作失敗,請稍後再試"
"258": "操作失敗,請稍後再試",
"259": "過去 %d 天",
"260": "昨天",
"261": "今天",
"262": "明天",
"263": "未來 %d 天",
"264": "字段 [%s] 值為空"
}
}

View file

@ -1627,6 +1627,12 @@
"255": "目标站点启用了防盗链,[%d] 个资源无法下载",
"256": "指定的路径 [%s] 父级存在工作空间路径 [%s]",
"257": "记住我 30 天",
"258": "操作失败,请稍后再试"
"258": "操作失败,请稍后再试",
"259": "过去 %d 天",
"260": "昨天",
"261": "今天",
"262": "明天",
"263": "未来 %d 天",
"264": "字段 [%s] 值为空"
}
}

View file

@ -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) {

View file

@ -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
}

View file

@ -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
}

View file

@ -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

View file

@ -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
}