mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 11:42:30 +03:00
Add keyboard support for inbox and simplify code (#4380)
Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
parent
cf8ff1be31
commit
ed634ccabf
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ActionIcon, CheckBox, Component, IconMoreH, Label, showPopup } from '@hcengineering/ui'
|
import { ActionIcon, Component, IconMoreH, Label, showPopup } from '@hcengineering/ui'
|
||||||
import notification, {
|
import notification, {
|
||||||
ActivityNotificationViewlet,
|
ActivityNotificationViewlet,
|
||||||
DisplayInboxNotification,
|
DisplayInboxNotification,
|
||||||
@ -71,13 +71,13 @@
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<CheckBox
|
<!-- <CheckBox-->
|
||||||
circle
|
<!-- circle-->
|
||||||
kind="primary"
|
<!-- kind="primary"-->
|
||||||
on:value={(event) => {
|
<!-- on:value={(event) => {-->
|
||||||
dispatch('check', event.detail)
|
<!-- dispatch('check', event.detail)-->
|
||||||
}}
|
<!-- }}-->
|
||||||
/>
|
<!-- />-->
|
||||||
<NotifyContextIcon {value} />
|
<NotifyContextIcon {value} />
|
||||||
|
|
||||||
{#if presenterMixin?.labelPresenter}
|
{#if presenterMixin?.labelPresenter}
|
||||||
@ -135,10 +135,6 @@
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
max-width: 20.5rem;
|
max-width: 20.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--highlight-hover);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.labels {
|
.labels {
|
||||||
|
@ -32,7 +32,8 @@
|
|||||||
Scroller,
|
Scroller,
|
||||||
Separator,
|
Separator,
|
||||||
TabItem,
|
TabItem,
|
||||||
TabList
|
TabList,
|
||||||
|
Location
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import chunter from '@hcengineering/chunter'
|
import chunter from '@hcengineering/chunter'
|
||||||
import { Ref, WithLookup } from '@hcengineering/core'
|
import { Ref, WithLookup } from '@hcengineering/core'
|
||||||
@ -41,7 +42,7 @@
|
|||||||
|
|
||||||
import { InboxNotificationsClientImpl } from '../../inboxNotificationsClient'
|
import { InboxNotificationsClientImpl } from '../../inboxNotificationsClient'
|
||||||
import Filter from '../Filter.svelte'
|
import Filter from '../Filter.svelte'
|
||||||
import { getDisplayInboxNotifications } from '../../utils'
|
import { getDisplayInboxNotifications, resolveLocation } from '../../utils'
|
||||||
import { InboxNotificationsFilter } from '../../types'
|
import { InboxNotificationsFilter } from '../../types'
|
||||||
|
|
||||||
export let visibleNav: boolean = true
|
export let visibleNav: boolean = true
|
||||||
@ -89,17 +90,21 @@
|
|||||||
viewlets = res
|
viewlets = res
|
||||||
})
|
})
|
||||||
|
|
||||||
$: getDisplayInboxNotifications($notificationsByContextStore, filter).then((res) => {
|
$: displayNotifications = getDisplayInboxNotifications($notificationsByContextStore, filter)
|
||||||
displayNotifications = res
|
|
||||||
})
|
|
||||||
|
|
||||||
locationStore.subscribe((newLocation) => {
|
locationStore.subscribe((newLocation) => {
|
||||||
selectedContextId = newLocation.fragment as Ref<DocNotifyContext> | undefined
|
syncLocation(newLocation)
|
||||||
|
})
|
||||||
|
|
||||||
|
async function syncLocation (newLocation: Location) {
|
||||||
|
const loc = await resolveLocation(newLocation)
|
||||||
|
|
||||||
|
selectedContextId = loc?.loc.fragment as Ref<DocNotifyContext> | undefined
|
||||||
|
|
||||||
if (selectedContextId !== selectedContext?._id) {
|
if (selectedContextId !== selectedContext?._id) {
|
||||||
selectedContext = undefined
|
selectedContext = undefined
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
$: selectedContext = selectedContextId
|
$: selectedContext = selectedContextId
|
||||||
? selectedContext ?? $notifyContextsStore.find(({ _id }) => _id === selectedContextId)
|
? selectedContext ?? $notifyContextsStore.find(({ _id }) => _id === selectedContextId)
|
||||||
@ -242,7 +247,8 @@
|
|||||||
props={{
|
props={{
|
||||||
notifications: filteredNotifications,
|
notifications: filteredNotifications,
|
||||||
checkedContexts,
|
checkedContexts,
|
||||||
viewlets
|
viewlets,
|
||||||
|
selectedContext
|
||||||
}}
|
}}
|
||||||
on:click={selectContext}
|
on:click={selectContext}
|
||||||
/>
|
/>
|
||||||
|
@ -13,10 +13,9 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Scroller } from '@hcengineering/ui'
|
import { ListView } from '@hcengineering/ui'
|
||||||
import { ActivityNotificationViewlet, DisplayInboxNotification } from '@hcengineering/notification'
|
import { ActivityNotificationViewlet, DisplayInboxNotification, DocNotifyContext } from '@hcengineering/notification'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import { flip } from 'svelte/animate'
|
|
||||||
|
|
||||||
import InboxNotificationPresenter from './InboxNotificationPresenter.svelte'
|
import InboxNotificationPresenter from './InboxNotificationPresenter.svelte'
|
||||||
import { InboxNotificationsClientImpl } from '../../inboxNotificationsClient'
|
import { InboxNotificationsClientImpl } from '../../inboxNotificationsClient'
|
||||||
@ -29,44 +28,94 @@
|
|||||||
const inboxClient = InboxNotificationsClientImpl.getClient()
|
const inboxClient = InboxNotificationsClientImpl.getClient()
|
||||||
const notifyContextsStore = inboxClient.docNotifyContexts
|
const notifyContextsStore = inboxClient.docNotifyContexts
|
||||||
|
|
||||||
async function handleCheck (notification: DisplayInboxNotification, isChecked: boolean) {
|
let list: ListView
|
||||||
if (!isChecked) {
|
let listSelection = 0
|
||||||
return
|
let element: HTMLDivElement | undefined
|
||||||
}
|
|
||||||
|
|
||||||
await deleteInboxNotification(notification)
|
function onKeydown (key: KeyboardEvent): void {
|
||||||
|
if (key.code === 'ArrowUp') {
|
||||||
|
key.stopPropagation()
|
||||||
|
key.preventDefault()
|
||||||
|
list.select(listSelection - 1)
|
||||||
|
}
|
||||||
|
if (key.code === 'ArrowDown') {
|
||||||
|
key.stopPropagation()
|
||||||
|
key.preventDefault()
|
||||||
|
list.select(listSelection + 1)
|
||||||
|
}
|
||||||
|
if (key.code === 'Backspace') {
|
||||||
|
key.preventDefault()
|
||||||
|
key.stopPropagation()
|
||||||
|
|
||||||
|
const notification = notifications[listSelection]
|
||||||
|
|
||||||
|
deleteInboxNotification(notification)
|
||||||
|
}
|
||||||
|
if (key.code === 'Enter') {
|
||||||
|
key.preventDefault()
|
||||||
|
key.stopPropagation()
|
||||||
|
const notification = notifications[listSelection]
|
||||||
|
const context = $notifyContextsStore.find(({ _id }) => _id === notification.docNotifyContext)
|
||||||
|
dispatch('click', {
|
||||||
|
context,
|
||||||
|
notification
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: if (element) {
|
||||||
|
element.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
// async function handleCheck(notification: DisplayInboxNotification, isChecked: boolean) {
|
||||||
|
// if (!isChecked) {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// await deleteInboxNotification(notification)
|
||||||
|
// }
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Scroller>
|
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||||
{#each notifications as notification (notification._id)}
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<div animate:flip={{ duration: 500 }}>
|
<div class="root" bind:this={element} tabindex="0" on:keydown={onKeydown}>
|
||||||
<div class="notification gap-2 ml-2">
|
<ListView bind:this={list} bind:selection={listSelection} count={notifications.length}>
|
||||||
<!-- <div class="mt-6">-->
|
<svelte:fragment slot="item" let:item={itemIndex}>
|
||||||
<!-- <CheckBox-->
|
{@const notification = notifications[itemIndex]}
|
||||||
<!-- circle-->
|
{#key notification._id}
|
||||||
<!-- kind="primary"-->
|
<div class="notification gap-2 ml-2">
|
||||||
<!-- on:value={(event) => {-->
|
<!-- <div class="mt-6">-->
|
||||||
<!-- handleCheck(notification, event.detail)-->
|
<!-- <CheckBox-->
|
||||||
<!-- }}-->
|
<!-- circle-->
|
||||||
<!-- />-->
|
<!-- kind="primary"-->
|
||||||
<!-- </div>-->
|
<!-- on:value={(event) => {-->
|
||||||
<InboxNotificationPresenter
|
<!-- handleCheck(notification, event.detail)-->
|
||||||
value={notification}
|
<!-- }}-->
|
||||||
{viewlets}
|
<!-- />-->
|
||||||
onClick={() => {
|
<!-- </div>-->
|
||||||
dispatch('click', {
|
<InboxNotificationPresenter
|
||||||
context: $notifyContextsStore.find(({ _id }) => _id === notification.docNotifyContext),
|
value={notification}
|
||||||
notification
|
{viewlets}
|
||||||
})
|
onClick={() => {
|
||||||
}}
|
dispatch('click', {
|
||||||
/>
|
context: $notifyContextsStore.find(({ _id }) => _id === notification.docNotifyContext),
|
||||||
</div>
|
notification
|
||||||
</div>
|
})
|
||||||
{/each}
|
}}
|
||||||
</Scroller>
|
/>
|
||||||
|
</div>
|
||||||
|
{/key}
|
||||||
|
</svelte:fragment>
|
||||||
|
</ListView>
|
||||||
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
.root {
|
||||||
|
&:focus {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.notification {
|
.notification {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,9 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ActivityNotificationViewlet, DisplayInboxNotification, DocNotifyContext } from '@hcengineering/notification'
|
import { ActivityNotificationViewlet, DisplayInboxNotification, DocNotifyContext } from '@hcengineering/notification'
|
||||||
import { groupByArray, Ref } from '@hcengineering/core'
|
import { Ref } from '@hcengineering/core'
|
||||||
import { flip } from 'svelte/animate'
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
import { ListView } from '@hcengineering/ui'
|
||||||
|
|
||||||
import { InboxNotificationsClientImpl } from '../../inboxNotificationsClient'
|
import { InboxNotificationsClientImpl } from '../../inboxNotificationsClient'
|
||||||
import DocNotifyContextCard from '../DocNotifyContextCard.svelte'
|
import DocNotifyContextCard from '../DocNotifyContextCard.svelte'
|
||||||
@ -24,12 +25,33 @@
|
|||||||
export let notifications: DisplayInboxNotification[] = []
|
export let notifications: DisplayInboxNotification[] = []
|
||||||
export let viewlets: ActivityNotificationViewlet[] = []
|
export let viewlets: ActivityNotificationViewlet[] = []
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
const inboxClient = InboxNotificationsClientImpl.getClient()
|
const inboxClient = InboxNotificationsClientImpl.getClient()
|
||||||
const notifyContextsStore = inboxClient.docNotifyContexts
|
const notifyContextsStore = inboxClient.docNotifyContexts
|
||||||
|
|
||||||
let displayNotificationsByContext = new Map<Ref<DocNotifyContext>, DisplayInboxNotification[]>()
|
let list: ListView
|
||||||
|
let listSelection = 0
|
||||||
|
let element: HTMLDivElement | undefined
|
||||||
|
|
||||||
$: displayNotificationsByContext = groupByArray(notifications, ({ docNotifyContext }) => docNotifyContext)
|
let displayData: [Ref<DocNotifyContext>, DisplayInboxNotification[]][] = []
|
||||||
|
|
||||||
|
$: updateDisplayData(notifications)
|
||||||
|
|
||||||
|
function updateDisplayData (notifications: DisplayInboxNotification[]) {
|
||||||
|
const result: [Ref<DocNotifyContext>, DisplayInboxNotification[]][] = []
|
||||||
|
|
||||||
|
notifications.forEach((item) => {
|
||||||
|
const data = result.find(([_id]) => _id === item.docNotifyContext)
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
result.push([item.docNotifyContext, [item]])
|
||||||
|
} else {
|
||||||
|
data[1].push(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
displayData = result
|
||||||
|
}
|
||||||
|
|
||||||
async function handleCheck (context: DocNotifyContext, isChecked: boolean) {
|
async function handleCheck (context: DocNotifyContext, isChecked: boolean) {
|
||||||
if (!isChecked) {
|
if (!isChecked) {
|
||||||
@ -38,28 +60,73 @@
|
|||||||
|
|
||||||
await deleteContextNotifications(context)
|
await deleteContextNotifications(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onKeydown (key: KeyboardEvent): void {
|
||||||
|
if (key.code === 'ArrowUp') {
|
||||||
|
key.stopPropagation()
|
||||||
|
key.preventDefault()
|
||||||
|
list.select(listSelection - 1)
|
||||||
|
}
|
||||||
|
if (key.code === 'ArrowDown') {
|
||||||
|
key.stopPropagation()
|
||||||
|
key.preventDefault()
|
||||||
|
list.select(listSelection + 1)
|
||||||
|
}
|
||||||
|
if (key.code === 'Backspace') {
|
||||||
|
key.preventDefault()
|
||||||
|
key.stopPropagation()
|
||||||
|
|
||||||
|
const context = $notifyContextsStore.find(({ _id }) => _id === displayData[listSelection][0])
|
||||||
|
|
||||||
|
deleteContextNotifications(context)
|
||||||
|
}
|
||||||
|
if (key.code === 'Enter') {
|
||||||
|
key.preventDefault()
|
||||||
|
key.stopPropagation()
|
||||||
|
const context = $notifyContextsStore.find(({ _id }) => _id === displayData[listSelection][0])
|
||||||
|
dispatch('click', { context })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: if (element) {
|
||||||
|
element.focus()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#each displayNotificationsByContext as [contextId, contextNotifications] (contextId)}
|
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||||
<div animate:flip={{ duration: 500 }}>
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
{#if contextNotifications.length}
|
<div class="root" bind:this={element} tabindex="0" on:keydown={onKeydown}>
|
||||||
|
<ListView bind:this={list} bind:selection={listSelection} count={displayData.length}>
|
||||||
|
<svelte:fragment slot="item" let:item={itemIndex}>
|
||||||
|
{@const contextId = displayData[itemIndex][0]}
|
||||||
|
{@const contextNotifications = displayData[itemIndex][1]}
|
||||||
{@const context = $notifyContextsStore.find(({ _id }) => _id === contextId)}
|
{@const context = $notifyContextsStore.find(({ _id }) => _id === contextId)}
|
||||||
|
{#key contextId}
|
||||||
{#if context}
|
{#if context}
|
||||||
<DocNotifyContextCard
|
<DocNotifyContextCard
|
||||||
value={context}
|
value={context}
|
||||||
notifications={contextNotifications}
|
notifications={contextNotifications}
|
||||||
{viewlets}
|
{viewlets}
|
||||||
on:click
|
on:click={(event) => {
|
||||||
on:check={(event) => handleCheck(context, event.detail)}
|
dispatch('click', event.detail)
|
||||||
/>
|
listSelection = itemIndex
|
||||||
<div class="separator" />
|
}}
|
||||||
{/if}
|
on:check={(event) => handleCheck(context, event.detail)}
|
||||||
{/if}
|
/>
|
||||||
</div>
|
<div class="separator" />
|
||||||
{/each}
|
{/if}
|
||||||
|
{/key}
|
||||||
|
</svelte:fragment>
|
||||||
|
</ListView>
|
||||||
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
.root {
|
||||||
|
&:focus {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.separator {
|
.separator {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
import { type ActivityMessage } from '@hcengineering/activity'
|
import activity, { type ActivityMessage } from '@hcengineering/activity'
|
||||||
import { SortingOrder, getCurrentAccount, type Class, type Doc, type Ref, type WithLookup } from '@hcengineering/core'
|
import { SortingOrder, getCurrentAccount, type Class, type Doc, type Ref, type WithLookup } from '@hcengineering/core'
|
||||||
import notification, {
|
import notification, {
|
||||||
type ActivityInboxNotification,
|
type ActivityInboxNotification,
|
||||||
@ -22,7 +22,7 @@ import notification, {
|
|||||||
type InboxNotificationsClient
|
type InboxNotificationsClient
|
||||||
} from '@hcengineering/notification'
|
} from '@hcengineering/notification'
|
||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { derived, writable } from 'svelte/store'
|
import { derived, get, writable } from 'svelte/store'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -33,7 +33,19 @@ export class InboxNotificationsClientImpl implements InboxNotificationsClient {
|
|||||||
readonly docNotifyContexts = writable<DocNotifyContext[]>([])
|
readonly docNotifyContexts = writable<DocNotifyContext[]>([])
|
||||||
readonly docNotifyContextByDoc = writable<Map<Ref<Doc>, DocNotifyContext>>(new Map())
|
readonly docNotifyContextByDoc = writable<Map<Ref<Doc>, DocNotifyContext>>(new Map())
|
||||||
|
|
||||||
readonly inboxNotifications = writable<InboxNotification[]>([])
|
readonly activityInboxNotifications = writable<Array<WithLookup<ActivityInboxNotification>>>([])
|
||||||
|
readonly otherInboxNotifications = writable<InboxNotification[]>([])
|
||||||
|
|
||||||
|
readonly inboxNotifications = derived(
|
||||||
|
[this.activityInboxNotifications, this.otherInboxNotifications],
|
||||||
|
([activityNotifications, otherNotifications]) => {
|
||||||
|
return otherNotifications
|
||||||
|
.concat(activityNotifications)
|
||||||
|
.sort((n1, n2) => (n2.createdOn ?? n2.modifiedOn) - (n1.createdOn ?? n1.modifiedOn))
|
||||||
|
},
|
||||||
|
[] as InboxNotification[]
|
||||||
|
)
|
||||||
|
|
||||||
readonly inboxNotificationsByContext = derived(
|
readonly inboxNotificationsByContext = derived(
|
||||||
[this.docNotifyContexts, this.inboxNotifications],
|
[this.docNotifyContexts, this.inboxNotifications],
|
||||||
([notifyContexts, inboxNotifications]) => {
|
([notifyContexts, inboxNotifications]) => {
|
||||||
@ -53,20 +65,11 @@ export class InboxNotificationsClientImpl implements InboxNotificationsClient {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
readonly activityInboxNotifications = derived(
|
|
||||||
[this.inboxNotifications],
|
|
||||||
([notifications]) =>
|
|
||||||
notifications.filter(
|
|
||||||
(n): n is ActivityInboxNotification => n._class === notification.class.ActivityInboxNotification
|
|
||||||
),
|
|
||||||
[] as ActivityInboxNotification[]
|
|
||||||
)
|
|
||||||
|
|
||||||
private readonly docNotifyContextsQuery = createQuery(true)
|
private readonly docNotifyContextsQuery = createQuery(true)
|
||||||
private readonly inboxNotificationsQuery = createQuery(true)
|
private readonly otherInboxNotificationsQuery = createQuery(true)
|
||||||
|
private readonly activityInboxNotificationsQuery = createQuery(true)
|
||||||
|
|
||||||
private _docNotifyContextByDoc = new Map<Ref<Doc>, DocNotifyContext>()
|
private _docNotifyContextByDoc = new Map<Ref<Doc>, DocNotifyContext>()
|
||||||
private _inboxNotifications: Array<WithLookup<InboxNotification>> = []
|
|
||||||
|
|
||||||
private constructor () {
|
private constructor () {
|
||||||
this.docNotifyContextsQuery.query(
|
this.docNotifyContextsQuery.query(
|
||||||
@ -80,14 +83,14 @@ export class InboxNotificationsClientImpl implements InboxNotificationsClient {
|
|||||||
this.docNotifyContextByDoc.set(this._docNotifyContextByDoc)
|
this.docNotifyContextByDoc.set(this._docNotifyContextByDoc)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
this.inboxNotificationsQuery.query(
|
this.otherInboxNotificationsQuery.query(
|
||||||
notification.class.InboxNotification,
|
notification.class.InboxNotification,
|
||||||
{
|
{
|
||||||
|
_class: { $nin: [notification.class.ActivityInboxNotification] },
|
||||||
user: getCurrentAccount()._id
|
user: getCurrentAccount()._id
|
||||||
},
|
},
|
||||||
(result: InboxNotification[]) => {
|
(result: InboxNotification[]) => {
|
||||||
this.inboxNotifications.set(result)
|
this.otherInboxNotifications.set(result)
|
||||||
this._inboxNotifications = result
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sort: {
|
sort: {
|
||||||
@ -95,6 +98,24 @@ export class InboxNotificationsClientImpl implements InboxNotificationsClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
this.activityInboxNotificationsQuery.query(
|
||||||
|
notification.class.ActivityInboxNotification,
|
||||||
|
{
|
||||||
|
user: getCurrentAccount()._id
|
||||||
|
},
|
||||||
|
(result: ActivityInboxNotification[]) => {
|
||||||
|
this.activityInboxNotifications.set(result)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sort: {
|
||||||
|
createdOn: SortingOrder.Descending
|
||||||
|
},
|
||||||
|
lookup: {
|
||||||
|
attachedTo: activity.class.ActivityMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
static createClient (): InboxNotificationsClientImpl {
|
static createClient (): InboxNotificationsClientImpl {
|
||||||
@ -117,7 +138,7 @@ export class InboxNotificationsClientImpl implements InboxNotificationsClient {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const inboxNotifications = this._inboxNotifications.filter(
|
const inboxNotifications = get(this.inboxNotifications).filter(
|
||||||
(notification) => notification.docNotifyContext === docNotifyContext._id && !notification.isViewed
|
(notification) => notification.docNotifyContext === docNotifyContext._id && !notification.isViewed
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -180,7 +201,7 @@ export class InboxNotificationsClientImpl implements InboxNotificationsClient {
|
|||||||
async readMessages (ids: Array<Ref<ActivityMessage>>): Promise<void> {
|
async readMessages (ids: Array<Ref<ActivityMessage>>): Promise<void> {
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
||||||
const notificationsToRead = this._inboxNotifications
|
const notificationsToRead = get(this.inboxNotifications)
|
||||||
.filter((n): n is ActivityInboxNotification => n._class === notification.class.ActivityInboxNotification)
|
.filter((n): n is ActivityInboxNotification => n._class === notification.class.ActivityInboxNotification)
|
||||||
.filter(({ attachedTo, isViewed }) => ids.includes(attachedTo) && !isViewed)
|
.filter(({ attachedTo, isViewed }) => ids.includes(attachedTo) && !isViewed)
|
||||||
|
|
||||||
@ -191,7 +212,7 @@ export class InboxNotificationsClientImpl implements InboxNotificationsClient {
|
|||||||
|
|
||||||
async readNotifications (ids: Array<Ref<InboxNotification>>): Promise<void> {
|
async readNotifications (ids: Array<Ref<InboxNotification>>): Promise<void> {
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
const notificationsToRead = this._inboxNotifications.filter(({ _id }) => ids.includes(_id))
|
const notificationsToRead = get(this.inboxNotifications).filter(({ _id }) => ids.includes(_id))
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
notificationsToRead.map(async (notification) => await client.update(notification, { isViewed: true }))
|
notificationsToRead.map(async (notification) => await client.update(notification, { isViewed: true }))
|
||||||
@ -200,7 +221,7 @@ export class InboxNotificationsClientImpl implements InboxNotificationsClient {
|
|||||||
|
|
||||||
async unreadNotifications (ids: Array<Ref<InboxNotification>>): Promise<void> {
|
async unreadNotifications (ids: Array<Ref<InboxNotification>>): Promise<void> {
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
const notificationsToUnread = this._inboxNotifications.filter(({ _id }) => ids.includes(_id))
|
const notificationsToUnread = get(this.inboxNotifications).filter(({ _id }) => ids.includes(_id))
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
notificationsToUnread.map(async (notification) => await client.update(notification, { isViewed: false }))
|
notificationsToUnread.map(async (notification) => await client.update(notification, { isViewed: false }))
|
||||||
@ -209,7 +230,7 @@ export class InboxNotificationsClientImpl implements InboxNotificationsClient {
|
|||||||
|
|
||||||
async deleteNotifications (ids: Array<Ref<InboxNotification>>): Promise<void> {
|
async deleteNotifications (ids: Array<Ref<InboxNotification>>): Promise<void> {
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
const inboxNotifications = this._inboxNotifications.filter(({ _id }) => ids.includes(_id))
|
const inboxNotifications = get(this.inboxNotifications).filter(({ _id }) => ids.includes(_id))
|
||||||
await Promise.all(inboxNotifications.map(async (notification) => await client.remove(notification)))
|
await Promise.all(inboxNotifications.map(async (notification) => await client.remove(notification)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,8 @@ import {
|
|||||||
canUnReadNotifyContext,
|
canUnReadNotifyContext,
|
||||||
readNotifyContext,
|
readNotifyContext,
|
||||||
unReadNotifyContext,
|
unReadNotifyContext,
|
||||||
deleteContextNotifications
|
deleteContextNotifications,
|
||||||
|
hasInboxNotifications
|
||||||
} from './utils'
|
} from './utils'
|
||||||
|
|
||||||
import { InboxNotificationsClientImpl } from './inboxNotificationsClient'
|
import { InboxNotificationsClientImpl } from './inboxNotificationsClient'
|
||||||
@ -85,7 +86,8 @@ export default async (): Promise<Resources> => ({
|
|||||||
IsDocNotifyContextTracked: isDocNotifyContextVisible,
|
IsDocNotifyContextTracked: isDocNotifyContextVisible,
|
||||||
HasHiddenDocNotifyContext: hasHiddenDocNotifyContext,
|
HasHiddenDocNotifyContext: hasHiddenDocNotifyContext,
|
||||||
CanReadNotifyContext: canReadNotifyContext,
|
CanReadNotifyContext: canReadNotifyContext,
|
||||||
CanUnReadNotifyContext: canUnReadNotifyContext
|
CanUnReadNotifyContext: canUnReadNotifyContext,
|
||||||
|
HasInboxNotifications: hasInboxNotifications
|
||||||
},
|
},
|
||||||
actionImpl: {
|
actionImpl: {
|
||||||
Unsubscribe: unsubscribe,
|
Unsubscribe: unsubscribe,
|
||||||
|
@ -21,7 +21,8 @@ import {
|
|||||||
getCurrentAccount,
|
getCurrentAccount,
|
||||||
type Ref,
|
type Ref,
|
||||||
SortingOrder,
|
SortingOrder,
|
||||||
type TxOperations
|
type TxOperations,
|
||||||
|
type WithLookup
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import notification, {
|
import notification, {
|
||||||
type ActivityInboxNotification,
|
type ActivityInboxNotification,
|
||||||
@ -203,7 +204,11 @@ export async function unReadNotifyContext (doc: DocNotifyContext): Promise<void>
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export async function deleteContextNotifications (doc: DocNotifyContext): Promise<void> {
|
export async function deleteContextNotifications (doc?: DocNotifyContext): Promise<void> {
|
||||||
|
if (doc === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
const inboxClient = InboxNotificationsClientImpl.getClient()
|
const inboxClient = InboxNotificationsClientImpl.getClient()
|
||||||
const inboxNotifications = get(inboxClient.inboxNotificationsByContext).get(doc._id) ?? []
|
const inboxNotifications = get(inboxClient.inboxNotificationsByContext).get(doc._id) ?? []
|
||||||
@ -324,9 +329,11 @@ async function generateLocation (
|
|||||||
const workspace = loc.path[1] ?? ''
|
const workspace = loc.path[1] ?? ''
|
||||||
const messageId = loc.query?.message as Ref<ActivityMessage> | undefined
|
const messageId = loc.query?.message as Ref<ActivityMessage> | undefined
|
||||||
|
|
||||||
const context = await client.findOne(notification.class.DocNotifyContext, { _id: contextId })
|
const contextNotification = await client.findOne(notification.class.InboxNotification, {
|
||||||
|
docNotifyContext: contextId
|
||||||
|
})
|
||||||
|
|
||||||
if (context === undefined) {
|
if (contextNotification === undefined) {
|
||||||
return {
|
return {
|
||||||
loc: {
|
loc: {
|
||||||
path: [loc.path[0], loc.path[1], inboxId],
|
path: [loc.path[0], loc.path[1], inboxId],
|
||||||
@ -355,12 +362,11 @@ async function generateLocation (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getDisplayInboxNotifications (
|
export function getDisplayInboxNotifications (
|
||||||
notificationsByContext: Map<Ref<DocNotifyContext>, InboxNotification[]>,
|
notificationsByContext: Map<Ref<DocNotifyContext>, InboxNotification[]>,
|
||||||
filter: InboxNotificationsFilter,
|
filter: InboxNotificationsFilter = 'all',
|
||||||
objectClass?: Ref<Class<Doc>>
|
objectClass?: Ref<Class<Doc>>
|
||||||
): Promise<DisplayInboxNotification[]> {
|
): DisplayInboxNotification[] {
|
||||||
const client = getClient()
|
|
||||||
const filteredNotifications = Array.from(notificationsByContext.values())
|
const filteredNotifications = Array.from(notificationsByContext.values())
|
||||||
.flat()
|
.flat()
|
||||||
.filter(({ isViewed }) => {
|
.filter(({ isViewed }) => {
|
||||||
@ -377,20 +383,14 @@ export async function getDisplayInboxNotifications (
|
|||||||
})
|
})
|
||||||
|
|
||||||
const activityNotifications = filteredNotifications.filter(
|
const activityNotifications = filteredNotifications.filter(
|
||||||
(n): n is ActivityInboxNotification => n._class === notification.class.ActivityInboxNotification
|
(n): n is WithLookup<ActivityInboxNotification> => n._class === notification.class.ActivityInboxNotification
|
||||||
)
|
)
|
||||||
const displayNotifications: DisplayInboxNotification[] = filteredNotifications.filter(
|
const displayNotifications: DisplayInboxNotification[] = filteredNotifications.filter(
|
||||||
({ _class }) => _class !== notification.class.ActivityInboxNotification
|
({ _class }) => _class !== notification.class.ActivityInboxNotification
|
||||||
)
|
)
|
||||||
|
|
||||||
const messages: Array<ActivityMessage | undefined> = await Promise.all(
|
const messages: ActivityMessage[] = activityNotifications
|
||||||
activityNotifications.map(
|
.map((activityNotification) => activityNotification.$lookup?.attachedTo)
|
||||||
async (activityNotification) =>
|
|
||||||
await client.findOne(activity.class.ActivityMessage, { _id: activityNotification.attachedTo })
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
const filteredMessages = messages
|
|
||||||
.filter((message): message is ActivityMessage => {
|
.filter((message): message is ActivityMessage => {
|
||||||
if (message === undefined) {
|
if (message === undefined) {
|
||||||
return false
|
return false
|
||||||
@ -406,7 +406,7 @@ export async function getDisplayInboxNotifications (
|
|||||||
})
|
})
|
||||||
.sort(activityMessagesComparator)
|
.sort(activityMessagesComparator)
|
||||||
|
|
||||||
const combinedMessages = combineActivityMessages(filteredMessages, SortingOrder.Descending)
|
const combinedMessages = combineActivityMessages(messages, SortingOrder.Descending)
|
||||||
|
|
||||||
for (const message of combinedMessages) {
|
for (const message of combinedMessages) {
|
||||||
if (message._class === activity.class.DocUpdateMessage) {
|
if (message._class === activity.class.DocUpdateMessage) {
|
||||||
@ -442,3 +442,11 @@ export async function getDisplayInboxNotifications (
|
|||||||
|
|
||||||
return displayNotifications
|
return displayNotifications
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function hasInboxNotifications (
|
||||||
|
notificationsByContext: Map<Ref<DocNotifyContext>, InboxNotification[]>
|
||||||
|
): Promise<boolean> {
|
||||||
|
const displayNotifications = getDisplayInboxNotifications(notificationsByContext)
|
||||||
|
|
||||||
|
return displayNotifications.some(({ isViewed }) => !isViewed)
|
||||||
|
}
|
||||||
|
@ -272,8 +272,8 @@ export interface DocNotifyContext extends Doc {
|
|||||||
export interface InboxNotificationsClient {
|
export interface InboxNotificationsClient {
|
||||||
docNotifyContextByDoc: Writable<Map<Ref<Doc>, DocNotifyContext>>
|
docNotifyContextByDoc: Writable<Map<Ref<Doc>, DocNotifyContext>>
|
||||||
docNotifyContexts: Writable<DocNotifyContext[]>
|
docNotifyContexts: Writable<DocNotifyContext[]>
|
||||||
inboxNotifications: Writable<InboxNotification[]>
|
inboxNotifications: Readable<InboxNotification[]>
|
||||||
activityInboxNotifications: Readable<ActivityInboxNotification[]>
|
activityInboxNotifications: Writable<ActivityInboxNotification[]>
|
||||||
inboxNotificationsByContext: Readable<Map<Ref<DocNotifyContext>, InboxNotification[]>>
|
inboxNotificationsByContext: Readable<Map<Ref<DocNotifyContext>, InboxNotification[]>>
|
||||||
readDoc: (_id: Ref<Doc>) => Promise<void>
|
readDoc: (_id: Ref<Doc>) => Promise<void>
|
||||||
forceReadDoc: (_id: Ref<Doc>, _class: Ref<Class<Doc>>) => Promise<void>
|
forceReadDoc: (_id: Ref<Doc>, _class: Ref<Class<Doc>>) => Promise<void>
|
||||||
@ -393,7 +393,10 @@ const notification = plugin(notificationId, {
|
|||||||
GetInboxNotificationsClient: '' as Resource<InboxNotificationsClientFactory>,
|
GetInboxNotificationsClient: '' as Resource<InboxNotificationsClientFactory>,
|
||||||
HasHiddenDocNotifyContext: '' as Resource<(doc: Doc[]) => Promise<boolean>>,
|
HasHiddenDocNotifyContext: '' as Resource<(doc: Doc[]) => Promise<boolean>>,
|
||||||
IsDocNotifyContextHidden: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>,
|
IsDocNotifyContextHidden: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>,
|
||||||
IsDocNotifyContextTracked: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>
|
IsDocNotifyContextTracked: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>,
|
||||||
|
HasInboxNotifications: '' as Resource<
|
||||||
|
(notificationsByContext: Map<Ref<DocNotifyContext>, InboxNotification[]>) => Promise<boolean>
|
||||||
|
>
|
||||||
},
|
},
|
||||||
resolver: {
|
resolver: {
|
||||||
Location: '' as Resource<(loc: Location) => Promise<ResolvedLocation | undefined>>
|
Location: '' as Resource<(loc: Location) => Promise<ResolvedLocation | undefined>>
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
import contact, { Employee, PersonAccount } from '@hcengineering/contact'
|
import contact, { Employee, PersonAccount } from '@hcengineering/contact'
|
||||||
import core, { Class, Doc, getCurrentAccount, Ref, setCurrentAccount, Space } from '@hcengineering/core'
|
import core, { Class, Doc, getCurrentAccount, Ref, setCurrentAccount, Space } from '@hcengineering/core'
|
||||||
import login from '@hcengineering/login'
|
import login from '@hcengineering/login'
|
||||||
import notification, { inboxId } from '@hcengineering/notification'
|
import notification, { DocNotifyContext, inboxId, InboxNotification } from '@hcengineering/notification'
|
||||||
import { BrowserNotificatator, InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
|
import { BrowserNotificatator, InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
|
||||||
import { broadcastEvent, getMetadata, getResource, IntlString } from '@hcengineering/platform'
|
import { broadcastEvent, getMetadata, getResource, IntlString } from '@hcengineering/platform'
|
||||||
import { ActionContext, createQuery, getClient } from '@hcengineering/presentation'
|
import { ActionContext, createQuery, getClient } from '@hcengineering/presentation'
|
||||||
@ -162,17 +162,21 @@
|
|||||||
)
|
)
|
||||||
|
|
||||||
const workspaceId = $location.path[1]
|
const workspaceId = $location.path[1]
|
||||||
|
const inboxClient = InboxNotificationsClientImpl.getClient()
|
||||||
|
const inboxNotificationsByContextStore = inboxClient.inboxNotificationsByContext
|
||||||
|
|
||||||
|
let hasNotificationsFn: ((data: Map<Ref<DocNotifyContext>, InboxNotification[]>) => Promise<boolean>) | undefined =
|
||||||
|
undefined
|
||||||
let hasInboxNotifications = false
|
let hasInboxNotifications = false
|
||||||
let syncPromise: Promise<void> | undefined = undefined
|
let syncPromise: Promise<void> | undefined = undefined
|
||||||
let locUpdate = 0
|
let locUpdate = 0
|
||||||
|
|
||||||
const notificationClient = InboxNotificationsClientImpl.getClient()
|
getResource(notification.function.HasInboxNotifications).then((f) => {
|
||||||
|
hasNotificationsFn = f
|
||||||
|
})
|
||||||
|
|
||||||
notificationClient.inboxNotificationsByContext.subscribe((inboxNotificationsByContext) => {
|
$: hasNotificationsFn?.($inboxNotificationsByContextStore).then((res) => {
|
||||||
hasInboxNotifications = Array.from(inboxNotificationsByContext.entries()).some(([_, notifications]) =>
|
hasInboxNotifications = res
|
||||||
notifications.some(({ isViewed }) => !isViewed)
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const doSyncLoc = async (loc: Location, iteration: number): Promise<void> => {
|
const doSyncLoc = async (loc: Location, iteration: number): Promise<void> => {
|
||||||
|
Loading…
Reference in New Issue
Block a user