diff --git a/.vscode/settings.default.json b/.vscode/settings.default.json
index 2b87c16..71423eb 100644
--- a/.vscode/settings.default.json
+++ b/.vscode/settings.default.json
@@ -27,8 +27,7 @@
"markdown",
"json",
"jsonc",
- "yaml",
- "toml"
+ "yaml"
],
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer"
diff --git a/src/App.vue b/src/App.vue
index d6d23dd..9e847d5 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,14 +1,10 @@
diff --git a/src/components/AppSidebar.vue b/src/components/AppSidebar.vue
index 2d74481..c05da33 100644
--- a/src/components/AppSidebar.vue
+++ b/src/components/AppSidebar.vue
@@ -8,7 +8,7 @@ import { AppStorage } from '../storage'
import { useKey } from '../composables/useKey'
import { useI18n } from '../composables/useI18n'
import { Page, useRoute } from '../composables/useRoute'
-import { useAppHooks } from '../composables/useAppHooks'
+import { useCommonCalls } from '../composables/useCommonCalls'
import { Icons } from './Icons'
import SidebarButton from './SidebarButton.vue'
import Popover from './Popover.vue'
@@ -48,10 +48,10 @@ const moreItems = computed(() => [
}),
])
-const { emitRefetch } = useAppHooks()
+const commonCalls = useCommonCalls()
useKey('r', () => {
- emitRefetch(true)
+ commonCalls.fetchThreads(true)
}, { source: () => route.currentPage.value === Page.Home })
const morePopover = ref | null>(null)
@@ -119,7 +119,7 @@ useKey('.', () => {
diff --git a/src/composables/useAppHooks.ts b/src/composables/useAppHooks.ts
deleted file mode 100644
index b02c288..0000000
--- a/src/composables/useAppHooks.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import type { MinimalRepository, Thread } from '../api/notifications'
-import { singleton } from '../utils/common'
-import { useCustomHook } from './useCustomHook'
-
-export const useAppHooks = singleton(() => {
- const [onMarkAsRead, emitMarkAsRead] = useCustomHook<[target: MinimalRepository | Thread | Thread[]]>()
- const [onOpen, emitOpen] = useCustomHook<[target: MinimalRepository | Thread]>()
- const [onRefetch, emitRefetch] = useCustomHook<[withSkeletons: boolean]>()
- const [onUnsubscribe, emitUnsubscribe] = useCustomHook<[target: MinimalRepository | Thread]>()
-
- return {
- onMarkAsRead,
- emitMarkAsRead,
- onOpen,
- emitOpen,
- onRefetch,
- emitRefetch,
- onUnsubscribe,
- emitUnsubscribe,
- }
-})
diff --git a/src/composables/useCommonCalls.ts b/src/composables/useCommonCalls.ts
new file mode 100644
index 0000000..59ad5aa
--- /dev/null
+++ b/src/composables/useCommonCalls.ts
@@ -0,0 +1,145 @@
+import { createSharedComposable } from '@vueuse/core'
+import { invoke } from '@tauri-apps/api'
+import { sendNotification } from '@tauri-apps/api/notification'
+import { open as shellOpen } from '@tauri-apps/api/shell'
+import { type MinimalRepository, type Thread, getNotifications, markNotificationAsRead, unsubscribeNotification } from '../api/notifications'
+import { useStore } from '../stores/store'
+import { filterNewNotifications, isRepository, isThread, toNotificationList } from '../utils/notification'
+import { AppStorage } from '../storage'
+import { InvokeCommand } from '../constants'
+import { createGithubWebURL } from '../utils/github'
+
+export const useCommonCalls = createSharedComposable(() => {
+ const store = useStore()
+
+ function getThreadsToProcess(target: Thread | MinimalRepository) {
+ let threads = [] as Thread[]
+
+ if (isRepository(target)) {
+ threads = store.getThreadsOfRepository(target)
+ }
+ else if (store.isChecked(target)) {
+ threads = [...store.checkedItems]
+ store.checkedItems = []
+ }
+ else {
+ threads = [target]
+ }
+
+ return threads
+ }
+
+ function markAsRead(target: MinimalRepository | Thread | Thread[]) {
+ let threads = [] as Thread[]
+
+ if (isRepository(target) || isThread(target)) {
+ threads = getThreadsToProcess(target)
+ }
+ else {
+ threads = target
+ }
+
+ for (const thread of threads) {
+ if (!thread.unread) {
+ continue
+ }
+
+ if (AppStorage.get('showReadNotifications')) {
+ thread.unread = false
+ }
+ else {
+ store.removeNotificationById(thread.id)
+ }
+
+ markNotificationAsRead(thread.id, AppStorage.get('accessToken')!)
+ }
+ }
+
+ function open(target: Thread | MinimalRepository) {
+ const threads = getThreadsToProcess(target)
+
+ for (const thread of threads) {
+ const url = createGithubWebURL({ notification: thread, userId: AppStorage.get('user')!.id })
+ shellOpen(url)
+ }
+
+ if (AppStorage.get('markAsReadOnOpen')) {
+ markAsRead(threads)
+ }
+ }
+
+ function unsubscribeThreadOrRepo(target: Thread | MinimalRepository) {
+ const threads = getThreadsToProcess(target)
+
+ for (const thread of threads) {
+ unsubscribeNotification(thread.id, AppStorage.get('accessToken')!)
+ }
+
+ markAsRead(threads)
+ }
+
+ async function fetchThreads(withSkeletons = false) {
+ if (store.loadingNotifications) {
+ return
+ }
+
+ const accessToken = AppStorage.get('accessToken')
+
+ if (accessToken == null) {
+ return
+ }
+
+ const previousThreads = store.notifications.filter(isThread)
+
+ if (withSkeletons) {
+ store.skeletonVisible = true
+ store.notifications = []
+ }
+
+ store.loadingNotifications = true
+ store.failedLoadingNotifications = false
+
+ try {
+ const { data } = await getNotifications({
+ accessToken,
+ showOnlyParticipating: AppStorage.get('showOnlyParticipating'),
+ showReadNotifications: AppStorage.get('showReadNotifications'),
+ })
+
+ const threadSet = new Set(data.map(thread => thread.id))
+
+ store.checkedItems = store.checkedItems.filter(thread => threadSet.has(thread.id))
+ store.notifications = toNotificationList(data)
+ }
+ catch (error) {
+ store.notifications = []
+ store.failedLoadingNotifications = true
+ store.checkedItems = []
+ }
+
+ store.loadingNotifications = false
+ store.skeletonVisible = false
+
+ const newNotifications = filterNewNotifications(previousThreads, store.notifications.filter(isThread))
+
+ if (newNotifications.length > 0) {
+ if (AppStorage.get('soundsEnabled')) {
+ invoke(InvokeCommand.PlayNotificationSound)
+ }
+
+ if (AppStorage.get('showSystemNotifications')) {
+ sendNotification({
+ title: newNotifications[0].repository.full_name,
+ body: newNotifications[0].subject.title,
+ })
+ }
+ }
+ }
+
+ return {
+ markAsRead,
+ open,
+ unsubscribeThreadOrRepo,
+ fetchThreads,
+ }
+})
diff --git a/src/main.ts b/src/main.ts
index ec13b74..971e943 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -18,6 +18,7 @@ import { useKey } from './composables/useKey'
import 'dayjs/locale/en'
import 'dayjs/locale/tr'
import { Page, useRoute } from './composables/useRoute'
+import { useCommonCalls } from './composables/useCommonCalls'
async function main() {
if (import.meta.env.MODE !== 'production') {
@@ -45,6 +46,7 @@ async function main() {
if (token && user) {
route.go(Page.Home)
+ useCommonCalls().fetchThreads()
}
const [autoStartEnabled, notificationsGranted] = await Promise.all([isAutostartEnabled(), isPermissionGranted()])
diff --git a/src/pages/HomePage.vue b/src/pages/HomePage.vue
index fea7b07..b3e2db4 100644
--- a/src/pages/HomePage.vue
+++ b/src/pages/HomePage.vue
@@ -19,19 +19,17 @@ import { CheckedNotificationProcess } from '../constants'
import { vContextmenu } from '../directives/contextmenu'
import { useI18n } from '../composables/useI18n'
import { useRoute } from '../composables/useRoute'
-import { useAppHooks } from '../composables/useAppHooks'
+import { useCommonCalls } from '../composables/useCommonCalls'
const store = useStore()
const route = useRoute()
const { t } = useI18n()
-const { emitRefetch } = useAppHooks()
+const commonCalls = useCommonCalls()
if (route.state.value.fetchOnEnter) {
- emitRefetch(true)
+ commonCalls.fetchThreads(true)
}
-const { emitOpen, emitUnsubscribe, emitMarkAsRead } = useAppHooks()
-
const home = ref(null)
useElementNavigation({
target: home,
@@ -53,7 +51,7 @@ useKey('m', () => {
return
}
- emitMarkAsRead(store.checkedItems[0])
+ commonCalls.markAsRead(store.checkedItems[0])
})
useKey('o', () => {
@@ -61,7 +59,7 @@ useKey('o', () => {
return
}
- emitOpen(store.checkedItems[0])
+ commonCalls.open(store.checkedItems[0])
})
useKey('u', () => {
@@ -69,7 +67,7 @@ useKey('u', () => {
return
}
- emitUnsubscribe(store.checkedItems[0])
+ commonCalls.unsubscribeThreadOrRepo(store.checkedItems[0])
})
function createContextmenuItems(item: Thread | MinimalRepository): ItemRenderList {
@@ -97,21 +95,21 @@ function createContextmenuItems(item: Thread | MinimalRepository): ItemRenderLis
key: 'mark:all',
meta: { text: t.markAllAsRead, icon: Icons.Check16, key: 'M' },
onSelect() {
- emitMarkAsRead(item)
+ commonCalls.markAsRead(item)
},
}),
menuItem({
key: 'open:all',
meta: { text: t.openAll, icon: Icons.LinkExternal16, key: 'O' },
onSelect() {
- emitOpen(item)
+ commonCalls.open(item)
},
}),
menuItem({
key: 'unsubscribe:all',
meta: { text: t.unsubscribeAll, icon: Icons.BellSlash16, key: 'U' },
onSelect() {
- emitUnsubscribe(item)
+ commonCalls.unsubscribeThreadOrRepo(item)
},
}),
checked && menuItem({
@@ -137,7 +135,7 @@ function createContextmenuItems(item: Thread | MinimalRepository): ItemRenderLis
key: 'read',
meta: { text: t.markAsRead, icon: Icons.Check16, key: 'M' },
onSelect() {
- emitMarkAsRead(item)
+ commonCalls.markAsRead(item)
},
}),
@@ -145,7 +143,7 @@ function createContextmenuItems(item: Thread | MinimalRepository): ItemRenderLis
key: 'open',
meta: { text: t.open, icon: Icons.LinkExternal16, key: 'O' },
onSelect() {
- emitOpen(item)
+ commonCalls.open(item)
},
}),
@@ -153,7 +151,7 @@ function createContextmenuItems(item: Thread | MinimalRepository): ItemRenderLis
key: 'unsubscribe',
meta: { text: t.unsubscribe, icon: Icons.BellSlash16, key: 'U' },
onSelect() {
- emitUnsubscribe(item)
+ commonCalls.unsubscribeThreadOrRepo(item)
},
}),
@@ -186,7 +184,7 @@ function handleClickRepo(repo: MinimalRepository) {
:description="t.oopsieCouldntLoad"
>
-
+
{{ t.refresh }}
@@ -208,7 +206,7 @@ function handleClickRepo(repo: MinimalRepository) {
:checkable="store.isCheckable(item)"
:indeterminate="store.isIndeterminate(item)"
:checkboxVisible="store.checkedItems.length > 0"
- @click:notification="emitOpen"
+ @click:notification="commonCalls.open"
@click:repo="handleClickRepo"
@update:checked="(value) => store.setChecked(item, value)"
/>
diff --git a/src/pages/LandingPage.vue b/src/pages/LandingPage.vue
index d0cd3b4..cd77a8c 100644
--- a/src/pages/LandingPage.vue
+++ b/src/pages/LandingPage.vue
@@ -15,12 +15,12 @@ import { useTimeoutPool } from '../composables/useTimeoutPool'
import { getServerPort } from '../api/app'
import { useI18n } from '../composables/useI18n'
import { Page, useRoute } from '../composables/useRoute'
-import { useAppHooks } from '../composables/useAppHooks'
+import { useCommonCalls } from '../composables/useCommonCalls'
const route = useRoute()
const processing = ref(true)
const { t } = useI18n()
-const { emitRefetch } = useAppHooks()
+const commonCalls = useCommonCalls()
useTauriEvent('code', async ({ payload }) => {
if (processing.value) {
@@ -42,7 +42,7 @@ useTauriEvent('code', async ({ payload }) => {
AppStorage.set('user', user)
invoke(InvokeCommand.StopServer)
route.go(Page.Home)
- emitRefetch(true)
+ commonCalls.fetchThreads(true)
}
finally {
processing.value = false