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
68c2bd38
Commit
68c2bd38
authored
Oct 16, 2024
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore: update memo relations
parent
82da20e1
Changes
16
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
635 additions
and
502 deletions
+635
-502
memo_relation_service.proto
proto/api/v1/memo_relation_service.proto
+13
-2
memo_service.proto
proto/api/v1/memo_service.proto
+1
-1
memo_relation_service.pb.go
proto/gen/api/v1/memo_relation_service.pb.go
+120
-41
memo_service.pb.go
proto/gen/api/v1/memo_service.pb.go
+302
-302
apidocs.swagger.yaml
proto/gen/apidocs.swagger.yaml
+100
-84
memo_relation_service.go
server/router/api/v1/memo_relation_service.go
+44
-11
EmbeddedMemo.tsx
...c/components/MemoContent/EmbeddedContent/EmbeddedMemo.tsx
+1
-1
AddMemoRelationPopover.tsx
...onents/MemoEditor/ActionButton/AddMemoRelationPopover.tsx
+4
-4
RelationListView.tsx
web/src/components/MemoEditor/RelationListView.tsx
+2
-2
index.tsx
web/src/components/MemoEditor/index.tsx
+2
-1
MemoRelationForceGraph.tsx
...ponents/MemoRelationForceGraph/MemoRelationForceGraph.tsx
+14
-7
types.ts
web/src/components/MemoRelationForceGraph/types.ts
+2
-4
utils.ts
web/src/components/MemoRelationForceGraph/utils.ts
+10
-9
MemoRelationListView.tsx
web/src/components/MemoRelationListView.tsx
+16
-29
MemoView.tsx
web/src/components/MemoView.tsx
+1
-1
MemoDetail.tsx
web/src/pages/MemoDetail.tsx
+3
-3
No files found.
proto/api/v1/memo_relation_service.proto
View file @
68c2bd38
...
@@ -2,16 +2,18 @@ syntax = "proto3";
...
@@ -2,16 +2,18 @@ syntax = "proto3";
package
memos
.
api.v1
;
package
memos
.
api.v1
;
import
"google/api/field_behavior.proto"
;
option
go_package
=
"gen/api/v1"
;
option
go_package
=
"gen/api/v1"
;
message
MemoRelation
{
message
MemoRelation
{
// The name of memo.
// The name of memo.
// Format: "memos/{uid}"
// Format: "memos/{uid}"
string
memo
=
1
;
Memo
memo
=
1
;
// The name of related memo.
// The name of related memo.
// Format: "memos/{uid}"
// Format: "memos/{uid}"
string
related_memo
=
2
;
Memo
related_memo
=
2
;
enum
Type
{
enum
Type
{
TYPE_UNSPECIFIED
=
0
;
TYPE_UNSPECIFIED
=
0
;
...
@@ -19,4 +21,13 @@ message MemoRelation {
...
@@ -19,4 +21,13 @@ message MemoRelation {
COMMENT
=
2
;
COMMENT
=
2
;
}
}
Type
type
=
3
;
Type
type
=
3
;
message
Memo
{
// The name of the memo.
// Format: memos/{id}
string
name
=
1
;
string
uid
=
2
;
// The snippet of the memo content. Plain text only.
string
snippet
=
3
[(
google.api.field_behavior
)
=
OUTPUT_ONLY
];
}
}
}
proto/api/v1/memo_service.proto
View file @
68c2bd38
...
@@ -172,7 +172,7 @@ message Memo {
...
@@ -172,7 +172,7 @@ message Memo {
optional
string
parent
=
18
[(
google.api.field_behavior
)
=
OUTPUT_ONLY
];
optional
string
parent
=
18
[(
google.api.field_behavior
)
=
OUTPUT_ONLY
];
// The snippet of the memo content. Plain text only.
// The snippet of the memo content. Plain text only.
string
snippet
=
19
;
string
snippet
=
19
[(
google.api.field_behavior
)
=
OUTPUT_ONLY
]
;
// The location of the memo.
// The location of the memo.
optional
Location
location
=
20
;
optional
Location
location
=
20
;
...
...
proto/gen/api/v1/memo_relation_service.pb.go
View file @
68c2bd38
This diff is collapsed.
Click to expand it.
proto/gen/api/v1/memo_service.pb.go
View file @
68c2bd38
This diff is collapsed.
Click to expand it.
proto/gen/apidocs.swagger.yaml
View file @
68c2bd38
...
@@ -347,7 +347,7 @@ paths:
...
@@ -347,7 +347,7 @@ paths:
"
200"
:
"
200"
:
description
:
A successful response.
description
:
A successful response.
schema
:
schema
:
$ref
:
'
#/definitions/v1Memo'
$ref
:
'
#/definitions/
api
v1Memo'
default
:
default
:
description
:
An unexpected error response.
description
:
An unexpected error response.
schema
:
schema
:
...
@@ -368,7 +368,7 @@ paths:
...
@@ -368,7 +368,7 @@ paths:
"
200"
:
"
200"
:
description
:
A successful response.
description
:
A successful response.
schema
:
schema
:
$ref
:
'
#/definitions/v1Memo'
$ref
:
'
#/definitions/
api
v1Memo'
default
:
default
:
description
:
An unexpected error response.
description
:
An unexpected error response.
schema
:
schema
:
...
@@ -813,7 +813,7 @@ paths:
...
@@ -813,7 +813,7 @@ paths:
"
200"
:
"
200"
:
description
:
A successful response.
description
:
A successful response.
schema
:
schema
:
$ref
:
'
#/definitions/v1Memo'
$ref
:
'
#/definitions/
api
v1Memo'
default
:
default
:
description
:
An unexpected error response.
description
:
An unexpected error response.
schema
:
schema
:
...
@@ -897,6 +897,7 @@ paths:
...
@@ -897,6 +897,7 @@ paths:
snippet
:
snippet
:
type
:
string
type
:
string
description
:
The snippet of the memo content. Plain text only.
description
:
The snippet of the memo content. Plain text only.
readOnly
:
true
location
:
location
:
$ref
:
'
#/definitions/apiv1Location'
$ref
:
'
#/definitions/apiv1Location'
description
:
The location of the memo.
description
:
The location of the memo.
...
@@ -1056,7 +1057,7 @@ paths:
...
@@ -1056,7 +1057,7 @@ paths:
"
200"
:
"
200"
:
description
:
A successful response.
description
:
A successful response.
schema
:
schema
:
$ref
:
'
#/definitions/v1Memo'
$ref
:
'
#/definitions/
api
v1Memo'
default
:
default
:
description
:
An unexpected error response.
description
:
An unexpected error response.
schema
:
schema
:
...
@@ -1257,7 +1258,7 @@ paths:
...
@@ -1257,7 +1258,7 @@ paths:
"
200"
:
"
200"
:
description
:
A successful response.
description
:
A successful response.
schema
:
schema
:
$ref
:
'
#/definitions/v1Memo'
$ref
:
'
#/definitions/
api
v1Memo'
default
:
default
:
description
:
An unexpected error response.
description
:
An unexpected error response.
schema
:
schema
:
...
@@ -1952,6 +1953,82 @@ definitions:
...
@@ -1952,6 +1953,82 @@ definitions:
longitude
:
longitude
:
type
:
number
type
:
number
format
:
double
format
:
double
apiv1Memo
:
type
:
object
properties
:
name
:
type
:
string
description
:
|-
The name of the memo.
Format: memos/{id}
id is the system generated id.
uid
:
type
:
string
description
:
The user defined id of the memo.
rowStatus
:
$ref
:
'
#/definitions/v1RowStatus'
creator
:
type
:
string
title
:
|-
The name of the creator.
Format: users/{id}
createTime
:
type
:
string
format
:
date-time
updateTime
:
type
:
string
format
:
date-time
displayTime
:
type
:
string
format
:
date-time
content
:
type
:
string
nodes
:
type
:
array
items
:
type
:
object
$ref
:
'
#/definitions/v1Node'
readOnly
:
true
visibility
:
$ref
:
'
#/definitions/v1Visibility'
tags
:
type
:
array
items
:
type
:
string
pinned
:
type
:
boolean
resources
:
type
:
array
items
:
type
:
object
$ref
:
'
#/definitions/v1Resource'
relations
:
type
:
array
items
:
type
:
object
$ref
:
'
#/definitions/v1MemoRelation'
reactions
:
type
:
array
items
:
type
:
object
$ref
:
'
#/definitions/v1Reaction'
readOnly
:
true
property
:
$ref
:
'
#/definitions/v1MemoProperty'
readOnly
:
true
parent
:
type
:
string
title
:
|-
The name of the parent memo.
Format: memos/{id}
readOnly: true
snippet
:
type
:
string
description
:
The snippet of the memo content. Plain text only.
readOnly
:
true
location
:
$ref
:
'
#/definitions/apiv1Location'
description
:
The location of the memo.
apiv1OAuth2Config
:
apiv1OAuth2Config
:
type
:
object
type
:
object
properties
:
properties
:
...
@@ -2482,7 +2559,7 @@ definitions:
...
@@ -2482,7 +2559,7 @@ definitions:
type
:
array
type
:
array
items
:
items
:
type
:
object
type
:
object
$ref
:
'
#/definitions/v1Memo'
$ref
:
'
#/definitions/
api
v1Memo'
v1ListMemoReactionsResponse
:
v1ListMemoReactionsResponse
:
type
:
object
type
:
object
properties
:
properties
:
...
@@ -2514,7 +2591,7 @@ definitions:
...
@@ -2514,7 +2591,7 @@ definitions:
type
:
array
type
:
array
items
:
items
:
type
:
object
type
:
object
$ref
:
'
#/definitions/v1Memo'
$ref
:
'
#/definitions/
api
v1Memo'
nextPageToken
:
nextPageToken
:
type
:
string
type
:
string
description
:
|-
description
:
|-
...
@@ -2575,81 +2652,6 @@ definitions:
...
@@ -2575,81 +2652,6 @@ definitions:
properties
:
properties
:
content
:
content
:
type
:
string
type
:
string
v1Memo
:
type
:
object
properties
:
name
:
type
:
string
description
:
|-
The name of the memo.
Format: memos/{id}
id is the system generated id.
uid
:
type
:
string
description
:
The user defined id of the memo.
rowStatus
:
$ref
:
'
#/definitions/v1RowStatus'
creator
:
type
:
string
title
:
|-
The name of the creator.
Format: users/{id}
createTime
:
type
:
string
format
:
date-time
updateTime
:
type
:
string
format
:
date-time
displayTime
:
type
:
string
format
:
date-time
content
:
type
:
string
nodes
:
type
:
array
items
:
type
:
object
$ref
:
'
#/definitions/v1Node'
readOnly
:
true
visibility
:
$ref
:
'
#/definitions/v1Visibility'
tags
:
type
:
array
items
:
type
:
string
pinned
:
type
:
boolean
resources
:
type
:
array
items
:
type
:
object
$ref
:
'
#/definitions/v1Resource'
relations
:
type
:
array
items
:
type
:
object
$ref
:
'
#/definitions/v1MemoRelation'
reactions
:
type
:
array
items
:
type
:
object
$ref
:
'
#/definitions/v1Reaction'
readOnly
:
true
property
:
$ref
:
'
#/definitions/v1MemoProperty'
readOnly
:
true
parent
:
type
:
string
title
:
|-
The name of the parent memo.
Format: memos/{id}
readOnly: true
snippet
:
type
:
string
description
:
The snippet of the memo content. Plain text only.
location
:
$ref
:
'
#/definitions/apiv1Location'
description
:
The location of the memo.
v1MemoProperty
:
v1MemoProperty
:
type
:
object
type
:
object
properties
:
properties
:
...
@@ -2669,17 +2671,31 @@ definitions:
...
@@ -2669,17 +2671,31 @@ definitions:
type
:
object
type
:
object
properties
:
properties
:
memo
:
memo
:
type
:
string
$ref
:
'
#/definitions/v1MemoRelationMemo'
title
:
|-
title
:
|-
The name of memo.
The name of memo.
Format: "memos/{uid}"
Format: "memos/{uid}"
relatedMemo
:
relatedMemo
:
type
:
string
$ref
:
'
#/definitions/v1MemoRelationMemo'
title
:
|-
title
:
|-
The name of related memo.
The name of related memo.
Format: "memos/{uid}"
Format: "memos/{uid}"
type
:
type
:
$ref
:
'
#/definitions/v1MemoRelationType'
$ref
:
'
#/definitions/v1MemoRelationType'
v1MemoRelationMemo
:
type
:
object
properties
:
name
:
type
:
string
title
:
|-
The name of the memo.
Format: memos/{id}
uid
:
type
:
string
snippet
:
type
:
string
description
:
The snippet of the memo content. Plain text only.
readOnly
:
true
v1MemoRelationType
:
v1MemoRelationType
:
type
:
string
type
:
string
enum
:
enum
:
...
...
server/router/api/v1/memo_relation_service.go
View file @
68c2bd38
...
@@ -8,6 +8,7 @@ import (
...
@@ -8,6 +8,7 @@ import (
"google.golang.org/grpc/status"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/emptypb"
"github.com/pkg/errors"
v1pb
"github.com/usememos/memos/proto/gen/api/v1"
v1pb
"github.com/usememos/memos/proto/gen/api/v1"
"github.com/usememos/memos/store"
"github.com/usememos/memos/store"
)
)
...
@@ -28,7 +29,7 @@ func (s *APIV1Service) SetMemoRelations(ctx context.Context, request *v1pb.SetMe
...
@@ -28,7 +29,7 @@ func (s *APIV1Service) SetMemoRelations(ctx context.Context, request *v1pb.SetMe
for
_
,
relation
:=
range
request
.
Relations
{
for
_
,
relation
:=
range
request
.
Relations
{
// Ignore reflexive relations.
// Ignore reflexive relations.
if
request
.
Name
==
relation
.
RelatedMemo
{
if
request
.
Name
==
relation
.
RelatedMemo
.
Name
{
continue
continue
}
}
// Ignore comment relations as there's no need to update a comment's relation.
// Ignore comment relations as there's no need to update a comment's relation.
...
@@ -36,7 +37,7 @@ func (s *APIV1Service) SetMemoRelations(ctx context.Context, request *v1pb.SetMe
...
@@ -36,7 +37,7 @@ func (s *APIV1Service) SetMemoRelations(ctx context.Context, request *v1pb.SetMe
if
relation
.
Type
==
v1pb
.
MemoRelation_COMMENT
{
if
relation
.
Type
==
v1pb
.
MemoRelation_COMMENT
{
continue
continue
}
}
relatedMemoID
,
err
:=
ExtractMemoIDFromName
(
relation
.
RelatedMemo
)
relatedMemoID
,
err
:=
ExtractMemoIDFromName
(
relation
.
RelatedMemo
.
Name
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid related memo name: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid related memo name: %v"
,
err
)
}
}
...
@@ -64,8 +65,12 @@ func (s *APIV1Service) ListMemoRelations(ctx context.Context, request *v1pb.List
...
@@ -64,8 +65,12 @@ func (s *APIV1Service) ListMemoRelations(ctx context.Context, request *v1pb.List
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
for
_
,
relation
:=
range
tempList
{
for
_
,
raw
:=
range
tempList
{
relationList
=
append
(
relationList
,
convertMemoRelationFromStore
(
relation
))
relation
,
err
:=
s
.
convertMemoRelationFromStore
(
ctx
,
raw
)
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to convert memo relation"
)
}
relationList
=
append
(
relationList
,
relation
)
}
}
tempList
,
err
=
s
.
Store
.
ListMemoRelations
(
ctx
,
&
store
.
FindMemoRelation
{
tempList
,
err
=
s
.
Store
.
ListMemoRelations
(
ctx
,
&
store
.
FindMemoRelation
{
RelatedMemoID
:
&
id
,
RelatedMemoID
:
&
id
,
...
@@ -73,8 +78,12 @@ func (s *APIV1Service) ListMemoRelations(ctx context.Context, request *v1pb.List
...
@@ -73,8 +78,12 @@ func (s *APIV1Service) ListMemoRelations(ctx context.Context, request *v1pb.List
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
for
_
,
relation
:=
range
tempList
{
for
_
,
raw
:=
range
tempList
{
relationList
=
append
(
relationList
,
convertMemoRelationFromStore
(
relation
))
relation
,
err
:=
s
.
convertMemoRelationFromStore
(
ctx
,
raw
)
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to convert memo relation"
)
}
relationList
=
append
(
relationList
,
relation
)
}
}
response
:=
&
v1pb
.
ListMemoRelationsResponse
{
response
:=
&
v1pb
.
ListMemoRelationsResponse
{
...
@@ -83,12 +92,36 @@ func (s *APIV1Service) ListMemoRelations(ctx context.Context, request *v1pb.List
...
@@ -83,12 +92,36 @@ func (s *APIV1Service) ListMemoRelations(ctx context.Context, request *v1pb.List
return
response
,
nil
return
response
,
nil
}
}
func
convertMemoRelationFromStore
(
memoRelation
*
store
.
MemoRelation
)
*
v1pb
.
MemoRelation
{
func
(
s
*
APIV1Service
)
convertMemoRelationFromStore
(
ctx
context
.
Context
,
memoRelation
*
store
.
MemoRelation
)
(
*
v1pb
.
MemoRelation
,
error
)
{
memo
,
err
:=
s
.
Store
.
GetMemo
(
ctx
,
&
store
.
FindMemo
{
ID
:
&
memoRelation
.
MemoID
})
if
err
!=
nil
{
return
nil
,
err
}
memoSnippet
,
err
:=
getMemoContentSnippet
(
memo
.
Content
)
if
err
!=
nil
{
return
nil
,
errors
.
Wrap
(
err
,
"failed to get memo content snippet"
)
}
relatedMemo
,
err
:=
s
.
Store
.
GetMemo
(
ctx
,
&
store
.
FindMemo
{
ID
:
&
memoRelation
.
RelatedMemoID
})
if
err
!=
nil
{
return
nil
,
err
}
relatedMemoSnippet
,
err
:=
getMemoContentSnippet
(
relatedMemo
.
Content
)
if
err
!=
nil
{
return
nil
,
errors
.
Wrap
(
err
,
"failed to get related memo content snippet"
)
}
return
&
v1pb
.
MemoRelation
{
return
&
v1pb
.
MemoRelation
{
Memo
:
fmt
.
Sprintf
(
"%s%d"
,
MemoNamePrefix
,
memoRelation
.
MemoID
),
Memo
:
&
v1pb
.
MemoRelation_Memo
{
RelatedMemo
:
fmt
.
Sprintf
(
"%s%d"
,
MemoNamePrefix
,
memoRelation
.
RelatedMemoID
),
Name
:
fmt
.
Sprintf
(
"%s%d"
,
MemoNamePrefix
,
memo
.
ID
),
Uid
:
memo
.
UID
,
Snippet
:
memoSnippet
,
},
RelatedMemo
:
&
v1pb
.
MemoRelation_Memo
{
Name
:
fmt
.
Sprintf
(
"%s%d"
,
MemoNamePrefix
,
relatedMemo
.
ID
),
Uid
:
relatedMemo
.
UID
,
Snippet
:
relatedMemoSnippet
,
},
Type
:
convertMemoRelationTypeFromStore
(
memoRelation
.
Type
),
Type
:
convertMemoRelationTypeFromStore
(
memoRelation
.
Type
),
}
}
,
nil
}
}
func
convertMemoRelationTypeFromStore
(
relationType
store
.
MemoRelationType
)
v1pb
.
MemoRelation_Type
{
func
convertMemoRelationTypeFromStore
(
relationType
store
.
MemoRelationType
)
v1pb
.
MemoRelation_Type
{
...
...
web/src/components/MemoContent/EmbeddedContent/EmbeddedMemo.tsx
View file @
68c2bd38
...
@@ -73,7 +73,7 @@ const EmbeddedMemo = ({ resourceId: uid, params: paramsStr }: Props) => {
...
@@ -73,7 +73,7 @@ const EmbeddedMemo = ({ resourceId: uid, params: paramsStr }: Props) => {
</
div
>
</
div
>
<
div
className=
"flex justify-end items-center gap-1"
>
<
div
className=
"flex justify-end items-center gap-1"
>
<
span
className=
"text-xs opacity-60 leading-5 cursor-pointer hover:opacity-80"
onClick=
{
()
=>
copyMemoUid
(
memo
.
uid
)
}
>
<
span
className=
"text-xs opacity-60 leading-5 cursor-pointer hover:opacity-80"
onClick=
{
()
=>
copyMemoUid
(
memo
.
uid
)
}
>
{
memo
.
uid
.
slice
(
0
,
8
)
}
{
memo
.
uid
.
slice
(
0
,
6
)
}
</
span
>
</
span
>
<
Link
className=
"opacity-60 hover:opacity-80"
to=
{
`/m/${memo.uid}`
}
unstable_viewTransition
>
<
Link
className=
"opacity-60 hover:opacity-80"
to=
{
`/m/${memo.uid}`
}
unstable_viewTransition
>
<
ArrowUpRightIcon
className=
"w-5 h-auto"
/>
<
ArrowUpRightIcon
className=
"w-5 h-auto"
/>
...
...
web/src/components/MemoEditor/ActionButton/AddMemoRelationPopover.tsx
View file @
68c2bd38
...
@@ -8,7 +8,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/Popover
...
@@ -8,7 +8,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/Popover
import
{
memoServiceClient
}
from
"@/grpcweb"
;
import
{
memoServiceClient
}
from
"@/grpcweb"
;
import
{
DEFAULT_LIST_MEMOS_PAGE_SIZE
}
from
"@/helpers/consts"
;
import
{
DEFAULT_LIST_MEMOS_PAGE_SIZE
}
from
"@/helpers/consts"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
{
MemoRelation_Type
}
from
"@/types/proto/api/v1/memo_relation_service"
;
import
{
MemoRelation_
Memo
,
MemoRelation_
Type
}
from
"@/types/proto/api/v1/memo_relation_service"
;
import
{
Memo
,
MemoView
}
from
"@/types/proto/api/v1/memo_service"
;
import
{
Memo
,
MemoView
}
from
"@/types/proto/api/v1/memo_service"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
EditorRefActions
}
from
"../Editor"
;
import
{
EditorRefActions
}
from
"../Editor"
;
...
@@ -34,7 +34,7 @@ const AddMemoRelationPopover = (props: Props) => {
...
@@ -34,7 +34,7 @@ const AddMemoRelationPopover = (props: Props) => {
(
memo
)
=>
(
memo
)
=>
!
selectedMemos
.
includes
(
memo
)
&&
!
selectedMemos
.
includes
(
memo
)
&&
memo
.
name
!==
context
.
memoName
&&
memo
.
name
!==
context
.
memoName
&&
!
context
.
relationList
.
some
((
relation
)
=>
relation
.
relatedMemo
===
memo
.
name
),
!
context
.
relationList
.
some
((
relation
)
=>
relation
.
relatedMemo
?.
name
===
memo
.
name
),
);
);
useDebounce
(
useDebounce
(
...
@@ -112,8 +112,8 @@ const AddMemoRelationPopover = (props: Props) => {
...
@@ -112,8 +112,8 @@ const AddMemoRelationPopover = (props: Props) => {
uniqBy
(
uniqBy
(
[
[
...
selectedMemos
.
map
((
memo
)
=>
({
...
selectedMemos
.
map
((
memo
)
=>
({
memo
:
context
.
memoName
||
""
,
memo
:
MemoRelation_Memo
.
fromPartial
({
name
:
memo
.
name
})
,
relatedMemo
:
memo
.
name
,
relatedMemo
:
MemoRelation_Memo
.
fromPartial
({
name
:
memo
.
name
})
,
type
:
MemoRelation_Type
.
REFERENCE
,
type
:
MemoRelation_Type
.
REFERENCE
,
})),
})),
...
context
.
relationList
,
...
context
.
relationList
,
...
...
web/src/components/MemoEditor/RelationListView.tsx
View file @
68c2bd38
...
@@ -19,7 +19,7 @@ const RelationListView = (props: Props) => {
...
@@ -19,7 +19,7 @@ const RelationListView = (props: Props) => {
const
requests
=
relationList
const
requests
=
relationList
.
filter
((
relation
)
=>
relation
.
type
===
MemoRelation_Type
.
REFERENCE
)
.
filter
((
relation
)
=>
relation
.
type
===
MemoRelation_Type
.
REFERENCE
)
.
map
(
async
(
relation
)
=>
{
.
map
(
async
(
relation
)
=>
{
return
await
memoStore
.
getOrFetchMemoByName
(
relation
.
relatedMemo
,
{
skipStore
:
true
});
return
await
memoStore
.
getOrFetchMemoByName
(
relation
.
relatedMemo
!
.
name
,
{
skipStore
:
true
});
});
});
const
list
=
await
Promise
.
all
(
requests
);
const
list
=
await
Promise
.
all
(
requests
);
setReferencingMemoList
(
list
);
setReferencingMemoList
(
list
);
...
@@ -27,7 +27,7 @@ const RelationListView = (props: Props) => {
...
@@ -27,7 +27,7 @@ const RelationListView = (props: Props) => {
},
[
relationList
]);
},
[
relationList
]);
const
handleDeleteRelation
=
async
(
memo
:
Memo
)
=>
{
const
handleDeleteRelation
=
async
(
memo
:
Memo
)
=>
{
setRelationList
(
relationList
.
filter
((
relation
)
=>
relation
.
relatedMemo
!==
memo
.
name
));
setRelationList
(
relationList
.
filter
((
relation
)
=>
relation
.
relatedMemo
?.
name
!==
memo
.
name
));
};
};
return
(
return
(
...
...
web/src/components/MemoEditor/index.tsx
View file @
68c2bd38
...
@@ -80,7 +80,8 @@ const MemoEditor = (props: Props) => {
...
@@ -80,7 +80,8 @@ const MemoEditor = (props: Props) => {
const
[
contentCache
,
setContentCache
]
=
useLocalStorage
<
string
>
(
contentCacheKey
,
""
);
const
[
contentCache
,
setContentCache
]
=
useLocalStorage
<
string
>
(
contentCacheKey
,
""
);
const
referenceRelations
=
memoName
const
referenceRelations
=
memoName
?
state
.
relationList
.
filter
(
?
state
.
relationList
.
filter
(
(
relation
)
=>
relation
.
memo
===
memoName
&&
relation
.
relatedMemo
!==
memoName
&&
relation
.
type
===
MemoRelation_Type
.
REFERENCE
,
(
relation
)
=>
relation
.
memo
?.
name
===
memoName
&&
relation
.
relatedMemo
?.
name
!==
memoName
&&
relation
.
type
===
MemoRelation_Type
.
REFERENCE
,
)
)
:
state
.
relationList
.
filter
((
relation
)
=>
relation
.
type
===
MemoRelation_Type
.
REFERENCE
);
:
state
.
relationList
.
filter
((
relation
)
=>
relation
.
type
===
MemoRelation_Type
.
REFERENCE
);
const
workspaceMemoRelatedSetting
=
const
workspaceMemoRelatedSetting
=
...
...
web/src/components/MemoRelationForceGraph/MemoRelationForceGraph.tsx
View file @
68c2bd38
import
{
useColorScheme
}
from
"@mui/joy"
;
import
{
useColorScheme
}
from
"@mui/joy"
;
import
clsx
from
"clsx"
;
import
clsx
from
"clsx"
;
import
{
useEffect
,
useRef
,
useState
}
from
"react"
;
import
{
useEffect
,
useRef
,
useState
}
from
"react"
;
import
ForceGraph2D
from
"react-force-graph-2d"
;
import
ForceGraph2D
,
{
ForceGraphMethods
,
LinkObject
,
NodeObject
}
from
"react-force-graph-2d"
;
import
useNavigateTo
from
"@/hooks/useNavigateTo"
;
import
{
Memo
}
from
"@/types/proto/api/v1/memo_service"
;
import
{
Memo
}
from
"@/types/proto/api/v1/memo_service"
;
import
{
FGMethods
}
from
"./types"
;
import
{
LinkType
,
NodeType
}
from
"./types"
;
import
{
convertMemoRelationsToGraphData
}
from
"./utils"
;
import
{
convertMemoRelationsToGraphData
}
from
"./utils"
;
interface
Props
{
interface
Props
{
...
@@ -15,9 +16,10 @@ const MAIN_NODE_COLOR = "#14b8a6";
...
@@ -15,9 +16,10 @@ const MAIN_NODE_COLOR = "#14b8a6";
const
DEFAULT_NODE_COLOR
=
"#a1a1aa"
;
const
DEFAULT_NODE_COLOR
=
"#a1a1aa"
;
const
MemoRelationForceGraph
=
({
className
,
memo
}:
Props
)
=>
{
const
MemoRelationForceGraph
=
({
className
,
memo
}:
Props
)
=>
{
const
navigateTo
=
useNavigateTo
();
const
{
mode
}
=
useColorScheme
();
const
{
mode
}
=
useColorScheme
();
const
containerRef
=
useRef
<
HTMLDivElement
>
(
null
);
const
containerRef
=
useRef
<
HTMLDivElement
>
(
null
);
const
graphRef
=
useRef
<
F
GMethods
|
undefined
>
(
undefined
);
const
graphRef
=
useRef
<
F
orceGraphMethods
<
NodeObject
<
NodeType
>
,
LinkObject
<
NodeType
,
LinkType
>>
|
undefined
>
(
undefined
);
const
[
graphSize
,
setGraphSize
]
=
useState
({
width
:
0
,
height
:
0
});
const
[
graphSize
,
setGraphSize
]
=
useState
({
width
:
0
,
height
:
0
});
useEffect
(()
=>
{
useEffect
(()
=>
{
...
@@ -25,8 +27,9 @@ const MemoRelationForceGraph = ({ className, memo }: Props) => {
...
@@ -25,8 +27,9 @@ const MemoRelationForceGraph = ({ className, memo }: Props) => {
setGraphSize
(
containerRef
.
current
.
getBoundingClientRect
());
setGraphSize
(
containerRef
.
current
.
getBoundingClientRect
());
},
[]);
},
[]);
const
onNodeClick
=
()
=>
{
const
onNodeClick
=
(
node
:
NodeObject
<
NodeType
>
)
=>
{
// TODO: Handle node click event
if
(
node
.
memo
.
uid
===
memo
.
uid
)
return
;
navigateTo
(
`/m/
${
node
.
memo
.
uid
}
`
);
};
};
return
(
return
(
...
@@ -37,11 +40,15 @@ const MemoRelationForceGraph = ({ className, memo }: Props) => {
...
@@ -37,11 +40,15 @@ const MemoRelationForceGraph = ({ className, memo }: Props) => {
height=
{
graphSize
.
height
}
height=
{
graphSize
.
height
}
enableZoomInteraction
enableZoomInteraction
cooldownTicks=
{
0
}
cooldownTicks=
{
0
}
nodeColor=
{
(
node
)
=>
(
node
.
name
===
memo
.
name
?
MAIN_NODE_COLOR
:
DEFAULT_NODE_COLOR
)
}
nodeColor=
{
(
node
)
=>
(
node
.
id
===
memo
.
name
?
MAIN_NODE_COLOR
:
DEFAULT_NODE_COLOR
)
}
nodeRelSize=
{
3
}
nodeRelSize=
{
3
}
linkColor=
{
()
=>
(
mode
===
"light"
?
""
:
"#525252"
)
}
nodeLabel=
{
(
node
)
=>
node
.
memo
.
uid
.
slice
(
0
,
6
).
toLowerCase
()
}
linkColor=
{
()
=>
(
mode
===
"light"
?
"#e4e4e7"
:
"#3f3f46"
)
}
graphData=
{
convertMemoRelationsToGraphData
(
memo
.
relations
)
}
graphData=
{
convertMemoRelationsToGraphData
(
memo
.
relations
)
}
onNodeClick=
{
onNodeClick
}
onNodeClick=
{
onNodeClick
}
linkDirectionalArrowLength=
{
3
}
linkDirectionalArrowRelPos=
{
1
}
linkCurvature=
{
0.25
}
/>
/>
</
div
>
</
div
>
);
);
...
...
web/src/components/MemoRelationForceGraph/types.ts
View file @
68c2bd38
import
{
ForceGraphMethods
,
LinkObject
,
NodeObject
}
from
"react-force-graph-2d
"
;
import
{
MemoRelation_Memo
}
from
"@/types/proto/api/v1/memo_relation_service
"
;
export
interface
NodeType
{
export
interface
NodeType
{
name
:
string
;
memo
:
MemoRelation_Memo
;
}
}
export
interface
LinkType
{
export
interface
LinkType
{
// ...add more additional properties relevant to the link here.
// ...add more additional properties relevant to the link here.
}
}
export
interface
FGMethods
extends
ForceGraphMethods
<
NodeObject
<
NodeType
>
,
LinkObject
<
NodeType
,
LinkType
>>
{}
web/src/components/MemoRelationForceGraph/utils.ts
View file @
68c2bd38
import
{
GraphData
,
LinkObject
,
NodeObject
}
from
"react-force-graph-2d"
;
import
{
GraphData
,
LinkObject
,
NodeObject
}
from
"react-force-graph-2d"
;
import
{
MemoRelation
}
from
"@/types/proto/api/v1/memo_relation_service"
;
import
{
MemoRelation
,
MemoRelation_Memo
}
from
"@/types/proto/api/v1/memo_relation_service"
;
import
{
LinkType
,
NodeType
}
from
"./types"
;
import
{
LinkType
,
NodeType
}
from
"./types"
;
export
const
convertMemoRelationsToGraphData
=
(
memoRelations
:
MemoRelation
[]):
GraphData
<
NodeType
,
LinkType
>
=>
{
export
const
convertMemoRelationsToGraphData
=
(
memoRelations
:
MemoRelation
[]):
GraphData
<
NodeType
,
LinkType
>
=>
{
...
@@ -8,23 +8,24 @@ export const convertMemoRelationsToGraphData = (memoRelations: MemoRelation[]):
...
@@ -8,23 +8,24 @@ export const convertMemoRelationsToGraphData = (memoRelations: MemoRelation[]):
// Iterate through memoRelations to populate nodes and links.
// Iterate through memoRelations to populate nodes and links.
memoRelations
.
forEach
((
relation
)
=>
{
memoRelations
.
forEach
((
relation
)
=>
{
const
{
memo
,
relatedMemo
,
type
}
=
relation
;
const
memo
=
relation
.
memo
as
MemoRelation_Memo
;
const
relatedMemo
=
relation
.
relatedMemo
as
MemoRelation_Memo
;
// Add memo node if not already present.
// Add memo node if not already present.
if
(
!
nodesMap
.
has
(
memo
))
{
if
(
!
nodesMap
.
has
(
memo
.
name
))
{
nodesMap
.
set
(
memo
,
{
id
:
memo
,
name
:
memo
});
nodesMap
.
set
(
memo
.
name
,
{
id
:
memo
.
name
,
memo
});
}
}
// Add related_memo node if not already present.
// Add related_memo node if not already present.
if
(
!
nodesMap
.
has
(
relatedMemo
))
{
if
(
!
nodesMap
.
has
(
relatedMemo
.
name
))
{
nodesMap
.
set
(
relatedMemo
,
{
id
:
relatedMemo
,
name
:
relatedMemo
});
nodesMap
.
set
(
relatedMemo
.
name
,
{
id
:
relatedMemo
.
name
,
memo
:
relatedMemo
});
}
}
// Create link between memo and relatedMemo.
// Create link between memo and relatedMemo.
links
.
push
({
links
.
push
({
source
:
memo
,
source
:
memo
.
name
,
target
:
relatedMemo
,
target
:
relatedMemo
.
name
,
type
,
// Include the type of relation as a property of the link.
type
:
relation
.
type
,
// Include the type of relation as a property of the link.
});
});
});
});
...
...
web/src/components/MemoRelationListView.tsx
View file @
68c2bd38
import
clsx
from
"clsx"
;
import
clsx
from
"clsx"
;
import
{
DotIcon
,
LinkIcon
,
MilestoneIcon
}
from
"lucide-react"
;
import
{
LinkIcon
,
MilestoneIcon
}
from
"lucide-react"
;
import
{
memo
,
useState
}
from
"react"
;
import
{
memo
,
useState
}
from
"react"
;
import
{
Link
}
from
"react-router-dom"
;
import
{
Link
}
from
"react-router-dom"
;
import
useAsyncEffect
from
"@/hooks/useAsyncEffect"
;
import
{
useMemoStore
}
from
"@/store/v1"
;
import
{
MemoRelation
}
from
"@/types/proto/api/v1/memo_relation_service"
;
import
{
MemoRelation
}
from
"@/types/proto/api/v1/memo_relation_service"
;
import
{
Memo
}
from
"@/types/proto/api/v1/memo_service"
;
import
{
Memo
}
from
"@/types/proto/api/v1/memo_service"
;
...
@@ -14,30 +12,15 @@ interface Props {
...
@@ -14,30 +12,15 @@ interface Props {
const
MemoRelationListView
=
(
props
:
Props
)
=>
{
const
MemoRelationListView
=
(
props
:
Props
)
=>
{
const
{
memo
,
relations
:
relationList
}
=
props
;
const
{
memo
,
relations
:
relationList
}
=
props
;
const
memoStore
=
useMemoStore
();
const
referencingMemoList
=
relationList
const
[
referencingMemoList
,
setReferencingMemoList
]
=
useState
<
Memo
[]
>
([]);
.
filter
((
relation
)
=>
relation
.
memo
?.
name
===
memo
.
name
&&
relation
.
relatedMemo
?.
name
!==
memo
.
name
)
const
[
referencedMemoList
,
setReferencedMemoList
]
=
useState
<
Memo
[]
>
([]);
.
map
((
relation
)
=>
relation
.
relatedMemo
!
);
const
[
selectedTab
,
setSelectedTab
]
=
useState
<
"referencing"
|
"referenced"
>
(
"referencing"
);
const
referencedMemoList
=
relationList
.
filter
((
relation
)
=>
relation
.
memo
?.
name
!==
memo
.
name
&&
relation
.
relatedMemo
?.
name
===
memo
.
name
)
useAsyncEffect
(
async
()
=>
{
.
map
((
relation
)
=>
relation
.
memo
!
);
const
referencingMemoList
=
await
Promise
.
all
(
const
[
selectedTab
,
setSelectedTab
]
=
useState
<
"referencing"
|
"referenced"
>
(
relationList
referencingMemoList
.
length
===
0
?
"referenced"
:
"referencing"
,
.
filter
((
relation
)
=>
relation
.
memo
===
memo
.
name
&&
relation
.
relatedMemo
!==
memo
.
name
)
.
map
((
relation
)
=>
memoStore
.
getOrFetchMemoByName
(
relation
.
relatedMemo
,
{
skipStore
:
true
})),
);
setReferencingMemoList
(
referencingMemoList
);
const
referencedMemoList
=
await
Promise
.
all
(
relationList
.
filter
((
relation
)
=>
relation
.
memo
!==
memo
.
name
&&
relation
.
relatedMemo
===
memo
.
name
)
.
map
((
relation
)
=>
memoStore
.
getOrFetchMemoByName
(
relation
.
memo
,
{
skipStore
:
true
})),
);
);
setReferencedMemoList
(
referencedMemoList
);
if
(
referencingMemoList
.
length
===
0
)
{
setSelectedTab
(
"referenced"
);
}
else
{
setSelectedTab
(
"referencing"
);
}
},
[
memo
.
name
,
relationList
]);
if
(
referencingMemoList
.
length
+
referencedMemoList
.
length
===
0
)
{
if
(
referencingMemoList
.
length
+
referencedMemoList
.
length
===
0
)
{
return
null
;
return
null
;
...
@@ -83,7 +66,9 @@ const MemoRelationListView = (props: Props) => {
...
@@ -83,7 +66,9 @@ const MemoRelationListView = (props: Props) => {
to=
{
`/m/${memo.uid}`
}
to=
{
`/m/${memo.uid}`
}
unstable_viewTransition
unstable_viewTransition
>
>
<
DotIcon
className=
"shrink-0 w-4 h-auto opacity-40"
/>
<
span
className=
"text-xs opacity-60 leading-4 border font-mono px-1 rounded-full mr-1 dark:border-zinc-700"
>
{
memo
.
uid
.
slice
(
0
,
6
)
}
</
span
>
<
span
className=
"truncate"
>
{
memo
.
snippet
}
</
span
>
<
span
className=
"truncate"
>
{
memo
.
snippet
}
</
span
>
</
Link
>
</
Link
>
);
);
...
@@ -100,7 +85,9 @@ const MemoRelationListView = (props: Props) => {
...
@@ -100,7 +85,9 @@ const MemoRelationListView = (props: Props) => {
to=
{
`/m/${memo.uid}`
}
to=
{
`/m/${memo.uid}`
}
unstable_viewTransition
unstable_viewTransition
>
>
<
DotIcon
className=
"shrink-0 w-4 h-auto opacity-40"
/>
<
span
className=
"text-xs opacity-60 leading-4 border font-mono px-1 rounded-full mr-1 dark:border-zinc-700"
>
{
memo
.
uid
.
slice
(
0
,
6
)
}
</
span
>
<
span
className=
"truncate"
>
{
memo
.
snippet
}
</
span
>
<
span
className=
"truncate"
>
{
memo
.
snippet
}
</
span
>
</
Link
>
</
Link
>
);
);
...
...
web/src/components/MemoView.tsx
View file @
68c2bd38
...
@@ -52,7 +52,7 @@ const MemoView: React.FC<Props> = (props: Props) => {
...
@@ -52,7 +52,7 @@ const MemoView: React.FC<Props> = (props: Props) => {
const
memoContainerRef
=
useRef
<
HTMLDivElement
>
(
null
);
const
memoContainerRef
=
useRef
<
HTMLDivElement
>
(
null
);
const
referencedMemos
=
memo
.
relations
.
filter
((
relation
)
=>
relation
.
type
===
MemoRelation_Type
.
REFERENCE
);
const
referencedMemos
=
memo
.
relations
.
filter
((
relation
)
=>
relation
.
type
===
MemoRelation_Type
.
REFERENCE
);
const
commentAmount
=
memo
.
relations
.
filter
(
const
commentAmount
=
memo
.
relations
.
filter
(
(
relation
)
=>
relation
.
type
===
MemoRelation_Type
.
COMMENT
&&
relation
.
relatedMemo
===
memo
.
name
,
(
relation
)
=>
relation
.
type
===
MemoRelation_Type
.
COMMENT
&&
relation
.
relatedMemo
?.
name
===
memo
.
name
,
).
length
;
).
length
;
const
relativeTimeFormat
=
Date
.
now
()
-
memo
.
displayTime
!
.
getTime
()
>
1000
*
60
*
60
*
24
?
"datetime"
:
"auto"
;
const
relativeTimeFormat
=
Date
.
now
()
-
memo
.
displayTime
!
.
getTime
()
>
1000
*
60
*
60
*
24
?
"datetime"
:
"auto"
;
const
readonly
=
memo
.
creator
!==
user
?.
name
&&
!
isSuperUser
(
user
);
const
readonly
=
memo
.
creator
!==
user
?.
name
&&
!
isSuperUser
(
user
);
...
...
web/src/pages/MemoDetail.tsx
View file @
68c2bd38
...
@@ -34,8 +34,8 @@ const MemoDetail = () => {
...
@@ -34,8 +34,8 @@ const MemoDetail = () => {
const
[
parentMemo
,
setParentMemo
]
=
useState
<
Memo
|
undefined
>
(
undefined
);
const
[
parentMemo
,
setParentMemo
]
=
useState
<
Memo
|
undefined
>
(
undefined
);
const
[
showCommentEditor
,
setShowCommentEditor
]
=
useState
(
false
);
const
[
showCommentEditor
,
setShowCommentEditor
]
=
useState
(
false
);
const
commentRelations
=
const
commentRelations
=
memo
?.
relations
.
filter
((
relation
)
=>
relation
.
relatedMemo
===
memo
.
name
&&
relation
.
type
===
MemoRelation_Type
.
COMMENT
)
||
[];
memo
?.
relations
.
filter
((
relation
)
=>
relation
.
relatedMemo
?.
name
===
memo
.
name
&&
relation
.
type
===
MemoRelation_Type
.
COMMENT
)
||
[];
const
comments
=
commentRelations
.
map
((
relation
)
=>
memoStore
.
getMemoByName
(
relation
.
memo
)).
filter
((
memo
)
=>
memo
)
as
any
as
Memo
[];
const
comments
=
commentRelations
.
map
((
relation
)
=>
memoStore
.
getMemoByName
(
relation
.
memo
!
.
name
)).
filter
((
memo
)
=>
memo
)
as
any
as
Memo
[];
const
showCreateCommentButton
=
workspaceMemoRelatedSetting
.
enableComment
&&
currentUser
;
const
showCreateCommentButton
=
workspaceMemoRelatedSetting
.
enableComment
&&
currentUser
;
// Prepare memo.
// Prepare memo.
...
@@ -64,7 +64,7 @@ const MemoDetail = () => {
...
@@ -64,7 +64,7 @@ const MemoDetail = () => {
}
else
{
}
else
{
setParentMemo
(
undefined
);
setParentMemo
(
undefined
);
}
}
await
Promise
.
all
(
commentRelations
.
map
((
relation
)
=>
memoStore
.
getOrFetchMemoByName
(
relation
.
memo
)));
await
Promise
.
all
(
commentRelations
.
map
((
relation
)
=>
memoStore
.
getOrFetchMemoByName
(
relation
.
memo
!
.
name
)));
})();
})();
},
[
memo
]);
},
[
memo
]);
...
...
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