Skip to content

Commit 591acf1

Browse files
authored
Add key shortcuts for context menu (#25)
1 parent facaaca commit 591acf1

File tree

3 files changed

+100
-53
lines changed

3 files changed

+100
-53
lines changed

src/components/Icons.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import BellSlashIcon16 from 'virtual:icons/octicon/bell-slash-16'
3232
import MailIcon16 from 'virtual:icons/octicon/mail-16'
3333
import LinkExternalIcon16 from 'virtual:icons/octicon/link-external-16'
3434
import SquareIcon16 from 'virtual:icons/octicon/square-16'
35-
import CircleIcon16 from 'virtual:icons/octicon/Circle-16'
35+
import CircleIcon24 from 'virtual:icons/octicon/Circle-24'
3636

3737
import CommandIcon from 'virtual:icons/ph/command'
3838

@@ -72,5 +72,5 @@ export const Icons = {
7272
Check16: markRaw(CheckIcon16),
7373
LinkExternal16: markRaw(LinkExternalIcon16),
7474
Square16: markRaw(SquareIcon16),
75-
Circle16: markRaw(CircleIcon16),
75+
Circle: markRaw(CircleIcon24),
7676
}

src/pages/HomePage.vue

Lines changed: 82 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ useElementNavigation({
4141
targetQuery: '.notification-item, .notification-title',
4242
})
4343
44-
function isChecked(item: MinimalRepository | Thread) {
44+
function isChecked(item: MinimalRepository | Thread | null) {
45+
if (item == null)
46+
return false
47+
4548
if (isRepository(item)) {
4649
return store.notifications
4750
.filter(isThread)
@@ -94,52 +97,98 @@ function isCheckable(item: MinimalRepository | Thread) {
9497
.filter(thread => thread.repository.id === item.id)
9598
.some(thread => thread.unread)
9699
}
100+
onScopeDispose(() => {
101+
store.checkedItems.length = 0
102+
})
97103
98104
useKey('esc', () => {
99105
store.checkedItems = []
100106
}, { prevent: true })
101107
102-
onScopeDispose(() => {
103-
store.checkedItems.length = 0
108+
const contextMenuThread = ref<Thread | null>(null)
109+
const popoverTarget = ref<ReferenceElement | null>(null)
110+
const popoverRef = ref<InstanceType<typeof Popover> | null>(null)
111+
112+
async function handleSelectMarkAsRead(triggeredByKeyboard = false) {
113+
if (triggeredByKeyboard) {
114+
if (store.checkedItems.length > 0) {
115+
store.markCheckedNotificationsAsRead(AppStorage.get('accessToken')!)
116+
store.checkedItems = []
117+
return
118+
}
119+
120+
if (contextMenuThread.value) {
121+
const thread = contextMenuThread.value
122+
markNotificationAsRead(contextMenuThread.value.id, AppStorage.get('accessToken')!)
123+
.then(() => {
124+
store.removeNotificationById(thread.id)
125+
})
126+
127+
return
128+
}
129+
}
130+
131+
if (!contextMenuThread.value)
132+
return
133+
134+
if (isChecked(contextMenuThread.value)) {
135+
store.markCheckedNotificationsAsRead(AppStorage.get('accessToken')!)
136+
store.checkedItems = []
137+
return
138+
}
139+
140+
const thread = contextMenuThread.value
141+
markNotificationAsRead(thread.id, AppStorage.get('accessToken')!)
142+
.then(() => {
143+
store.removeNotificationById(thread.id)
144+
})
145+
}
146+
147+
useKey('m', () => {
148+
handleSelectMarkAsRead(true)
149+
popoverRef.value?.hide()
150+
})
151+
152+
function handleSelectOpen(triggeredByKeyboard = false) {
153+
if (triggeredByKeyboard) {
154+
if (store.checkedItems.length > 0) {
155+
store.checkedItems.forEach(handleNotificationClick)
156+
store.checkedItems = []
157+
return
158+
}
159+
160+
if (contextMenuThread.value) {
161+
handleNotificationClick(contextMenuThread.value)
162+
return
163+
}
164+
}
165+
166+
if (!contextMenuThread.value)
167+
return
168+
169+
if (isChecked(contextMenuThread.value))
170+
store.checkedItems.forEach(handleNotificationClick)
171+
else
172+
handleNotificationClick(contextMenuThread.value)
173+
174+
store.checkedItems = []
175+
}
176+
177+
useKey('o', () => {
178+
handleSelectOpen(true)
179+
popoverRef.value?.hide()
104180
})
105181
106-
const contextMenuThread = ref<Thread | null>(null)
107182
const contextMenuItems = computed(() => [
108183
menuItem({
109184
key: 'read',
110185
meta: { text: 'Mark as read', icon: Icons.Check16, key: 'M' },
111-
async onSelect() {
112-
if (!contextMenuThread.value)
113-
return
114-
115-
if (isChecked(contextMenuThread.value)) {
116-
store.markCheckedNotificationsAsRead(AppStorage.get('accessToken')!)
117-
store.checkedItems = []
118-
return
119-
}
120-
121-
markNotificationAsRead(contextMenuThread.value.id, AppStorage.get('accessToken')!)
122-
.then(() => {
123-
store.notifications = store.notifications.filter(notification => (
124-
notification.id !== contextMenuThread.value!.id
125-
))
126-
})
127-
},
186+
onSelect: () => handleSelectMarkAsRead(),
128187
}),
129188
menuItem({
130189
key: 'open',
131190
meta: { text: 'Open', icon: Icons.LinkExternal16, key: 'O' },
132-
onSelect() {
133-
if (!contextMenuThread.value)
134-
return
135-
136-
if (isChecked(contextMenuThread.value))
137-
store.checkedItems.forEach(handleNotificationClick)
138-
else
139-
handleNotificationClick(contextMenuThread.value)
140-
141-
store.checkedItems = []
142-
},
191+
onSelect: () => handleSelectOpen(),
143192
}),
144193
// menuItem({
145194
// key: 'unsubscribe',
@@ -148,16 +197,13 @@ const contextMenuItems = computed(() => [
148197
// }),
149198
isChecked(contextMenuThread.value!) && menuItem({
150199
key: 'clear',
151-
meta: { text: 'Clear selections', icon: Icons.Circle16, key: 'ESC' },
200+
meta: { text: 'Clear selections', icon: Icons.Circle, key: 'ESC' },
152201
onSelect: () => {
153202
store.checkedItems = []
154203
},
155204
}),
156205
])
157206
158-
const popoverTarget = ref<ReferenceElement | null>(null)
159-
const popoverRef = ref<InstanceType<typeof Popover> | null>(null)
160-
161207
function handleThreadContextmenu(thread: Thread, event: MouseEvent) {
162208
if (!isChecked(thread))
163209
store.checkedItems = []

src/stores/store.ts

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,20 @@ export const useStore = defineStore('store', () => {
1717
const failedLoadingNotifications = ref(false)
1818
const skeletonVisible = ref(false)
1919

20+
function removeNotificationById(id: Thread['id']) {
21+
const index = notifications.value.findIndex(item => isThread(item) && item.id === id)
22+
const thread = notifications.value[index] as Thread
23+
notifications.value.splice(index, 1)
24+
25+
const repoHasNotifications = notifications.value.some(item => isThread(item) && item.repository.id === thread.repository.id)
26+
if (!repoHasNotifications) {
27+
const repoIndex = notifications.value.findIndex(item => isRepository(item) && item.id === thread.repository.id)
28+
notifications.value.splice(repoIndex, 1)
29+
}
30+
31+
triggerRef(notifications)
32+
}
33+
2034
let threadsRaw: Thread[] = []
2135
let threadsPreviousRaw: Thread[] = []
2236

@@ -129,21 +143,7 @@ export const useStore = defineStore('store', () => {
129143
)
130144
}
131145
finally {
132-
notifications.value = notifications.value
133-
.filter((item) => {
134-
if (isRepository(item))
135-
return true
136-
137-
return !deletedThreads.includes(item.id)
138-
})
139-
.filter((item, _, array) => {
140-
if (isThread(item))
141-
return true
142-
143-
return array.some((someItem) => {
144-
return isThread(someItem) && someItem.repository.id === item.id
145-
})
146-
})
146+
deletedThreads.forEach(id => removeNotificationById(id))
147147
checkedItems.value = []
148148
triggerRef(notifications)
149149
}
@@ -153,6 +153,7 @@ export const useStore = defineStore('store', () => {
153153
newRelease,
154154
notifications,
155155
currentPage: readonly(currentPage),
156+
removeNotificationById,
156157
loadingNotifications,
157158
skeletonVisible,
158159
pageFrom,

0 commit comments

Comments
 (0)