Skip to content

Commit 752de2f

Browse files
authored
feat(UI):Rich text editor supports associated issues (#1240)
1 parent 9287e63 commit 752de2f

10 files changed

Lines changed: 194 additions & 24 deletions

File tree

moon/apps/web/components/MarkdownEditor/MentionList.tsx

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ComponentPropsWithoutRef } from 'react'
22
import { uniqBy } from 'remeda'
33

4-
import { Mention } from '@gitmono/editor/extensions'
4+
import { Mention, LinkIssue } from '@gitmono/editor/extensions'
55
import { OrganizationMember, SyncOrganizationMember } from '@gitmono/types/generated'
66
import { GitCommitIcon, UIText } from '@gitmono/ui'
77

@@ -43,7 +43,7 @@ export function MentionList({ editor, defaultMentions, modal }: Props) {
4343
char='$'
4444
allow={({ state, range }) => {
4545
const $from = state.doc.resolve(range.from)
46-
const type = state.schema.nodes[Mention.name]
46+
const type = state.schema.nodes[LinkIssue.name]
4747
const allow = !!$from.parent.type.contentMatch.matchType(type)
4848

4949
return allow
@@ -122,15 +122,15 @@ function InnerIssueList({ editor }: Pick<Props, 'editor'>) {
122122
{
123123
role: 'app',
124124
issue: {
125-
hash: '#1234',
126-
name: 'This is an issue example.'
125+
hash: 'OCN2RX13',
126+
name: 'MR/Issue open close icon 需要替换'
127127
}
128128
},
129129
{
130130
role: 'app',
131131
issue: {
132-
hash: '#5678',
133-
name: 'Another issue example.'
132+
hash: 'DVUAVF2J',
133+
name: 'cccc'
134134
}
135135
}
136136
]
@@ -144,12 +144,10 @@ function InnerIssueList({ editor }: Pick<Props, 'editor'>) {
144144
// downrank guest members
145145
scoreModifier={member.role === 'guest' ? 0.3 : 1}
146146
onSelect={({ editor, range }) =>
147-
editor.commands.insertMention({
147+
editor.commands.insertIssue({
148148
range,
149149
id: member.issue.hash,
150-
label: member.issue.hash,
151-
username: '',
152-
role: member.role === 'app' ? 'app' : 'member'
150+
label: member.issue.hash
153151
})
154152
}
155153
>

moon/apps/web/components/MrView/MRComment.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getMarkdownExtensions } from '@gitmono/editor'
1+
import { getNoteExtensions } from '@gitmono/editor'
22
import { RichTextRenderer } from '@/components/RichTextRenderer'
33
import { useMemo } from 'react'
44
import { Button, ConditionalWrap, FaceSmilePlusIcon, UIText } from '@gitmono/ui'
@@ -22,7 +22,7 @@ interface CommentProps {
2222
const Comment = ({ conv, id, whoamI }: CommentProps) => {
2323
const { data: member } = useGetOrganizationMember({ username: conv.username })
2424

25-
const extensions = useMemo(() => getMarkdownExtensions({ linkUnfurl: {} }), [])
25+
const extensions = useMemo(() => getNoteExtensions({ linkUnfurl: {} }), [])
2626
const handleReactionSelect = useHandleExpression({ conv, id, type: whoamI })
2727

2828
return (
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Link } from '@gitmono/ui/Link'
2+
import { NodeHandler } from '.'
3+
import { useScope } from '@/contexts/scope'
4+
5+
export const LinkIssue: NodeHandler = ({ node, children }) => {
6+
const { scope } = useScope()
7+
8+
if (!node.attrs || !node.attrs.id) {
9+
return <span>{children}</span>
10+
}
11+
12+
const issueId = node.attrs?.id
13+
const label = node.attrs?.label || issueId
14+
const issueUrl = `/${scope}/issue/${issueId}`
15+
16+
return (
17+
<Link data-type='issue' href={issueUrl} className="issue-link">
18+
<span className='text-blue-500 border-b border-blue-500'>${label}</span>
19+
</Link>
20+
)
21+
}

moon/apps/web/components/RichTextRenderer/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Blockquote } from '@/components/RichTextRenderer/handlers/Blockquote'
88
import { CodeBlock } from '@/components/RichTextRenderer/handlers/CodeBlock'
99
import { Hardbreak } from '@/components/RichTextRenderer/handlers/Hardbreak'
1010
import { InlineResourceMention } from '@/components/RichTextRenderer/handlers/InlineResourceMention'
11+
import { LinkIssue } from '@/components/RichTextRenderer/handlers/LinkIssue'
1112
import { PostNoteAttachment } from '@/components/RichTextRenderer/handlers/PostNoteAttachment'
1213
import { RelativeTime } from '@/components/RichTextRenderer/handlers/RelativeTime'
1314

@@ -103,6 +104,8 @@ function RenderBlock({
103104
return <LinkUnfurl {...props} />
104105
case 'mention':
105106
return <Mention {...props} />
107+
case 'linkIssue':
108+
return <LinkIssue {...props} />
106109
case 'postNoteAttachment':
107110
return <PostNoteAttachment {...props} {...options?.postNoteAttachment} />
108111
case 'reaction':

moon/apps/web/components/SimpleNoteEditor/SimpleNoteContent.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export interface SimpleNoteContentRef {
4949
clearAndBlur(): void
5050
insertReaction: TTEditor['commands']['insertReaction']
5151
uploadAndAppendAttachments: (files: File[]) => Promise<void>
52+
getLinkedIssues(): string[]
5253
}
5354

5455
export const SimpleNoteContent = memo(
@@ -117,6 +118,17 @@ export const SimpleNoteContent = memo(
117118
}
118119
}
119120
},
121+
getLinkedIssues: () => {
122+
const issues: string[] = []
123+
124+
editor.state.doc.descendants((node) => {
125+
if (node.type.name === 'linkIssue' && node.attrs.id) {
126+
issues.push(node.attrs.id)
127+
}
128+
})
129+
130+
return Array.from(new Set(issues))
131+
},
120132
uploadAndAppendAttachments,
121133
...imperativeHandlers,
122134
editor

moon/apps/web/pages/[org]/mr/[id].tsx

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,19 +82,23 @@ const MRDetailPage:PageWithLayout<any> = () =>{
8282

8383
const send_comment = () => {
8484
const currentContentHTML = editorRef.current?.editor?.getHTML() ?? '<p></p>';
85-
86-
if (trimHtml(currentContentHTML) === '') {
87-
toast.error('Please enter the content.')
88-
}else {
89-
postMrComment(
90-
{ content: currentContentHTML },
91-
{
92-
onSuccess: () =>{
93-
editorRef.current?.clearAndBlur()
85+
const issues = editorRef.current?.getLinkedIssues() || []
86+
87+
/* eslint-disable-next-line no-console */
88+
console.log('commentIssues:',issues);
89+
90+
if (trimHtml(currentContentHTML) === '') {
91+
toast.error('Please enter the content.')
92+
} else {
93+
postMrComment(
94+
{ content: currentContentHTML },
95+
{
96+
onSuccess: () =>{
97+
editorRef.current?.clearAndBlur()
98+
}
9499
}
95-
}
96-
);
97-
}
100+
);
101+
}
98102
}
99103

100104
const buttonClasses= 'cursor-pointer';

moon/apps/web/styles/prose.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@
8585
@apply text-primary cursor-pointer font-semibold;
8686
}
8787

88+
.link-issue {
89+
@apply text-blue-500 border-b border-blue-500 cursor-pointer;
90+
}
91+
8892
:where(ul, ol):not(:where([class~='not-prose']) *) {
8993
/* Erase margins in nested lists */
9094
ul,
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { mergeAttributes, Node, Range } from '@tiptap/core'
2+
3+
import { handleRestoreMentionBackspace } from '../utils/handleRestoreMentionBackspace'
4+
import { insertContent } from '../utils/insertContent'
5+
6+
declare module '@tiptap/core' {
7+
interface Commands<ReturnType> {
8+
issue: {
9+
insertIssue: (props: {
10+
id: string
11+
label: string
12+
range: Range
13+
}) => ReturnType
14+
}
15+
}
16+
}
17+
18+
export interface IssueNodeAttrs {
19+
/**
20+
* The identifier for the selected issue, stored as a `data-id` attribute.
21+
*/
22+
id: string | null
23+
/**
24+
* The label to be rendered by the editor as the displayed text for this issue.
25+
*/
26+
label?: string | null
27+
}
28+
29+
export type IssueOptions = {
30+
HTMLAttributes: Record<string, any>
31+
}
32+
33+
export const LinkIssue = Node.create<IssueOptions>({
34+
name: 'linkIssue',
35+
36+
addOptions() {
37+
return {
38+
HTMLAttributes: {
39+
class: 'link-issue'
40+
}
41+
}
42+
},
43+
44+
group: 'inline',
45+
46+
inline: true,
47+
48+
selectable: false,
49+
50+
atom: true,
51+
52+
addAttributes() {
53+
return {
54+
id: {
55+
default: null,
56+
parseHTML: (element) => element.getAttribute('data-id'),
57+
renderHTML: (attributes) => {
58+
if (!attributes.id) {
59+
return {}
60+
}
61+
62+
return {
63+
'data-id': attributes.id
64+
}
65+
}
66+
},
67+
68+
label: {
69+
default: null,
70+
parseHTML: (element) => element.getAttribute('data-label'),
71+
renderHTML: (attributes) => {
72+
if (!attributes.label) {
73+
return {}
74+
}
75+
76+
return {
77+
'data-label': attributes.label
78+
}
79+
}
80+
}
81+
}
82+
},
83+
84+
parseHTML() {
85+
return [
86+
{
87+
tag: `span[data-type="${this.name}"]`
88+
}
89+
]
90+
},
91+
92+
renderHTML({ node, HTMLAttributes }) {
93+
return [
94+
'span',
95+
mergeAttributes(this.options.HTMLAttributes, { 'data-type': this.name }, HTMLAttributes),
96+
`$${node.attrs.label ?? node.attrs.id}`
97+
]
98+
},
99+
100+
renderText({ node }) {
101+
return `$${node.attrs.label ?? node.attrs.id}`
102+
},
103+
104+
addCommands() {
105+
return {
106+
insertIssue:
107+
({ range, ...attrs }) =>
108+
({ chain, state }) =>
109+
insertContent({
110+
chain,
111+
range,
112+
state,
113+
content: { type: this.name, attrs }
114+
})
115+
}
116+
},
117+
118+
addKeyboardShortcuts() {
119+
return {
120+
Backspace: () =>
121+
this.editor.commands.command(({ tr, state }) =>
122+
handleRestoreMentionBackspace({ transaction: tr, state, nodeName: this.name, char: '$' })
123+
)
124+
}
125+
}
126+
})

moon/packages/editor/src/extensions/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export * from './Italic'
2020
export * from './Kbd'
2121
export * from './Link'
2222
export * from './LinkUnfurl'
23+
export * from './LinkIssue'
2324
export * from './ListItem'
2425
export * from './ListKeyMap'
2526
export * from './MediaGallery'

moon/packages/editor/src/note.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ export function getNoteExtensions(options?: GetNoteExtensionsOptions) {
118118
E.TaskItem.configure(options?.taskItem),
119119
E.TaskList,
120120
E.Mention.configure(options?.mention),
121+
E.LinkIssue,
121122
E.Reaction,
122123

123124
E.Details.configure(options?.details),

0 commit comments

Comments
 (0)