mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-13 19:58:09 +00:00
TSK-1051 TSK-1052 TSK-1061 Inbox actions (#2914)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
44de234d01
commit
210d3eebc6
@ -30,6 +30,8 @@
|
||||
"@hcengineering/ui": "^0.6.3",
|
||||
"@hcengineering/platform": "^0.6.8",
|
||||
"@hcengineering/model-core": "^0.6.0",
|
||||
"@hcengineering/model-view": "^0.6.0",
|
||||
"@hcengineering/view": "^0.6.2",
|
||||
"@hcengineering/workbench": "^0.6.2",
|
||||
"@hcengineering/notification": "^0.6.8",
|
||||
"@hcengineering/setting": "^0.6.3"
|
||||
|
@ -36,6 +36,7 @@ import setting from '@hcengineering/setting'
|
||||
import workbench from '@hcengineering/workbench'
|
||||
import notification from './plugin'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
import view, { createAction } from '@hcengineering/model-view'
|
||||
|
||||
export const DOMAIN_NOTIFICATION = 'notification' as Domain
|
||||
|
||||
@ -143,9 +144,12 @@ export class TDocUpdates extends TDoc implements DocUpdates {
|
||||
@Index(IndexKind.Indexed)
|
||||
attachedTo!: Ref<Doc>
|
||||
|
||||
@Index(IndexKind.Indexed)
|
||||
hidden!: boolean
|
||||
|
||||
attachedToClass!: Ref<Class<Doc>>
|
||||
lastTx?: Ref<TxCUD<Doc>>
|
||||
lastTxTime?: Timestamp
|
||||
lastTx!: Ref<TxCUD<Doc>>
|
||||
lastTxTime!: Timestamp
|
||||
txes!: [Ref<TxCUD<Doc>>, Timestamp][]
|
||||
}
|
||||
|
||||
@ -240,6 +244,52 @@ export function createModel (builder: Builder): void {
|
||||
},
|
||||
notification.app.Notification
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: notification.actionImpl.MarkAsUnread,
|
||||
actionProps: {},
|
||||
label: notification.string.MarkAsUnread,
|
||||
icon: notification.icon.Track,
|
||||
input: 'focus',
|
||||
visibilityTester: notification.function.HasntNotifications,
|
||||
category: notification.category.Notification,
|
||||
target: notification.class.DocUpdates,
|
||||
context: { mode: 'context', application: notification.app.Notification, group: 'edit' }
|
||||
},
|
||||
notification.action.MarkAsUnread
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: notification.actionImpl.Hide,
|
||||
actionProps: {},
|
||||
label: notification.string.Hide,
|
||||
icon: notification.icon.Hide,
|
||||
input: 'focus',
|
||||
category: notification.category.Notification,
|
||||
target: notification.class.DocUpdates,
|
||||
context: { mode: 'context', application: notification.app.Notification, group: 'edit' }
|
||||
},
|
||||
notification.action.Hide
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: notification.actionImpl.Unsubscribe,
|
||||
actionProps: {},
|
||||
label: notification.string.DontTrack,
|
||||
icon: view.icon.Delete,
|
||||
input: 'focus',
|
||||
category: notification.category.Notification,
|
||||
target: notification.class.DocUpdates,
|
||||
context: { mode: 'context', application: notification.app.Notification, group: 'edit' }
|
||||
},
|
||||
notification.action.Unsubscribe
|
||||
)
|
||||
}
|
||||
|
||||
export { notificationOperation } from './migration'
|
||||
|
@ -184,11 +184,25 @@ async function fillCollaborators (client: MigrationClient): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function fillDocUpdatesHidder (client: MigrationClient): Promise<void> {
|
||||
await client.update(
|
||||
DOMAIN_NOTIFICATION,
|
||||
{
|
||||
_class: notification.class.DocUpdates,
|
||||
hidden: { $exists: false }
|
||||
},
|
||||
{
|
||||
hidden: false
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export const notificationOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient): Promise<void> {
|
||||
await fillNotificationText(client)
|
||||
await migrateLastView(client)
|
||||
await fillCollaborators(client)
|
||||
await fillDocUpdatesHidder(client)
|
||||
},
|
||||
async upgrade (client: MigrationUpgradeClient): Promise<void> {
|
||||
await createSpace(client)
|
||||
|
@ -14,11 +14,12 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { Application } from '@hcengineering/workbench'
|
||||
import { Doc, Ref } from '@hcengineering/core'
|
||||
import notification, { notificationId } from '@hcengineering/notification'
|
||||
import { IntlString, mergeIds } from '@hcengineering/platform'
|
||||
import { IntlString, Resource, mergeIds } from '@hcengineering/platform'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
import { Action, ActionCategory, ViewAction } from '@hcengineering/view'
|
||||
import { Application } from '@hcengineering/workbench'
|
||||
|
||||
export default mergeIds(notificationId, notification, {
|
||||
string: {
|
||||
@ -29,12 +30,30 @@ export default mergeIds(notificationId, notification, {
|
||||
PlatformNotification: '' as IntlString,
|
||||
BrowserNotification: '' as IntlString,
|
||||
EmailNotification: '' as IntlString,
|
||||
Collaborators: '' as IntlString
|
||||
Collaborators: '' as IntlString,
|
||||
Hide: '' as IntlString,
|
||||
MarkAsUnread: '' as IntlString
|
||||
},
|
||||
app: {
|
||||
Notification: '' as Ref<Application>
|
||||
},
|
||||
component: {
|
||||
NotificationSettings: '' as AnyComponent
|
||||
},
|
||||
function: {
|
||||
HasntNotifications: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>
|
||||
},
|
||||
category: {
|
||||
Notification: '' as Ref<ActionCategory>
|
||||
},
|
||||
action: {
|
||||
Unsubscribe: '' as Ref<Action>,
|
||||
Hide: '' as Ref<Action>,
|
||||
MarkAsUnread: '' as Ref<Action>
|
||||
},
|
||||
actionImpl: {
|
||||
Unsubscribe: '' as ViewAction,
|
||||
Hide: '' as ViewAction,
|
||||
MarkAsUnread: '' as ViewAction
|
||||
}
|
||||
})
|
||||
|
@ -10,4 +10,12 @@
|
||||
<symbol id="donttrack" viewBox="0 0 24 24">
|
||||
<path d="M21.7,15l-0.2,0.1L21.7,15L21,13.8c-0.6-1.1-1.1-2.3-1.2-3.4l-0.4-2.7C19,3.9,15.8,1,12,1S5,3.9,4.5,7.7l-0.4,2.7 c-0.1,1.2-0.5,2.4-1.2,3.4L2.2,15c-0.7,1.2-1.1,1.8-1,2.6c0.1,0.5,0.4,1,0.7,1.3c0.6,0.5,1.3,0.5,2.7,0.5h3c0.2,1,0.8,1.9,1.5,2.5 C10.1,22.6,11,23,12,23c1,0,2-0.4,2.7-1.1c0.7-0.6,1.2-1.5,1.5-2.5h3c1.4,0,2.1,0,2.7-0.5c0.4-0.4,0.6-0.8,0.7-1.3 C22.9,16.8,22.6,16.2,21.7,15z M14.5,19.4c-0.2,0.5-0.5,0.8-0.8,1.2l0,0c-1,0.8-2.3,0.8-3.2,0c-0.4-0.2-0.6-0.7-0.8-1.2H14.5z M20.9,17.5c-0.1,0.1-1,0.1-1.5,0.1H4.8c-0.6,0-1.4,0-1.5-0.1l-0.1-0.1c0-0.1,0.5-0.8,0.7-1.4l0.7-1.2c0.7-1.3,1.2-2.6,1.4-4.2 l0.4-2.7c0.4-3,2.7-5.1,5.7-5.1s5.4,2.1,5.7,5.1l0.4,2.7c0.1,1.5,0.6,2.9,1.4,4.2l0.7,1.2c0.4,0.6,0.7,1.2,0.7,1.3 C21,17.4,20.9,17.4,20.9,17.5z" />
|
||||
</symbol>
|
||||
<symbol id="hide" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M4.5,8.4C6.3,6.9,9,5.2,12,5.2c3,0,5.7,1.6,7.5,3.2c0.9,0.8,1.7,1.6,2.2,2.1c0.3,0.3,0.5,0.5,0.6,0.7c0.1,0.1,0.1,0.2,0.2,0.2c0,0,0,0,0,0.1l0,0l0,0l0,0c0,0,0,0-0.6,0.4c0.6,0.4,0.6,0.4,0.6,0.4l0,0l0,0l0,0c0,0,0,0,0,0.1c0,0-0.1,0.1-0.2,0.2c-0.1,0.2-0.3,0.4-0.6,0.7c-0.5,0.6-1.3,1.4-2.2,2.1c-1.9,1.5-4.5,3.2-7.5,3.2c-3,0-5.7-1.6-7.5-3.2c-0.9-0.8-1.7-1.6-2.2-2.1c-0.3-0.3-0.5-0.5-0.6-0.7c-0.1-0.1-0.1-0.2-0.2-0.2c0,0,0,0,0-0.1l0,0l0,0l0,0c0,0,0,0,0.6-0.4c-0.6-0.4-0.6-0.4-0.6-0.4l0,0l0,0l0,0c0,0,0,0,0-0.1c0,0,0.1-0.1,0.2-0.2c0.1-0.2,0.3-0.4,0.6-0.7C2.8,10,3.5,9.2,4.5,8.4z M2,12l-0.6-0.4c-0.2,0.3-0.2,0.6,0,0.9L2,12z M3,12c0.1,0.1,0.2,0.3,0.4,0.4c0.5,0.5,1.2,1.3,2.1,2c1.8,1.5,4.1,2.8,6.6,2.8c2.5,0,4.8-1.4,6.6-2.8c0.9-0.7,1.6-1.4,2.1-2c0.1-0.2,0.3-0.3,0.4-0.4c-0.1-0.1-0.2-0.3-0.4-0.4c-0.5-0.5-1.2-1.3-2.1-2c-1.8-1.5-4.1-2.8-6.6-2.8c-2.5,0-4.8,1.4-6.6,2.8c-0.9,0.7-1.6,1.4-2.1,2C3.2,11.7,3.1,11.9,3,12z M22,12l0.6,0.4c0.2-0.3,0.2-0.6,0-0.9L22,12z"
|
||||
/>
|
||||
<path
|
||||
d="M8.8,12c0-1.8,1.5-3.2,3.2-3.2s3.2,1.5,3.2,3.2s-1.5,3.2-3.2,3.2S8.8,13.8,8.8,12z M12,10.2c-1,0-1.8,0.8-1.8,1.8s0.8,1.8,1.8,1.8s1.8-0.8,1.8-1.8S13,10.2,12,10.2z"
|
||||
/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 4.1 KiB |
@ -16,6 +16,8 @@
|
||||
"RemoveAll": "Delete all notifications",
|
||||
"MarkAllAsRead": "Mark all notifications as read",
|
||||
"MarkAsRead": "Mark as read",
|
||||
"MarkAsUnread": "Mark as unread",
|
||||
"Hide": "Hide",
|
||||
"Inbox": "Inbox",
|
||||
"Collaborators": "Collaborators"
|
||||
}
|
||||
|
@ -16,6 +16,8 @@
|
||||
"RemoveAll": "Удалить все нотификации",
|
||||
"MarkAllAsRead": "Отметить все нотификации как прочитанные",
|
||||
"MarkAsRead": "Отметить нотификация прочитанной",
|
||||
"MarkAsUnread": "Отметить непрочитанным",
|
||||
"Hide": "Скрыть",
|
||||
"Inbox": "Inbox",
|
||||
"Collaborators": "Участники"
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ const icons = require('../assets/icons.svg') as string // eslint-disable-line
|
||||
loadMetadata(notification.icon, {
|
||||
Notifications: `${icons}#notification`,
|
||||
Track: `${icons}#track`,
|
||||
Hide: `${icons}#hide`,
|
||||
DontTrack: `${icons}#donttrack`
|
||||
})
|
||||
|
||||
|
@ -34,7 +34,8 @@
|
||||
$: query.query(
|
||||
notification.class.DocUpdates,
|
||||
{
|
||||
user: getCurrentAccount()._id
|
||||
user: getCurrentAccount()._id,
|
||||
hidden: false
|
||||
},
|
||||
(res) => {
|
||||
docs = res
|
||||
|
@ -21,8 +21,9 @@
|
||||
import notification, { DocUpdates } from '@hcengineering/notification'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { AnySvelteComponent, Label, TimeSince } from '@hcengineering/ui'
|
||||
import { AnySvelteComponent, Label, TimeSince, getEventPositionElement, showPopup } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { Menu } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import TxView from './TxView.svelte'
|
||||
|
||||
@ -65,6 +66,10 @@
|
||||
$: docQuery.query(value.attachedToClass, { _id: value.attachedTo }, (res) => ([doc] = res))
|
||||
|
||||
$: newTxes = value.txes.length
|
||||
|
||||
function showMenu (e: MouseEvent) {
|
||||
showPopup(Menu, { object: value, baseMenuClass: value._class }, getEventPositionElement(e))
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
@ -72,6 +77,7 @@
|
||||
<div
|
||||
class="container cursor-pointer bottom-divider"
|
||||
class:selected
|
||||
on:contextmenu|preventDefault={showMenu}
|
||||
on:click={() => dispatch('click', { _id: value.attachedTo, _class: value.attachedToClass })}
|
||||
>
|
||||
<div class="content">
|
||||
|
@ -18,7 +18,7 @@ import { Resources } from '@hcengineering/platform'
|
||||
import Inbox from './components/Inbox.svelte'
|
||||
import NotificationSettings from './components/NotificationSettings.svelte'
|
||||
import NotificationPresenter from './components/NotificationPresenter.svelte'
|
||||
import { NotificationClientImpl } from './utils'
|
||||
import { NotificationClientImpl, hasntNotifications, hide, markAsUnread, unsubscribe } from './utils'
|
||||
|
||||
export * from './utils'
|
||||
|
||||
@ -31,6 +31,12 @@ export default async (): Promise<Resources> => ({
|
||||
NotificationSettings
|
||||
},
|
||||
function: {
|
||||
GetNotificationClient: NotificationClientImpl.getClient
|
||||
GetNotificationClient: NotificationClientImpl.getClient,
|
||||
HasntNotifications: hasntNotifications
|
||||
},
|
||||
actionImpl: {
|
||||
Unsubscribe: unsubscribe,
|
||||
Hide: hide,
|
||||
MarkAsUnread: markAsUnread
|
||||
}
|
||||
})
|
||||
|
@ -23,7 +23,6 @@ export default mergeIds(notificationId, notification, {
|
||||
string: {
|
||||
NoNotifications: '' as IntlString,
|
||||
Track: '' as IntlString,
|
||||
DontTrack: '' as IntlString,
|
||||
Remove: '' as IntlString,
|
||||
RemoveAll: '' as IntlString,
|
||||
MarkAsRead: '' as IntlString,
|
||||
|
@ -15,7 +15,7 @@
|
||||
//
|
||||
|
||||
import core, { Account, Class, Doc, getCurrentAccount, Ref, Timestamp } from '@hcengineering/core'
|
||||
import notification, { LastView, NotificationClient } from '@hcengineering/notification'
|
||||
import notification, { DocUpdates, LastView, NotificationClient } from '@hcengineering/notification'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { get, writable, Writable } from 'svelte/store'
|
||||
|
||||
@ -98,3 +98,57 @@ export class NotificationClientImpl implements NotificationClient {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function hasntNotifications (object: DocUpdates): Promise<boolean> {
|
||||
if (object._class !== notification.class.DocUpdates) return false
|
||||
return object.txes.length === 0
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function unsubscribe (object: DocUpdates): Promise<void> {
|
||||
const me = getCurrentAccount()._id
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
const target = await client.findOne(object.attachedToClass, { _id: object.attachedTo })
|
||||
if (target !== undefined) {
|
||||
if (hierarchy.hasMixin(target, notification.mixin.Collaborators)) {
|
||||
const collab = hierarchy.as(target, notification.mixin.Collaborators)
|
||||
if (collab.collaborators.includes(me)) {
|
||||
await client.updateMixin(collab._id, collab._class, collab.space, notification.mixin.Collaborators, {
|
||||
$pull: {
|
||||
collaborators: me
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
await client.remove(object)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function hide (object: DocUpdates): Promise<void> {
|
||||
const client = getClient()
|
||||
await client.update(object, {
|
||||
hidden: true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function markAsUnread (object: DocUpdates): Promise<void> {
|
||||
const client = getClient()
|
||||
if (object.txes.length > 0) return
|
||||
if (object.lastTx !== undefined && object.lastTxTime !== undefined) {
|
||||
await client.update(object, {
|
||||
txes: [[object.lastTx, object.lastTxTime]]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -136,8 +136,9 @@ export interface DocUpdates extends Doc {
|
||||
user: Ref<Account>
|
||||
attachedTo: Ref<Doc>
|
||||
attachedToClass: Ref<Class<Doc>>
|
||||
lastTx?: Ref<TxCUD<Doc>>
|
||||
lastTxTime?: Timestamp
|
||||
hidden: boolean
|
||||
lastTx: Ref<TxCUD<Doc>>
|
||||
lastTxTime: Timestamp
|
||||
txes: [Ref<TxCUD<Doc>>, Timestamp][]
|
||||
}
|
||||
|
||||
@ -199,7 +200,8 @@ const notification = plugin(notificationId, {
|
||||
icon: {
|
||||
Notifications: '' as Asset,
|
||||
Track: '' as Asset,
|
||||
DontTrack: '' as Asset
|
||||
DontTrack: '' as Asset,
|
||||
Hide: '' as Asset
|
||||
},
|
||||
space: {
|
||||
Notifications: '' as Ref<Space>
|
||||
@ -207,6 +209,7 @@ const notification = plugin(notificationId, {
|
||||
string: {
|
||||
Notification: '' as IntlString,
|
||||
Notifications: '' as IntlString,
|
||||
DontTrack: '' as IntlString,
|
||||
Inbox: '' as IntlString
|
||||
},
|
||||
function: {
|
||||
|
@ -16,7 +16,7 @@
|
||||
"Open": "Открыть",
|
||||
"Assignees": "Исполнители",
|
||||
"Labels": "Метки",
|
||||
"ActionPlaceholder": "введите для филтрации...",
|
||||
"ActionPlaceholder": "введите для фильтрации...",
|
||||
"General": "Общее",
|
||||
"Navigation": "Навигация",
|
||||
"Editor": "Редактор",
|
||||
|
@ -52,7 +52,6 @@
|
||||
}
|
||||
)
|
||||
|
||||
let visibleActions: WithLookup<Action>[] = []
|
||||
let supportedActions: WithLookup<Action>[] = []
|
||||
let filteredActions: WithLookup<Action>[] = []
|
||||
|
||||
@ -70,16 +69,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
visibleActions = resultActions
|
||||
return resultActions
|
||||
}
|
||||
$: filterVisibleActions(actions, getSelection($focusStore, $selectionStore))
|
||||
|
||||
const client = getClient()
|
||||
|
||||
$: {
|
||||
let fActions: WithLookup<Action>[] = visibleActions
|
||||
|
||||
async function getSupportedActions (actions: WithLookup<Action>[]) {
|
||||
const docs = getSelection($focusStore, $selectionStore)
|
||||
let fActions: WithLookup<Action>[] = actions
|
||||
|
||||
for (const d of docs) {
|
||||
fActions = filterActions(client, d, fActions)
|
||||
}
|
||||
@ -91,10 +89,13 @@
|
||||
(it.$lookup?.category?.visible ?? true) &&
|
||||
(it.context.application === viewContext.application || it.context.application === undefined)
|
||||
)
|
||||
fActions = await filterVisibleActions(fActions, docs)
|
||||
// Sort by category.
|
||||
supportedActions = fActions.sort((a, b) => a.category.localeCompare(b.category))
|
||||
}
|
||||
|
||||
$: getSupportedActions(actions)
|
||||
|
||||
async function filterSearchActions (actions: WithLookup<Action>[], search: string): Promise<void> {
|
||||
const res: WithLookup<Action>[] = []
|
||||
search = search.trim().toLowerCase()
|
||||
|
@ -514,7 +514,8 @@ async function createCollabDocInfo (
|
||||
res.push(
|
||||
control.txFactory.createTxUpdateDoc(doc._class, doc.space, doc._id, {
|
||||
lastTx: txId ?? tx._id,
|
||||
lastTxTime: tx.modifiedOn
|
||||
lastTxTime: tx.modifiedOn,
|
||||
hidden: false
|
||||
})
|
||||
)
|
||||
}
|
||||
@ -525,6 +526,7 @@ async function createCollabDocInfo (
|
||||
user: target,
|
||||
attachedTo: objectId,
|
||||
attachedToClass: objectClass,
|
||||
hidden: false,
|
||||
lastTx: txId ?? tx._id,
|
||||
lastTxTime: tx.modifiedOn,
|
||||
txes: [[txId ?? tx._id, tx.modifiedOn]]
|
||||
|
Loading…
Reference in New Issue
Block a user