mirror of
https://github.com/usememos/memos.git
synced 2025-10-25 22:07:19 +08:00
feat: enhance memo sorting functionality to support multiple fields
This commit is contained in:
parent
95de5cc700
commit
b4ea7d843f
30 changed files with 113 additions and 55 deletions
|
|
@ -291,7 +291,9 @@ message ListMemosRequest {
|
|||
|
||||
// Optional. The order to sort results by.
|
||||
// Default to "display_time desc".
|
||||
// Example: "display_time desc" or "create_time asc"
|
||||
// Supports comma-separated list of fields following AIP-132.
|
||||
// Example: "pinned desc, display_time desc" or "create_time asc"
|
||||
// Supported fields: pinned, display_time, create_time, update_time, name
|
||||
string order_by = 4 [(google.api.field_behavior) = OPTIONAL];
|
||||
|
||||
// Optional. Filter to apply to the list results.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.9
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc (unknown)
|
||||
// source: api/v1/activity_service.proto
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.9
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc (unknown)
|
||||
// source: api/v1/attachment_service.proto
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.9
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc (unknown)
|
||||
// source: api/v1/auth_service.proto
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.9
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc (unknown)
|
||||
// source: api/v1/common.proto
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.9
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc (unknown)
|
||||
// source: api/v1/idp_service.proto
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.9
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc (unknown)
|
||||
// source: api/v1/inbox_service.proto
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.9
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc (unknown)
|
||||
// source: api/v1/markdown_service.proto
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.9
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc (unknown)
|
||||
// source: api/v1/memo_service.proto
|
||||
|
||||
|
|
@ -564,7 +564,9 @@ type ListMemosRequest struct {
|
|||
State State `protobuf:"varint,3,opt,name=state,proto3,enum=memos.api.v1.State" json:"state,omitempty"`
|
||||
// Optional. The order to sort results by.
|
||||
// Default to "display_time desc".
|
||||
// Example: "display_time desc" or "create_time asc"
|
||||
// Supports comma-separated list of fields following AIP-132.
|
||||
// Example: "pinned desc, display_time desc" or "create_time asc"
|
||||
// Supported fields: pinned, display_time, create_time, update_time, name
|
||||
OrderBy string `protobuf:"bytes,4,opt,name=order_by,json=orderBy,proto3" json:"order_by,omitempty"`
|
||||
// Optional. Filter to apply to the list results.
|
||||
// Filter is a CEL expression to filter memos.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.9
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc (unknown)
|
||||
// source: api/v1/shortcut_service.proto
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.9
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc (unknown)
|
||||
// source: api/v1/user_service.proto
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.9
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc (unknown)
|
||||
// source: api/v1/workspace_service.proto
|
||||
|
||||
|
|
|
|||
|
|
@ -656,7 +656,9 @@ paths:
|
|||
description: |-
|
||||
Optional. The order to sort results by.
|
||||
Default to "display_time desc".
|
||||
Example: "display_time desc" or "create_time asc"
|
||||
Supports comma-separated list of fields following AIP-132.
|
||||
Example: "pinned desc, display_time desc" or "create_time asc"
|
||||
Supported fields: pinned, display_time, create_time, update_time, name
|
||||
schema:
|
||||
type: string
|
||||
- name: filter
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.9
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc (unknown)
|
||||
// source: store/activity.proto
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.9
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc (unknown)
|
||||
// source: store/attachment.proto
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.9
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc (unknown)
|
||||
// source: store/idp.proto
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.9
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc (unknown)
|
||||
// source: store/inbox.proto
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.9
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc (unknown)
|
||||
// source: store/memo.proto
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.9
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc (unknown)
|
||||
// source: store/user_setting.proto
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.9
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc (unknown)
|
||||
// source: store/workspace_setting.proto
|
||||
|
||||
|
|
|
|||
|
|
@ -875,30 +875,55 @@ func substring(s string, length int) string {
|
|||
}
|
||||
|
||||
// parseMemoOrderBy parses the order_by field and sets the appropriate ordering in memoFind.
|
||||
// Follows AIP-132: supports comma-separated list of fields with optional "desc" suffix.
|
||||
// Example: "pinned desc, display_time desc" or "create_time asc".
|
||||
func (*APIV1Service) parseMemoOrderBy(orderBy string, memoFind *store.FindMemo) error {
|
||||
// Parse order_by field like "display_time desc" or "create_time asc"
|
||||
parts := strings.Fields(strings.TrimSpace(orderBy))
|
||||
if len(parts) == 0 {
|
||||
if strings.TrimSpace(orderBy) == "" {
|
||||
return errors.New("empty order_by")
|
||||
}
|
||||
|
||||
field := parts[0]
|
||||
direction := "desc" // default
|
||||
if len(parts) > 1 {
|
||||
direction = strings.ToLower(parts[1])
|
||||
if direction != "asc" && direction != "desc" {
|
||||
return errors.Errorf("invalid order direction: %s, must be 'asc' or 'desc'", parts[1])
|
||||
// Split by comma to support multiple sort fields per AIP-132.
|
||||
fields := strings.Split(orderBy, ",")
|
||||
|
||||
// Track if we've seen pinned field.
|
||||
hasPinned := false
|
||||
|
||||
for _, field := range fields {
|
||||
parts := strings.Fields(strings.TrimSpace(field))
|
||||
if len(parts) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
fieldName := parts[0]
|
||||
fieldDirection := "desc" // default per AIP-132 (we use desc as default for time fields)
|
||||
if len(parts) > 1 {
|
||||
fieldDirection = strings.ToLower(parts[1])
|
||||
if fieldDirection != "asc" && fieldDirection != "desc" {
|
||||
return errors.Errorf("invalid order direction: %s, must be 'asc' or 'desc'", parts[1])
|
||||
}
|
||||
}
|
||||
|
||||
switch fieldName {
|
||||
case "pinned":
|
||||
hasPinned = true
|
||||
memoFind.OrderByPinned = true
|
||||
// Note: pinned is always DESC (true first) regardless of direction specified.
|
||||
case "display_time", "create_time", "name":
|
||||
// Only set if this is the first time field we encounter.
|
||||
if !memoFind.OrderByUpdatedTs {
|
||||
memoFind.OrderByTimeAsc = fieldDirection == "asc"
|
||||
}
|
||||
case "update_time":
|
||||
memoFind.OrderByUpdatedTs = true
|
||||
memoFind.OrderByTimeAsc = fieldDirection == "asc"
|
||||
default:
|
||||
return errors.Errorf("unsupported order field: %s, supported fields are: pinned, display_time, create_time, update_time, name", fieldName)
|
||||
}
|
||||
}
|
||||
|
||||
switch field {
|
||||
case "display_time", "create_time", "name":
|
||||
memoFind.OrderByTimeAsc = direction == "asc"
|
||||
case "update_time":
|
||||
memoFind.OrderByUpdatedTs = true
|
||||
memoFind.OrderByTimeAsc = direction == "asc"
|
||||
default:
|
||||
return errors.Errorf("unsupported order field: %s, supported fields are: display_time, create_time, update_time, name", field)
|
||||
// If only pinned was specified, still need to set a default time ordering.
|
||||
if hasPinned && !memoFind.OrderByUpdatedTs && len(fields) == 1 {
|
||||
memoFind.OrderByTimeAsc = false // default to desc
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -106,6 +106,9 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
|
|||
order = "ASC"
|
||||
}
|
||||
orderBy := []string{}
|
||||
if find.OrderByPinned {
|
||||
orderBy = append(orderBy, "`pinned` DESC")
|
||||
}
|
||||
if find.OrderByUpdatedTs {
|
||||
orderBy = append(orderBy, "`updated_ts` "+order)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -97,6 +97,9 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
|
|||
order = "ASC"
|
||||
}
|
||||
orderBy := []string{}
|
||||
if find.OrderByPinned {
|
||||
orderBy = append(orderBy, "pinned DESC")
|
||||
}
|
||||
if find.OrderByUpdatedTs {
|
||||
orderBy = append(orderBy, "updated_ts "+order)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -98,6 +98,9 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
|
|||
order = "ASC"
|
||||
}
|
||||
orderBy := []string{}
|
||||
if find.OrderByPinned {
|
||||
orderBy = append(orderBy, "`pinned` DESC")
|
||||
}
|
||||
if find.OrderByUpdatedTs {
|
||||
orderBy = append(orderBy, "`updated_ts` "+order)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ type FindMemo struct {
|
|||
Offset *int
|
||||
|
||||
// Ordering
|
||||
OrderByPinned bool
|
||||
OrderByUpdatedTs bool
|
||||
OrderByTimeAsc bool
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ const (
|
|||
// Before 0.22, migration history had inconsistent versioning that needed normalization.
|
||||
migrationHistoryNormalizedVersion = "0.22"
|
||||
|
||||
// Mode constants for profile mode
|
||||
// Mode constants for profile mode.
|
||||
modeProd = "prod"
|
||||
modeDemo = "demo"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -34,14 +34,19 @@ const Archived = observer(() => {
|
|||
listSort={(memos: Memo[]) =>
|
||||
memos
|
||||
.filter((memo) => memo.state === State.ARCHIVED)
|
||||
.sort((a, b) =>
|
||||
viewStore.state.orderByTimeAsc
|
||||
.sort((a, b) => {
|
||||
// First, sort by pinned status (pinned memos first)
|
||||
if (a.pinned !== b.pinned) {
|
||||
return b.pinned ? 1 : -1;
|
||||
}
|
||||
// Then sort by display time
|
||||
return viewStore.state.orderByTimeAsc
|
||||
? dayjs(a.displayTime).unix() - dayjs(b.displayTime).unix()
|
||||
: dayjs(b.displayTime).unix() - dayjs(a.displayTime).unix(),
|
||||
)
|
||||
: dayjs(b.displayTime).unix() - dayjs(a.displayTime).unix();
|
||||
})
|
||||
}
|
||||
state={State.ARCHIVED}
|
||||
orderBy={viewStore.state.orderByTimeAsc ? "display_time asc" : "display_time desc"}
|
||||
orderBy={viewStore.state.orderByTimeAsc ? "pinned desc, display_time asc" : "pinned desc, display_time desc"}
|
||||
filter={memoFitler}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -63,13 +63,18 @@ const Home = observer(() => {
|
|||
listSort={(memos: Memo[]) =>
|
||||
memos
|
||||
.filter((memo) => memo.state === State.NORMAL)
|
||||
.sort((a, b) =>
|
||||
viewStore.state.orderByTimeAsc
|
||||
.sort((a, b) => {
|
||||
// First, sort by pinned status (pinned memos first)
|
||||
if (a.pinned !== b.pinned) {
|
||||
return b.pinned ? 1 : -1;
|
||||
}
|
||||
// Then sort by display time
|
||||
return viewStore.state.orderByTimeAsc
|
||||
? dayjs(a.displayTime).unix() - dayjs(b.displayTime).unix()
|
||||
: dayjs(b.displayTime).unix() - dayjs(a.displayTime).unix(),
|
||||
)
|
||||
: dayjs(b.displayTime).unix() - dayjs(a.displayTime).unix();
|
||||
})
|
||||
}
|
||||
orderBy={viewStore.state.orderByTimeAsc ? "display_time asc" : "display_time desc"}
|
||||
orderBy={viewStore.state.orderByTimeAsc ? "pinned desc, display_time asc" : "pinned desc, display_time desc"}
|
||||
filter={memoFilter}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -96,13 +96,18 @@ const UserProfile = observer(() => {
|
|||
listSort={(memos: Memo[]) =>
|
||||
memos
|
||||
.filter((memo) => memo.state === State.NORMAL)
|
||||
.sort((a, b) =>
|
||||
viewStore.state.orderByTimeAsc
|
||||
.sort((a, b) => {
|
||||
// First, sort by pinned status (pinned memos first)
|
||||
if (a.pinned !== b.pinned) {
|
||||
return b.pinned ? 1 : -1;
|
||||
}
|
||||
// Then sort by display time
|
||||
return viewStore.state.orderByTimeAsc
|
||||
? dayjs(a.displayTime).unix() - dayjs(b.displayTime).unix()
|
||||
: dayjs(b.displayTime).unix() - dayjs(a.displayTime).unix(),
|
||||
)
|
||||
: dayjs(b.displayTime).unix() - dayjs(a.displayTime).unix();
|
||||
})
|
||||
}
|
||||
orderBy={viewStore.state.orderByTimeAsc ? "display_time asc" : "display_time desc"}
|
||||
orderBy={viewStore.state.orderByTimeAsc ? "pinned desc, display_time asc" : "pinned desc, display_time desc"}
|
||||
filter={memoFilter}
|
||||
/>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -195,7 +195,9 @@ export interface ListMemosRequest {
|
|||
/**
|
||||
* Optional. The order to sort results by.
|
||||
* Default to "display_time desc".
|
||||
* Example: "display_time desc" or "create_time asc"
|
||||
* Supports comma-separated list of fields following AIP-132.
|
||||
* Example: "pinned desc, display_time desc" or "create_time asc"
|
||||
* Supported fields: pinned, display_time, create_time, update_time, name
|
||||
*/
|
||||
orderBy: string;
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue