Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
b4ea7d84
Commit
b4ea7d84
authored
Oct 20, 2025
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: enhance memo sorting functionality to support multiple fields
parent
95de5cc7
Changes
30
Hide whitespace changes
Inline
Side-by-side
Showing
30 changed files
with
113 additions
and
55 deletions
+113
-55
memo_service.proto
proto/api/v1/memo_service.proto
+3
-1
activity_service.pb.go
proto/gen/api/v1/activity_service.pb.go
+1
-1
attachment_service.pb.go
proto/gen/api/v1/attachment_service.pb.go
+1
-1
auth_service.pb.go
proto/gen/api/v1/auth_service.pb.go
+1
-1
common.pb.go
proto/gen/api/v1/common.pb.go
+1
-1
idp_service.pb.go
proto/gen/api/v1/idp_service.pb.go
+1
-1
inbox_service.pb.go
proto/gen/api/v1/inbox_service.pb.go
+1
-1
markdown_service.pb.go
proto/gen/api/v1/markdown_service.pb.go
+1
-1
memo_service.pb.go
proto/gen/api/v1/memo_service.pb.go
+4
-2
shortcut_service.pb.go
proto/gen/api/v1/shortcut_service.pb.go
+1
-1
user_service.pb.go
proto/gen/api/v1/user_service.pb.go
+1
-1
workspace_service.pb.go
proto/gen/api/v1/workspace_service.pb.go
+1
-1
openapi.yaml
proto/gen/openapi.yaml
+3
-1
activity.pb.go
proto/gen/store/activity.pb.go
+1
-1
attachment.pb.go
proto/gen/store/attachment.pb.go
+1
-1
idp.pb.go
proto/gen/store/idp.pb.go
+1
-1
inbox.pb.go
proto/gen/store/inbox.pb.go
+1
-1
memo.pb.go
proto/gen/store/memo.pb.go
+1
-1
user_setting.pb.go
proto/gen/store/user_setting.pb.go
+1
-1
workspace_setting.pb.go
proto/gen/store/workspace_setting.pb.go
+1
-1
memo_service.go
server/router/api/v1/memo_service.go
+42
-17
memo.go
store/db/mysql/memo.go
+3
-0
memo.go
store/db/postgres/memo.go
+3
-0
memo.go
store/db/sqlite/memo.go
+3
-0
memo.go
store/memo.go
+1
-0
migrator.go
store/migrator.go
+1
-1
Archived.tsx
web/src/pages/Archived.tsx
+10
-5
Home.tsx
web/src/pages/Home.tsx
+10
-5
UserProfile.tsx
web/src/pages/UserProfile.tsx
+10
-5
memo_service.ts
web/src/types/proto/api/v1/memo_service.ts
+3
-1
No files found.
proto/api/v1/memo_service.proto
View file @
b4ea7d84
...
...
@@ -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.
...
...
proto/gen/api/v1/activity_service.pb.go
View file @
b4ea7d84
// 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
...
...
proto/gen/api/v1/attachment_service.pb.go
View file @
b4ea7d84
// 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
...
...
proto/gen/api/v1/auth_service.pb.go
View file @
b4ea7d84
// 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
...
...
proto/gen/api/v1/common.pb.go
View file @
b4ea7d84
// 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
...
...
proto/gen/api/v1/idp_service.pb.go
View file @
b4ea7d84
// 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
...
...
proto/gen/api/v1/inbox_service.pb.go
View file @
b4ea7d84
// 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
...
...
proto/gen/api/v1/markdown_service.pb.go
View file @
b4ea7d84
// 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
...
...
proto/gen/api/v1/memo_service.pb.go
View file @
b4ea7d84
// 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.
...
...
proto/gen/api/v1/shortcut_service.pb.go
View file @
b4ea7d84
// 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
...
...
proto/gen/api/v1/user_service.pb.go
View file @
b4ea7d84
// 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
...
...
proto/gen/api/v1/workspace_service.pb.go
View file @
b4ea7d84
// 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
...
...
proto/gen/openapi.yaml
View file @
b4ea7d84
...
...
@@ -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
...
...
proto/gen/store/activity.pb.go
View file @
b4ea7d84
// 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
...
...
proto/gen/store/attachment.pb.go
View file @
b4ea7d84
// 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
...
...
proto/gen/store/idp.pb.go
View file @
b4ea7d84
// 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
...
...
proto/gen/store/inbox.pb.go
View file @
b4ea7d84
// 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
...
...
proto/gen/store/memo.pb.go
View file @
b4ea7d84
// 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
...
...
proto/gen/store/user_setting.pb.go
View file @
b4ea7d84
// 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
...
...
proto/gen/store/workspace_setting.pb.go
View file @
b4ea7d84
// 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
...
...
server/router/api/v1/memo_service.go
View file @
b4ea7d84
...
...
@@ -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
...
...
store/db/mysql/memo.go
View file @
b4ea7d84
...
...
@@ -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
{
...
...
store/db/postgres/memo.go
View file @
b4ea7d84
...
...
@@ -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
{
...
...
store/db/sqlite/memo.go
View file @
b4ea7d84
...
...
@@ -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
{
...
...
store/memo.go
View file @
b4ea7d84
...
...
@@ -76,6 +76,7 @@ type FindMemo struct {
Offset
*
int
// Ordering
OrderByPinned
bool
OrderByUpdatedTs
bool
OrderByTimeAsc
bool
}
...
...
store/migrator.go
View file @
b4ea7d84
...
...
@@ -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"
)
...
...
web/src/pages/Archived.tsx
View file @
b4ea7d84
...
...
@@ -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
}
/>
);
...
...
web/src/pages/Home.tsx
View file @
b4ea7d84
...
...
@@ -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
>
...
...
web/src/pages/UserProfile.tsx
View file @
b4ea7d84
...
...
@@ -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
}
/>
</>
...
...
web/src/types/proto/api/v1/memo_service.ts
View file @
b4ea7d84
...
...
@@ -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
;
/**
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment