Skip to content

Commit d39d0c8

Browse files
authored
fix: Add CL filter box echo functionality and multiple UI fixes (#1599)
* fix: Add CL filter box echo functionality and multiple UI fixes - Add filter condition echo functionality to CL list - Fix tag editor scroll synchronization issue - Fix double line break issue when creating files with Enter key - Fix clone path display issue showing "main" - Fix UI misalignment in tag dropdown list * fix: Add CL filter box echo functionality and multiple UI fixes - Add filter condition echo functionality to CL list - Fix tag editor scroll synchronization issue - Fix double line break issue when creating files with Enter key - Fix clone path display issue showing "main" - Fix UI misalignment in tag dropdown list
1 parent d9179c8 commit d39d0c8

File tree

5 files changed

+139
-63
lines changed

5 files changed

+139
-63
lines changed

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

Lines changed: 89 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
11
'use client'
22

33
import React, { useCallback, useEffect, useMemo, useState } from 'react'
4-
import { GitMergeIcon, GitPullRequestClosedIcon, GitPullRequestIcon } from '@primer/octicons-react'
4+
import { GitMergeIcon, GitPullRequestClosedIcon, GitPullRequestIcon, XIcon } from '@primer/octicons-react'
55
import { formatDistance, fromUnixTime } from 'date-fns'
66
import { useAtom } from 'jotai'
77
import { useRouter } from 'next/router'
88

99
import { LabelItem, SyncOrganizationMember as Member, PostApiClListData } from '@gitmono/types/generated'
10-
import { Button, CheckIcon, ChevronDownIcon, OrderedListIcon } from '@gitmono/ui'
10+
import { Button, CheckIcon, ChevronDownIcon, OrderedListIcon, SearchIcon } from '@gitmono/ui'
1111
import { cn } from '@gitmono/ui/src/utils'
1212

1313
import { MemberHovercard } from '@/components/InlinePost/MemberHovercard'
1414
import { IssueIndexTabFilter as CLIndexTabFilter } from '@/components/Issues/IssueIndex'
1515
import {
16+
ListItem as CLItem,
17+
IssueList as ClList,
1618
Dropdown,
1719
DropdownItemwithAvatar,
1820
DropdownItemwithLabel,
1921
DropdownOrder,
2022
DropdownReview,
21-
ListBanner,
22-
ListItem as CLItem,
23-
IssueList as ClList
23+
ListBanner
2424
} from '@/components/Issues/IssueList'
2525
import { useScope } from '@/contexts/scope'
26-
import { useGetLabelList } from '@/hooks/useGetLabelList'
2726
import { usePostClList } from '@/hooks/CL/usePostClList'
27+
import { useGetLabelList } from '@/hooks/useGetLabelList'
2828
import { useSyncedMembers } from '@/hooks/useSyncedMembers'
2929
import { apiErrorToast } from '@/utils/apiErrorToast'
3030
import { atomWithWebStorage } from '@/utils/atomWithWebStorage'
@@ -34,7 +34,7 @@ import { AdditionType, ItemLabels, ItemRightIcons } from '../Issues/IssuesConten
3434
import { Pagination } from '../Issues/Pagenation'
3535
import { orderTags, reviewTags } from '../Issues/utils/consts'
3636
import { generateAllMenuItems, MenuConfig } from '../Issues/utils/generateAllMenuItems'
37-
import { filterAtom, clCloseCurrentPage, clidAtom, clOpenCurrentPage, sortAtom } from '../Issues/utils/store'
37+
import { clCloseCurrentPage, clidAtom, clOpenCurrentPage, filterAtom, sortAtom } from '../Issues/utils/store'
3838
import { Heading } from './catalyst/heading'
3939

4040
// interface ClInfoItem {
@@ -48,6 +48,10 @@ import { Heading } from './catalyst/heading'
4848

4949
type ItemsType = NonNullable<PostApiClListData['data']>['items']
5050

51+
const escapeRegExp = (str: string): string => {
52+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
53+
}
54+
5155
export default function CLView() {
5256
const { scope } = useScope()
5357
const [clList, setClList] = useState<ItemsType>([])
@@ -81,6 +85,8 @@ export default function CLView() {
8185

8286
const [review, setReview] = useAtom(reviewAtom)
8387

88+
const [searchQuery, setSearchQuery] = useState('')
89+
8490
const additions = useCallback(
8591
(labels: number[]): AdditionType => {
8692
const additional: AdditionType = { status, asc: false }
@@ -179,11 +185,8 @@ export default function CLView() {
179185
<>
180186
opened {formatDistance(fromUnixTime(item.open_timestamp), new Date(), { addSuffix: true })} by{' '}
181187
<MemberHovercard username={item.author}>
182-
<span className='cursor-pointer hover:text-blue-600 hover:underline'>
183-
{item.author}
184-
</span>
188+
<span className='cursor-pointer hover:text-blue-600 hover:underline'>{item.author}</span>
185189
</MemberHovercard>
186-
187190
</>
188191
)
189192
case 'merged':
@@ -192,13 +195,10 @@ export default function CLView() {
192195
<>
193196
by{' '}
194197
<MemberHovercard username={item.author}>
195-
<span className='cursor-pointer hover:text-blue-600 hover:underline'>
196-
{item.author}
197-
</span>
198+
<span className='cursor-pointer hover:text-blue-600 hover:underline'>{item.author}</span>
198199
</MemberHovercard>
199200
{' was merged '}
200201
{formatDistance(fromUnixTime(item.merge_timestamp ?? 0), new Date(), { addSuffix: true })}
201-
202202
</>
203203
)
204204
} else {
@@ -209,16 +209,10 @@ export default function CLView() {
209209
<>
210210
by{' '}
211211
<MemberHovercard username={item.author}>
212-
<span className='cursor-pointer hover:text-blue-600 hover:underline'>
213-
{item.author}
214-
</span>
212+
<span className='cursor-pointer hover:text-blue-600 hover:underline'>{item.author}</span>
215213
</MemberHovercard>
216-
217214
{' was closed '}
218-
219215
{formatDistance(fromUnixTime(item.updated_at), new Date(), { addSuffix: true })}
220-
221-
222216
</>
223217
)
224218
default:
@@ -233,12 +227,18 @@ export default function CLView() {
233227
onSelectFactory: (item: Member) => (e: Event) => {
234228
e.preventDefault()
235229
if (item.user.username === sort['Author']) {
236-
loadClList()
230+
const escapedAuthor = escapeRegExp(sort['Author'])
231+
const newQuery = searchQuery.replace(new RegExp(`author:${escapedAuthor}\\s*`, 'g'), '').trim()
232+
233+
setSearchQuery(newQuery)
237234
setSort({
238235
...sort,
239236
Author: ''
240237
})
241238
} else {
239+
const newQuery = searchQuery ? `${searchQuery} author:${item.user.username}` : `author:${item.user.username}`
240+
241+
setSearchQuery(newQuery)
242242
setSort({
243243
...sort,
244244
Author: item.user.username
@@ -254,12 +254,20 @@ export default function CLView() {
254254
onSelectFactory: (item: Member) => (e: Event) => {
255255
e.preventDefault()
256256
if (item.user.username === sort['Assignees']) {
257-
loadClList()
257+
const escapedAssignee = escapeRegExp(sort['Assignees'])
258+
const newQuery = searchQuery.replace(new RegExp(`assignee:${escapedAssignee}\\s*`, 'g'), '').trim()
259+
260+
setSearchQuery(newQuery)
258261
setSort({
259262
...sort,
260263
Assignees: ''
261264
})
262265
} else {
266+
const newQuery = searchQuery
267+
? `${searchQuery} assignee:${item.user.username}`
268+
: `assignee:${item.user.username}`
269+
270+
setSearchQuery(newQuery)
263271
setSort({
264272
...sort,
265273
Assignees: item.user.username
@@ -279,8 +287,16 @@ export default function CLView() {
279287
onSelectFactory: (item) => (e: Event) => {
280288
e.preventDefault()
281289
if (label?.includes(String(item.id))) {
290+
const escapedLabelName = escapeRegExp(item.name)
291+
const newQuery = searchQuery.replace(new RegExp(`label:"${escapedLabelName}"\\s*`, 'g'), '').trim()
292+
293+
setSearchQuery(newQuery)
282294
setLabel(label.filter((i) => i !== String(item.id)))
283295
} else {
296+
const escapedLabelName = escapeRegExp(item.name)
297+
const newQuery = searchQuery ? `${searchQuery} label:"${escapedLabelName}"` : `label:"${escapedLabelName}"`
298+
299+
setSearchQuery(newQuery)
284300
setLabel([...label, String(item.id)])
285301
}
286302
},
@@ -297,8 +313,17 @@ export default function CLView() {
297313
onSelectFactory: (item) => (e: Event) => {
298314
e.preventDefault()
299315
if (item === review) {
316+
const escapedReview = escapeRegExp(review)
317+
const newQuery = searchQuery.replace(new RegExp(`review:${escapedReview}\\s*`, 'g'), '').trim()
318+
319+
setSearchQuery(newQuery)
300320
setReview('')
301321
} else {
322+
const escapedReview = escapeRegExp(review)
323+
let newQuery = searchQuery.replace(new RegExp(`review:${escapedReview}\\s*`, 'g'), '').trim()
324+
325+
newQuery = newQuery ? `${newQuery} review:${item}` : `review:${item}`
326+
setSearchQuery(newQuery)
302327
setReview(item)
303328
}
304329
},
@@ -445,13 +470,52 @@ export default function CLView() {
445470

446471
const router = useRouter()
447472

473+
const clearAllFilters = () => {
474+
setSearchQuery('')
475+
setSort({ ...sort, Author: '', Assignees: '' })
476+
setLabel([])
477+
setReview('')
478+
}
479+
448480
return (
449481
<div className='relative m-4 flex h-screen flex-col'>
450482
<Heading>Change List</Heading>
451483
<br />
452484
<IndexPageContainer>
453485
<IndexPageContent id='/[org]/cl' className={cn('@container', '3xl:max-w-7xl max-w-7xl')}>
454486
<div className='flex h-full flex-col'>
487+
<div className='mb-4'>
488+
<div className='group flex min-h-[42px] items-center gap-2 rounded-md border border-gray-300 bg-white px-3 py-2 shadow-sm transition-all focus-within:border-blue-500 focus-within:shadow-md focus-within:ring-2 focus-within:ring-blue-100 hover:border-gray-400'>
489+
<div className='flex items-center text-gray-400'>
490+
<SearchIcon className='h-4 w-4' />
491+
</div>
492+
493+
<input
494+
type='text'
495+
className='w-full flex-1 border-none bg-transparent py-1 text-sm text-gray-400 outline-none ring-0 focus:outline-none focus:ring-0'
496+
value={searchQuery}
497+
onChange={(e) => setSearchQuery(e.target.value)}
498+
onKeyDown={(e) => {
499+
if (e.key === 'Enter') {
500+
loadClList()
501+
}
502+
}}
503+
/>
504+
505+
{searchQuery && (
506+
<button
507+
onClick={() => {
508+
clearAllFilters()
509+
}}
510+
className='flex items-center justify-center rounded-md p-1 text-gray-400 transition-all hover:bg-gray-100 hover:text-gray-600'
511+
title='Clear search'
512+
>
513+
<XIcon className='h-4 w-4' />
514+
</button>
515+
)}
516+
</div>
517+
</div>
518+
455519
<ClList
456520
isLoading={isLoading}
457521
Issuelists={clList}
@@ -487,7 +551,7 @@ export default function CLView() {
487551
}}
488552
>
489553
<div className='text-xs text-[#59636e]'>
490-
<span className="mr-2">#{i.link}</span>
554+
<span className='mr-2'>#{i.link}</span>
491555
{getDescription(i)}
492556
{' • ChangeList'}
493557
</div>

moon/apps/web/components/CodeView/BlobView/BlobEditor.tsx

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import React, { useCallback, useMemo, useState } from 'react'
3+
import React, { useCallback, useMemo, useRef, useState } from 'react'
44
import { DiffFile, DiffModeEnum, DiffView } from '@git-diff-view/react'
55
import toast from 'react-hot-toast'
66

@@ -42,6 +42,9 @@ export default function BlobEditor({ fileContent, filePath, fileName, onCancel }
4242

4343
const [isCommitDialogOpen, setIsCommitDialogOpen] = useState(false)
4444

45+
const lineNumbersRef = useRef<HTMLDivElement>(null)
46+
const textareaRef = useRef<HTMLTextAreaElement>(null)
47+
4548
const contentLines = useMemo(() => content.split('\n'), [content])
4649

4750
const hasChanges = useMemo(
@@ -129,10 +132,20 @@ export default function BlobEditor({ fileContent, filePath, fileName, onCancel }
129132
setDiffFile(null)
130133
}
131134

135+
const handleScroll = useCallback(() => {
136+
if (textareaRef.current && lineNumbersRef.current) {
137+
lineNumbersRef.current.scrollTop = textareaRef.current.scrollTop
138+
}
139+
}, [])
140+
132141
const renderEditView = () => {
133142
return (
134-
<div className='flex h-full w-full font-mono text-sm leading-6'>
135-
<div className='select-none border-r border-gray-200 bg-gray-50 px-4 text-right text-gray-400'>
143+
<div className='flex h-full w-full overflow-hidden font-mono text-sm leading-6'>
144+
<div
145+
ref={lineNumbersRef}
146+
className='h-full select-none overflow-hidden border-r border-gray-200 bg-gray-50 px-4 text-right text-gray-400'
147+
style={{ flexShrink: 0 }}
148+
>
136149
{contentLines.map((_, index) => (
137150
// eslint-disable-next-line react/no-array-index-key
138151
<div key={index} className='leading-6'>
@@ -141,11 +154,13 @@ export default function BlobEditor({ fileContent, filePath, fileName, onCancel }
141154
))}
142155
</div>
143156

144-
<div className='flex-1 pl-4'>
157+
<div className='flex-1 overflow-hidden'>
145158
<textarea
159+
ref={textareaRef}
146160
value={content}
147161
onChange={handleTextareaChange}
148-
className='h-full w-full resize-none border-0 bg-transparent p-0 font-mono text-sm leading-6 focus:outline-none'
162+
onScroll={handleScroll}
163+
className='h-full w-full resize-none overflow-auto border-0 bg-transparent p-0 pl-4 font-mono text-sm leading-6 focus:outline-none'
149164
spellCheck={false}
150165
style={{ tabSize: 2 }}
151166
/>
@@ -218,8 +233,8 @@ export default function BlobEditor({ fileContent, filePath, fileName, onCancel }
218233
}
219234

220235
return (
221-
<div className='flex h-full w-full flex-col gap-2'>
222-
<div className='flex min-h-14 w-full items-center justify-between px-2'>
236+
<div className='flex min-h-0 w-full flex-1 flex-col gap-2'>
237+
<div className='flex min-h-14 w-full flex-shrink-0 items-center justify-between px-2'>
223238
<div className='flex max-w-[900px] flex-wrap items-center gap-x-1 gap-y-2 text-gray-700'>
224239
{pathSegments.map((seg, i) => (
225240
// eslint-disable-next-line react/no-array-index-key
@@ -249,8 +264,8 @@ export default function BlobEditor({ fileContent, filePath, fileName, onCancel }
249264
</div>
250265
</div>
251266

252-
<div className='flex h-full min-h-0 w-full flex-col rounded-xl border border-[#bec7ce]'>
253-
<div className='flex h-14 w-full items-center rounded-t-xl border-b border-[#d0d9e0] bg-[#f9fbfd] px-4'>
267+
<div className='flex min-h-0 w-full flex-1 flex-col rounded-xl border border-[#bec7ce]'>
268+
<div className='flex h-14 w-full flex-shrink-0 items-center rounded-t-xl border-b border-[#d0d9e0] bg-[#f9fbfd] px-4'>
254269
<div className='inline-flex rounded-md border border-gray-300 bg-white'>
255270
<button
256271
onClick={() => setViewMode('edit')}
@@ -271,7 +286,7 @@ export default function BlobEditor({ fileContent, filePath, fileName, onCancel }
271286
</div>
272287
</div>
273288

274-
<div className='flex-1 overflow-auto'>
289+
<div className='min-h-0 flex-1 overflow-hidden'>
275290
{viewMode === 'edit' && renderEditView()}
276291
{viewMode === 'preview' && renderPreviewView()}
277292
</div>

moon/apps/web/components/CodeView/NewCodeView/MarkdownEditor.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default function MarkdownEditor({ contentState, disabled = false }: Markd
2121
onUpdate: ({ editor }) => {
2222
const text = editor.getText().replace(/\n\n/g, '\n')
2323

24-
setContent(editor.getText())
24+
setContent(text)
2525
setLineCount(text.split('\n').length || 1)
2626
},
2727
editorProps: {

moon/apps/web/components/CodeView/Tags/TagSwitcher.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,15 @@ export default function TagSwitcher() {
9797
key={t.name}
9898
value={t.name}
9999
title={t.name}
100+
className='h-auto min-h-8 py-1.5'
100101
onSelect={() => {
101102
setOpen(false)
102103
if (t.name) {
103104
router.push(`/${org}/code/tree/${encodeURIComponent(t.name)}/${path}`)
104105
}
105106
}}
106107
>
107-
<div className='flex min-w-0 flex-col'>
108+
<div className='flex min-w-0 flex-col gap-0.5'>
108109
<span className='truncate'>{t.name}</span>
109110
{t.message && (
110111
<UIText quaternary size='text-[12px]' className='truncate'>

0 commit comments

Comments
 (0)