UBERF-5527: add context menu for activity and inbox (#5373)

Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
Kristina 2024-04-17 16:26:30 +04:00 committed by GitHub
parent 577c948d29
commit 7405b01d04
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 421 additions and 419 deletions

View File

@ -21,8 +21,6 @@ import {
type ActivityInfoMessage, type ActivityInfoMessage,
type ActivityMessage, type ActivityMessage,
type ActivityMessageControl, type ActivityMessageControl,
type ActivityMessageExtension,
type ActivityMessageExtensionKind,
type ActivityMessagePreview, type ActivityMessagePreview,
type ActivityMessagesFilter, type ActivityMessagesFilter,
type ActivityReference, type ActivityReference,
@ -70,7 +68,7 @@ import {
} from '@hcengineering/model' } from '@hcengineering/model'
import { TAttachedDoc, TClass, TDoc } from '@hcengineering/model-core' import { TAttachedDoc, TClass, TDoc } from '@hcengineering/model-core'
import preference, { TPreference } from '@hcengineering/model-preference' import preference, { TPreference } from '@hcengineering/model-preference'
import view from '@hcengineering/model-view' import view, { createAction } from '@hcengineering/model-view'
import notification from '@hcengineering/notification' import notification from '@hcengineering/notification'
import type { Asset, IntlString, Resource } from '@hcengineering/platform' import type { Asset, IntlString, Resource } from '@hcengineering/platform'
import { type AnyComponent } from '@hcengineering/ui/src/types' import { type AnyComponent } from '@hcengineering/ui/src/types'
@ -210,15 +208,6 @@ export class TDocUpdateMessageViewlet extends TDoc implements DocUpdateMessageVi
onlyWithParent?: boolean onlyWithParent?: boolean
} }
@Model(activity.class.ActivityMessageExtension, core.class.Doc, DOMAIN_MODEL)
export class TActivityMessageExtension extends TDoc implements ActivityMessageExtension {
@Prop(TypeRef(activity.class.ActivityMessage), core.string.Class)
@Index(IndexKind.Indexed)
ofMessage!: Ref<Class<ActivityMessage>>
components!: { kind: ActivityMessageExtensionKind, component: AnyComponent }[]
}
@Model(activity.class.ActivityExtension, core.class.Doc, DOMAIN_MODEL) @Model(activity.class.ActivityExtension, core.class.Doc, DOMAIN_MODEL)
export class TActivityExtension extends TDoc implements ActivityExtension { export class TActivityExtension extends TDoc implements ActivityExtension {
@Prop(TypeRef(core.class.Class), core.string.Class) @Prop(TypeRef(core.class.Class), core.string.Class)
@ -275,7 +264,6 @@ export function createModel (builder: Builder): void {
TTxViewlet, TTxViewlet,
TActivityDoc, TActivityDoc,
TActivityMessagesFilter, TActivityMessagesFilter,
TActivityMessageExtension,
TActivityMessage, TActivityMessage,
TDocUpdateMessage, TDocUpdateMessage,
TDocUpdateMessageViewlet, TDocUpdateMessageViewlet,
@ -426,6 +414,113 @@ export function createModel (builder: Builder): void {
{ attachedToClass: 1 } { attachedToClass: 1 }
] ]
}) })
createAction(
builder,
{
action: activity.actionImpl.AddReaction,
label: activity.string.AddReaction,
icon: activity.icon.Emoji,
input: 'focus',
category: activity.category.Activity,
target: activity.class.ActivityMessage,
inline: true,
context: {
mode: 'context',
group: 'edit'
}
},
activity.ids.AddReactionAction
)
createAction(
builder,
{
action: activity.actionImpl.SaveForLater,
label: activity.string.SaveForLater,
icon: activity.icon.Bookmark,
input: 'focus',
inline: true,
actionProps: {
size: 'x-small'
},
category: activity.category.Activity,
target: activity.class.ActivityMessage,
visibilityTester: activity.function.CanSaveForLater,
context: {
mode: 'context',
group: 'edit'
}
},
activity.ids.SaveForLaterAction
)
createAction(
builder,
{
action: activity.actionImpl.RemoveFromSaved,
label: activity.string.RemoveFromLater,
icon: activity.icon.BookmarkFilled,
input: 'focus',
inline: true,
actionProps: {
iconProps: {
fill: 'var(--global-accent-TextColor)'
}
},
category: activity.category.Activity,
target: activity.class.ActivityMessage,
visibilityTester: activity.function.CanRemoveFromSaved,
context: {
mode: 'context',
group: 'edit'
}
},
activity.ids.RemoveFromLaterAction
)
createAction(
builder,
{
action: activity.actionImpl.PinMessage,
label: view.string.Pin,
icon: view.icon.Pin,
input: 'focus',
inline: true,
category: activity.category.Activity,
target: activity.class.ActivityMessage,
visibilityTester: activity.function.CanPinMessage,
context: {
mode: 'context',
group: 'edit'
}
},
activity.ids.PinMessageAction
)
createAction(
builder,
{
action: activity.actionImpl.UnpinMessage,
label: view.string.Unpin,
icon: view.icon.Pin,
input: 'focus',
inline: true,
actionProps: {
iconProps: {
fill: 'var(--global-accent-TextColor)'
}
},
category: activity.category.Activity,
target: activity.class.ActivityMessage,
visibilityTester: activity.function.CanUnpinMessage,
context: {
mode: 'context',
group: 'edit'
}
},
activity.ids.UnpinMessageAction
)
} }
export default activity export default activity

View File

@ -16,7 +16,13 @@ import { activityId, type ActivityMessage, type DocUpdateMessageViewlet } from '
import activity from '@hcengineering/activity-resources/src/plugin' import activity from '@hcengineering/activity-resources/src/plugin'
import { type IntlString, mergeIds, type Resource } from '@hcengineering/platform' import { type IntlString, mergeIds, type Resource } from '@hcengineering/platform'
import { type Doc, type Ref } from '@hcengineering/core' import { type Doc, type Ref } from '@hcengineering/core'
import { type ActionCategory } from '@hcengineering/view' import type { Location } from '@hcengineering/ui'
import {
type Action,
type ActionCategory,
type ViewAction,
type ViewActionAvailabilityFunction
} from '@hcengineering/view'
import { type NotificationGroup, type NotificationType } from '@hcengineering/notification' import { type NotificationGroup, type NotificationType } from '@hcengineering/notification'
export default mergeIds(activityId, activity, { export default mergeIds(activityId, activity, {
@ -24,7 +30,10 @@ export default mergeIds(activityId, activity, {
Attributes: '' as IntlString, Attributes: '' as IntlString,
Pinned: '' as IntlString, Pinned: '' as IntlString,
Emoji: '' as IntlString, Emoji: '' as IntlString,
Replies: '' as IntlString Replies: '' as IntlString,
AddReaction: '' as IntlString,
SaveForLater: '' as IntlString,
RemoveFromLater: '' as IntlString
}, },
filter: { filter: {
AttributesFilter: '' as Resource<(message: ActivityMessage, _class?: Ref<Doc>) => boolean>, AttributesFilter: '' as Resource<(message: ActivityMessage, _class?: Ref<Doc>) => boolean>,
@ -35,9 +44,28 @@ export default mergeIds(activityId, activity, {
ids: { ids: {
ReactionAddedActivityViewlet: '' as Ref<DocUpdateMessageViewlet>, ReactionAddedActivityViewlet: '' as Ref<DocUpdateMessageViewlet>,
ActivityNotificationGroup: '' as Ref<NotificationGroup>, ActivityNotificationGroup: '' as Ref<NotificationGroup>,
AddReactionNotification: '' as Ref<NotificationType> AddReactionNotification: '' as Ref<NotificationType>,
AddReactionAction: '' as Ref<Action>,
SaveForLaterAction: '' as Ref<Action>,
RemoveFromLaterAction: '' as Ref<Action>,
PinMessageAction: '' as Ref<Action>,
UnpinMessageAction: '' as Ref<Action>
},
function: {
GetFragment: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<Location>>,
CanSaveForLater: '' as Resource<ViewActionAvailabilityFunction>,
CanRemoveFromSaved: '' as Resource<ViewActionAvailabilityFunction>,
CanPinMessage: '' as Resource<ViewActionAvailabilityFunction>,
CanUnpinMessage: '' as Resource<ViewActionAvailabilityFunction>
}, },
category: { category: {
Activity: '' as Ref<ActionCategory> Activity: '' as Ref<ActionCategory>
},
actionImpl: {
AddReaction: '' as ViewAction,
SaveForLater: '' as ViewAction,
RemoveFromSaved: '' as ViewAction,
PinMessage: '' as ViewAction,
UnpinMessage: '' as ViewAction
} }
}) })

View File

@ -677,46 +677,6 @@ export function createModel (builder: Builder, options = { addApplication: true
components: { input: chunter.component.ChatMessageInput } components: { input: chunter.component.ChatMessageInput }
}) })
builder.createDoc(activity.class.ActivityMessageExtension, core.space.Model, {
ofMessage: chunter.class.ChatMessage,
components: [{ kind: 'footer', component: chunter.component.Replies }]
})
builder.createDoc(activity.class.ActivityMessageExtension, core.space.Model, {
ofMessage: activity.class.DocUpdateMessage,
components: [{ kind: 'footer', component: chunter.component.Replies }]
})
builder.createDoc(activity.class.ActivityMessageExtension, core.space.Model, {
ofMessage: activity.class.ActivityInfoMessage,
components: [{ kind: 'footer', component: chunter.component.Replies }]
})
builder.createDoc(activity.class.ActivityMessageExtension, core.space.Model, {
ofMessage: activity.class.ActivityReference,
components: [{ kind: 'footer', component: chunter.component.Replies }]
})
builder.createDoc(activity.class.ActivityMessageExtension, core.space.Model, {
ofMessage: chunter.class.ChatMessage,
components: [{ kind: 'action', component: chunter.component.ReplyToThreadAction }]
})
builder.createDoc(activity.class.ActivityMessageExtension, core.space.Model, {
ofMessage: activity.class.DocUpdateMessage,
components: [{ kind: 'action', component: chunter.component.ReplyToThreadAction }]
})
builder.createDoc(activity.class.ActivityMessageExtension, core.space.Model, {
ofMessage: activity.class.ActivityInfoMessage,
components: [{ kind: 'action', component: chunter.component.ReplyToThreadAction }]
})
builder.createDoc(activity.class.ActivityMessageExtension, core.space.Model, {
ofMessage: activity.class.ActivityReference,
components: [{ kind: 'action', component: chunter.component.ReplyToThreadAction }]
})
builder.mixin(chunter.class.Channel, core.class.Class, chunter.mixin.ObjectChatPanel, { builder.mixin(chunter.class.Channel, core.class.Class, chunter.mixin.ObjectChatPanel, {
ignoreKeys: ['archived', 'collaborators', 'lastMessage', 'pinned', 'topic', 'description'] ignoreKeys: ['archived', 'collaborators', 'lastMessage', 'pinned', 'topic', 'description']
}) })
@ -741,6 +701,25 @@ export function createModel (builder: Builder, options = { addApplication: true
builder.createDoc(activity.class.ReplyProvider, core.space.Model, { builder.createDoc(activity.class.ReplyProvider, core.space.Model, {
function: chunter.function.ReplyToThread function: chunter.function.ReplyToThread
}) })
createAction(
builder,
{
action: chunter.actionImpl.ReplyToThread,
label: chunter.string.ReplyToThread,
icon: chunter.icon.Thread,
input: 'focus',
category: chunter.category.Chunter,
target: activity.class.ActivityMessage,
visibilityTester: chunter.function.CanReplyToThread,
inline: true,
context: {
mode: 'context',
group: 'edit'
}
},
chunter.action.ReplyToThreadAction
)
} }
export default chunter export default chunter

View File

@ -40,13 +40,15 @@ export default mergeIds(chunterId, chunter, {
ArchiveChannel: '' as Ref<Action>, ArchiveChannel: '' as Ref<Action>,
UnarchiveChannel: '' as Ref<Action>, UnarchiveChannel: '' as Ref<Action>,
ConvertToPrivate: '' as Ref<Action>, ConvertToPrivate: '' as Ref<Action>,
CopyChatMessageLink: '' as Ref<Action<Doc, any>> CopyChatMessageLink: '' as Ref<Action<Doc, any>>,
ReplyToThreadAction: '' as Ref<Action>
}, },
actionImpl: { actionImpl: {
ArchiveChannel: '' as ViewAction, ArchiveChannel: '' as ViewAction,
UnarchiveChannel: '' as ViewAction, UnarchiveChannel: '' as ViewAction,
ConvertDmToPrivateChannel: '' as ViewAction, ConvertDmToPrivateChannel: '' as ViewAction,
DeleteChatMessage: '' as ViewAction DeleteChatMessage: '' as ViewAction,
ReplyToThread: '' as ViewAction
}, },
category: { category: {
Chunter: '' as Ref<ActionCategory> Chunter: '' as Ref<ActionCategory>
@ -100,8 +102,9 @@ export default mergeIds(chunterId, chunter, {
CanCopyMessageLink: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>, CanCopyMessageLink: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>,
GetChunterSpaceLinkFragment: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<Location>>, GetChunterSpaceLinkFragment: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<Location>>,
GetThreadLink: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<Location>>, GetThreadLink: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<Location>>,
GetMessageLink: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<Location>>, ReplyToThread: '' as Resource<(doc: ActivityMessage) => Promise<void>>,
ReplyToThread: '' as Resource<(doc: ActivityMessage) => Promise<void>> CanReplyToThread: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>,
GetMessageLink: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<Location>>
}, },
filter: { filter: {
ChatMessagesFilter: '' as Resource<(message: ActivityMessage, _class?: Ref<Doc>) => boolean> ChatMessagesFilter: '' as Resource<(message: ActivityMessage, _class?: Ref<Doc>) => boolean>

View File

@ -111,6 +111,7 @@ export function closePopup (category?: string): void {
} else { } else {
for (let i = popups.length - 1; i >= 0; i--) { for (let i = popups.length - 1; i >= 0; i--) {
if (popups[i].options.fixed !== true) { if (popups[i].options.fixed !== true) {
popups[i].onClose?.(undefined)
popups.splice(i, 1) popups.splice(i, 1)
break break
} }

View File

@ -239,6 +239,7 @@ export type IconSize =
| 'inline' | 'inline'
| 'tiny' | 'tiny'
| 'card' | 'card'
| 'xx-small'
| 'x-small' | 'x-small'
| 'smaller' | 'smaller'
| 'small' | 'small'
@ -258,6 +259,7 @@ export function getIconSize2x (size: IconSize): IconSize {
switch (size) { switch (size) {
case 'inline': case 'inline':
case 'tiny': case 'tiny':
case 'xx-small':
case 'x-small': case 'x-small':
case 'small': case 'small':
case 'card': case 'card':

View File

@ -2,13 +2,16 @@
<symbol id="activity" viewBox="0 0 16 16"> <symbol id="activity" viewBox="0 0 16 16">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13 2C12.4477 2 12 2.44772 12 3C12 3.55228 12.4477 4 13 4C13.5523 4 14 3.55228 14 3C14 2.44772 13.5523 2 13 2ZM11 3C11 1.89543 11.8954 1 13 1C14.1046 1 15 1.89543 15 3C15 4.10457 14.1046 5 13 5C11.8954 5 11 4.10457 11 3ZM2 4C2 2.89543 2.89543 2 4 2H9.5C9.77614 2 10 2.22386 10 2.5C10 2.77614 9.77614 3 9.5 3H4C3.44772 3 3 3.44772 3 4V12C3 12.5523 3.44772 13 4 13H12C12.5523 13 13 12.5523 13 12V6.5C13 6.22386 13.2239 6 13.5 6C13.7761 6 14 6.22386 14 6.5V12C14 13.1046 13.1046 14 12 14H4C2.89543 14 2 13.1046 2 12V4ZM6.64645 6.64645C6.84171 6.45118 7.15829 6.45118 7.35355 6.64645L9 8.29289L10.1464 7.14645C10.3417 6.95118 10.6583 6.95118 10.8536 7.14645C11.0488 7.34171 11.0488 7.65829 10.8536 7.85355L9.35355 9.35355C9.15829 9.54882 8.84171 9.54882 8.64645 9.35355L7 7.70711L5.85355 8.85355C5.65829 9.04882 5.34171 9.04882 5.14645 8.85355C4.95118 8.65829 4.95118 8.34171 5.14645 8.14645L6.64645 6.64645Z"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M13 2C12.4477 2 12 2.44772 12 3C12 3.55228 12.4477 4 13 4C13.5523 4 14 3.55228 14 3C14 2.44772 13.5523 2 13 2ZM11 3C11 1.89543 11.8954 1 13 1C14.1046 1 15 1.89543 15 3C15 4.10457 14.1046 5 13 5C11.8954 5 11 4.10457 11 3ZM2 4C2 2.89543 2.89543 2 4 2H9.5C9.77614 2 10 2.22386 10 2.5C10 2.77614 9.77614 3 9.5 3H4C3.44772 3 3 3.44772 3 4V12C3 12.5523 3.44772 13 4 13H12C12.5523 13 13 12.5523 13 12V6.5C13 6.22386 13.2239 6 13.5 6C13.7761 6 14 6.22386 14 6.5V12C14 13.1046 13.1046 14 12 14H4C2.89543 14 2 13.1046 2 12V4ZM6.64645 6.64645C6.84171 6.45118 7.15829 6.45118 7.35355 6.64645L9 8.29289L10.1464 7.14645C10.3417 6.95118 10.6583 6.95118 10.8536 7.14645C11.0488 7.34171 11.0488 7.65829 10.8536 7.85355L9.35355 9.35355C9.15829 9.54882 8.84171 9.54882 8.64645 9.35355L7 7.70711L5.85355 8.85355C5.65829 9.04882 5.34171 9.04882 5.14645 8.85355C4.95118 8.65829 4.95118 8.34171 5.14645 8.14645L6.64645 6.64645Z"/>
</symbol> </symbol>
<symbol id="emoji" viewBox="0 0 32 32"> <symbol id="emoji" viewBox="0 0 32 32">
<path d="M16 2.00037C13.2311 2.00037 10.5243 2.82145 8.22201 4.35979C5.91973 5.89813 4.12531 8.08463 3.06569 10.6428C2.00606 13.201 1.72881 16.0159 2.269 18.7316C2.8092 21.4474 4.14257 23.9419 6.1005 25.8999C8.05844 27.8578 10.553 29.1912 13.2687 29.7314C15.9845 30.2716 18.7994 29.9943 21.3576 28.9347C23.9157 27.8751 26.1022 26.0806 27.6406 23.7783C29.1789 21.4761 30 18.7693 30 16.0004C30 12.2873 28.525 8.72638 25.8995 6.10087C23.274 3.47536 19.713 2.00037 16 2.00037ZM16 28.0004C13.6266 28.0004 11.3065 27.2966 9.33316 25.978C7.35977 24.6594 5.8217 22.7853 4.91344 20.5926C4.00519 18.3999 3.76755 15.9871 4.23058 13.6593C4.6936 11.3315 5.83649 9.19332 7.51472 7.51508C9.19295 5.83685 11.3311 4.69397 13.6589 4.23094C15.9867 3.76792 18.3995 4.00556 20.5922 4.91381C22.7849 5.82206 24.6591 7.36013 25.9776 9.33352C27.2962 11.3069 28 13.627 28 16.0004C28 19.183 26.7357 22.2352 24.4853 24.4856C22.2348 26.7361 19.1826 28.0004 16 28.0004Z" /> <path d="M16 2.00037C13.2311 2.00037 10.5243 2.82145 8.22201 4.35979C5.91973 5.89813 4.12531 8.08463 3.06569 10.6428C2.00606 13.201 1.72881 16.0159 2.269 18.7316C2.8092 21.4474 4.14257 23.9419 6.1005 25.8999C8.05844 27.8578 10.553 29.1912 13.2687 29.7314C15.9845 30.2716 18.7994 29.9943 21.3576 28.9347C23.9157 27.8751 26.1022 26.0806 27.6406 23.7783C29.1789 21.4761 30 18.7693 30 16.0004C30 12.2873 28.525 8.72638 25.8995 6.10087C23.274 3.47536 19.713 2.00037 16 2.00037ZM16 28.0004C13.6266 28.0004 11.3065 27.2966 9.33316 25.978C7.35977 24.6594 5.8217 22.7853 4.91344 20.5926C4.00519 18.3999 3.76755 15.9871 4.23058 13.6593C4.6936 11.3315 5.83649 9.19332 7.51472 7.51508C9.19295 5.83685 11.3311 4.69397 13.6589 4.23094C15.9867 3.76792 18.3995 4.00556 20.5922 4.91381C22.7849 5.82206 24.6591 7.36013 25.9776 9.33352C27.2962 11.3069 28 13.627 28 16.0004C28 19.183 26.7357 22.2352 24.4853 24.4856C22.2348 26.7361 19.1826 28.0004 16 28.0004Z" />
<path d="M11.5 11.0004C11.0055 11.0004 10.5222 11.147 10.1111 11.4217C9.69995 11.6964 9.37952 12.0868 9.1903 12.5437C9.00108 13.0005 8.95157 13.5031 9.04804 13.9881C9.1445 14.473 9.3826 14.9185 9.73223 15.2681C10.0819 15.6178 10.5273 15.8559 11.0123 15.9523C11.4972 16.0488 11.9999 15.9993 12.4567 15.8101C12.9135 15.6208 13.304 15.3004 13.5787 14.8893C13.8534 14.4782 14 13.9948 14 13.5004C14.0027 13.1713 13.9398 12.845 13.8151 12.5405C13.6904 12.236 13.5064 11.9593 13.2737 11.7267C13.041 11.494 12.7644 11.3099 12.4599 11.1852C12.1553 11.0605 11.829 10.9977 11.5 11.0004Z" /> <path d="M11.5 11.0004C11.0055 11.0004 10.5222 11.147 10.1111 11.4217C9.69995 11.6964 9.37952 12.0868 9.1903 12.5437C9.00108 13.0005 8.95157 13.5031 9.04804 13.9881C9.1445 14.473 9.3826 14.9185 9.73223 15.2681C10.0819 15.6178 10.5273 15.8559 11.0123 15.9523C11.4972 16.0488 11.9999 15.9993 12.4567 15.8101C12.9135 15.6208 13.304 15.3004 13.5787 14.8893C13.8534 14.4782 14 13.9948 14 13.5004C14.0027 13.1713 13.9398 12.845 13.8151 12.5405C13.6904 12.236 13.5064 11.9593 13.2737 11.7267C13.041 11.494 12.7644 11.3099 12.4599 11.1852C12.1553 11.0605 11.829 10.9977 11.5 11.0004Z" />
<path d="M20.5 11.0004C20.0055 11.0004 19.5222 11.147 19.1111 11.4217C18.6999 11.6964 18.3795 12.0868 18.1903 12.5437C18.0011 13.0005 17.9516 13.5031 18.048 13.9881C18.1445 14.473 18.3826 14.9185 18.7322 15.2681C19.0819 15.6178 19.5273 15.8559 20.0123 15.9523C20.4972 16.0488 20.9999 15.9993 21.4567 15.8101C21.9135 15.6208 22.304 15.3004 22.5787 14.8893C22.8534 14.4782 23 13.9948 23 13.5004C23.0027 13.1713 22.9398 12.845 22.8151 12.5405C22.6904 12.236 22.5064 11.9593 22.2737 11.7267C22.041 11.494 21.7644 11.3099 21.4599 11.1852C21.1553 11.0605 20.829 10.9977 20.5 11.0004Z" /> <path d="M20.5 11.0004C20.0055 11.0004 19.5222 11.147 19.1111 11.4217C18.6999 11.6964 18.3795 12.0868 18.1903 12.5437C18.0011 13.0005 17.9516 13.5031 18.048 13.9881C18.1445 14.473 18.3826 14.9185 18.7322 15.2681C19.0819 15.6178 19.5273 15.8559 20.0123 15.9523C20.4972 16.0488 20.9999 15.9993 21.4567 15.8101C21.9135 15.6208 22.304 15.3004 22.5787 14.8893C22.8534 14.4782 23 13.9948 23 13.5004C23.0027 13.1713 22.9398 12.845 22.8151 12.5405C22.6904 12.236 22.5064 11.9593 22.2737 11.7267C22.041 11.494 21.7644 11.3099 21.4599 11.1852C21.1553 11.0605 20.829 10.9977 20.5 11.0004Z" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.3431 20.243C10.7337 19.8525 11.3668 19.8525 11.7574 20.243C12.3145 20.8002 12.9759 21.2421 13.7039 21.5436C14.4319 21.8452 15.2121 22.0004 16 22.0004C16.7879 22.0004 17.5681 21.8452 18.2961 21.5436C19.0241 21.2421 19.6855 20.8002 20.2426 20.243C20.6332 19.8525 21.2663 19.8525 21.6569 20.243C22.0474 20.6335 22.0474 21.2667 21.6569 21.6572C20.914 22.4001 20.0321 22.9894 19.0615 23.3914C18.0909 23.7934 17.0506 24.0004 16 24.0004C14.9494 24.0004 13.9091 23.7934 12.9385 23.3914C11.9679 22.9894 11.086 22.4001 10.3431 21.6572C9.95262 21.2667 9.95262 20.6335 10.3431 20.243Z" /> <path fill-rule="evenodd" clip-rule="evenodd" d="M10.3431 20.243C10.7337 19.8525 11.3668 19.8525 11.7574 20.243C12.3145 20.8002 12.9759 21.2421 13.7039 21.5436C14.4319 21.8452 15.2121 22.0004 16 22.0004C16.7879 22.0004 17.5681 21.8452 18.2961 21.5436C19.0241 21.2421 19.6855 20.8002 20.2426 20.243C20.6332 19.8525 21.2663 19.8525 21.6569 20.243C22.0474 20.6335 22.0474 21.2667 21.6569 21.6572C20.914 22.4001 20.0321 22.9894 19.0615 23.3914C18.0909 23.7934 17.0506 24.0004 16 24.0004C14.9494 24.0004 13.9091 23.7934 12.9385 23.3914C11.9679 22.9894 11.086 22.4001 10.3431 21.6572C9.95262 21.2667 9.95262 20.6335 10.3431 20.243Z" />
</symbol> </symbol>
<symbol id="bookmark" viewBox="0 0 20 28"> <symbol id="bookmark" viewBox="0 0 20 28">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 4.00012C0 1.79098 1.79086 0.00012207 4 0.00012207H16C18.2091 0.00012207 20 1.79098 20 4.00012V27.0001C20 27.3689 19.797 27.7078 19.4719 27.8818C19.1467 28.0558 18.7522 28.0367 18.4453 27.8322L10 22.202L1.5547 27.8322C1.24784 28.0367 0.8533 28.0558 0.528142 27.8818C0.202985 27.7078 0 27.3689 0 27.0001V4.00012ZM4 2.00012C2.89543 2.00012 2 2.89555 2 4.00012V25.1316L9.4453 20.1681C9.7812 19.9441 10.2188 19.9441 10.5547 20.1681L18 25.1316V4.00012C18 2.89555 17.1046 2.00012 16 2.00012H4Z" /> <path fill-rule="evenodd" clip-rule="evenodd" d="M0 4.00012C0 1.79098 1.79086 0.00012207 4 0.00012207H16C18.2091 0.00012207 20 1.79098 20 4.00012V27.0001C20 27.3689 19.797 27.7078 19.4719 27.8818C19.1467 28.0558 18.7522 28.0367 18.4453 27.8322L10 22.202L1.5547 27.8322C1.24784 28.0367 0.8533 28.0558 0.528142 27.8818C0.202985 27.7078 0 27.3689 0 27.0001V4.00012ZM4 2.00012C2.89543 2.00012 2 2.89555 2 4.00012V25.1316L9.4453 20.1681C9.7812 19.9441 10.2188 19.9441 10.5547 20.1681L18 25.1316V4.00012C18 2.89555 17.1046 2.00012 16 2.00012H4Z" />
</symbol> </symbol>
<symbol id="bookmark-filled" viewBox="0 0 32 32">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6 6.00012C6 3.79098 7.79086 2.00012 10 2.00012H22C24.2091 2.00012 26 3.79098 26 6.00012V29.0001C26 29.3689 25.797 29.7078 25.4719 29.8818C25.1467 30.0558 24.7522 30.0367 24.4453 29.8322L16 24.202L7.5547 29.8322C7.24784 30.0367 6.8533 30.0558 6.52814 29.8818C6.20298 29.7078 6 29.3689 6 29.0001V6.00012Z" />
</symbol>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -40,6 +40,9 @@
"Mentions": "Mentions", "Mentions": "Mentions",
"MentionedYouIn": "Mentioned you in {title}", "MentionedYouIn": "Mentioned you in {title}",
"Messages": "Messages", "Messages": "Messages",
"Thread": "Thread" "Thread": "Thread",
"AddReaction": "Add reaction",
"SaveForLater": "Save for later",
"RemoveFromLater": "Remove from saved"
} }
} }

View File

@ -40,6 +40,9 @@
"Mentions": "Упоминания", "Mentions": "Упоминания",
"MentionedYouIn": "Упомянул(а) вас в {title}", "MentionedYouIn": "Упомянул(а) вас в {title}",
"Messages": "Cообщения", "Messages": "Cообщения",
"Thread": "Обсуждение" "Thread": "Обсуждение",
"AddReaction": "Добавить реакцию",
"SaveForLater": "Cохранить",
"RemoveFromLater": "Удалить из сохраненных"
} }
} }

View File

@ -20,5 +20,6 @@ const icons = require('../assets/icons.svg') as string // eslint-disable-line
loadMetadata(activity.icon, { loadMetadata(activity.icon, {
Activity: `${icons}#activity`, Activity: `${icons}#activity`,
Emoji: `${icons}#emoji`, Emoji: `${icons}#emoji`,
Bookmark: `${icons}#bookmark` Bookmark: `${icons}#bookmark`,
BookmarkFilled: `${icons}#bookmark-filled`
}) })

View File

@ -14,9 +14,10 @@
--> -->
<script lang="ts"> <script lang="ts">
import { type AnySvelteComponent, ButtonIcon, IconSize } from '@hcengineering/ui' import { type AnySvelteComponent, ButtonIcon, IconSize } from '@hcengineering/ui'
import { Asset } from '@hcengineering/platform' import { Asset, IntlString } from '@hcengineering/platform'
import { ComponentType } from 'svelte' import { ComponentType } from 'svelte'
export let label: IntlString
export let icon: Asset | AnySvelteComponent | ComponentType export let icon: Asset | AnySvelteComponent | ComponentType
export let iconProps: any | undefined = undefined export let iconProps: any | undefined = undefined
export let size: IconSize = 'small' export let size: IconSize = 'small'
@ -30,4 +31,13 @@
} }
</script> </script>
<ButtonIcon {icon} {iconProps} iconSize={size} size="small" kind="tertiary" pressed={opened} on:click={onClick} /> <ButtonIcon
{icon}
{iconProps}
iconSize={size}
size="small"
kind="tertiary"
pressed={opened}
on:click={onClick}
tooltip={{ label }}
/>

View File

@ -13,60 +13,95 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import activity, { ActivityMessage, ActivityMessageExtension } from '@hcengineering/activity' import activity, { ActivityMessage } from '@hcengineering/activity'
import { Action, IconMoreV, showPopup } from '@hcengineering/ui' import { Action, IconMoreV, showPopup } from '@hcengineering/ui'
import { Menu } from '@hcengineering/view-resources' import { getActions, Menu } from '@hcengineering/view-resources'
import { createEventDispatcher } from 'svelte' import { getClient } from '@hcengineering/presentation'
import { getResource } from '@hcengineering/platform'
import view, { Action as ViewAction } from '@hcengineering/view'
import ActivityMessageAction from './ActivityMessageAction.svelte' import ActivityMessageAction from './ActivityMessageAction.svelte'
import PinMessageAction from './PinMessageAction.svelte' import { savedMessagesStore } from '../activity'
import SaveMessageAction from './SaveMessageAction.svelte'
import ActivityMessageExtensionComponent from './activity-message/ActivityMessageExtension.svelte'
import AddReactionAction from './reactions/AddReactionAction.svelte'
export let message: ActivityMessage | undefined export let message: ActivityMessage | undefined
export let extensions: ActivityMessageExtension[] = []
export let actions: Action[] = [] export let actions: Action[] = []
export let withActionMenu = true export let withActionMenu = true
export let onOpen: () => void
export let onClose: () => void
const dispatch = createEventDispatcher() const client = getClient()
let inlineActions: ViewAction[] = []
let isActionMenuOpened = false let isActionMenuOpened = false
$: void updateInlineActions(message)
savedMessagesStore.subscribe(() => {
void updateInlineActions(message)
})
function handleActionMenuOpened (): void { function handleActionMenuOpened (): void {
isActionMenuOpened = true isActionMenuOpened = true
dispatch('open') onOpen()
} }
function handleActionMenuClosed (): void { function handleActionMenuClosed (): void {
isActionMenuOpened = false isActionMenuOpened = false
dispatch('close') onClose()
} }
function showMenu (ev: MouseEvent): void { function showMenu (ev: MouseEvent): void {
const excludedActions = inlineActions.map(({ _id }) => _id)
showPopup( showPopup(
Menu, Menu,
{ {
object: message, object: message,
actions, actions,
baseMenuClass: activity.class.ActivityMessage baseMenuClass: activity.class.ActivityMessage,
excludedActions
}, },
ev.target as HTMLElement, ev.target as HTMLElement,
handleActionMenuClosed handleActionMenuClosed
) )
handleActionMenuOpened() handleActionMenuOpened()
} }
async function updateInlineActions (message?: ActivityMessage): Promise<void> {
if (message === undefined) {
inlineActions = []
return
}
inlineActions = (await getActions(client, message, activity.class.ActivityMessage)).filter(
(action) => action.inline
)
}
</script> </script>
{#if message} {#if message}
<div class="root"> <div class="root">
<AddReactionAction object={message} on:open on:close /> {#each inlineActions as inline}
<ActivityMessageExtensionComponent kind="action" {extensions} props={{ object: message }} on:close on:open /> {#if inline.icon}
<PinMessageAction object={message} /> {#await getResource(inline.action) then action}
<SaveMessageAction object={message} /> <ActivityMessageAction
label={inline.label}
size={inline.actionProps?.size ?? 'small'}
icon={inline.icon}
iconProps={inline.actionProps?.iconProps}
action={(ev) => action(message, ev, { onOpen, onClose })}
/>
{/await}
{/if}
{/each}
{#if withActionMenu} {#if withActionMenu}
<ActivityMessageAction size="small" icon={IconMoreV} opened={isActionMenuOpened} action={showMenu} /> <ActivityMessageAction
size="small"
icon={IconMoreV}
opened={isActionMenuOpened}
action={showMenu}
label={view.string.MoreActions}
/>
{/if} {/if}
</div> </div>
{/if} {/if}

View File

@ -26,9 +26,10 @@
import core, { Account, Doc, Ref, Timestamp } from '@hcengineering/core' import core, { Account, Doc, Ref, Timestamp } from '@hcengineering/core'
import { Icon, Label, resizeObserver, TimeSince, tooltip } from '@hcengineering/ui' import { Icon, Label, resizeObserver, TimeSince, tooltip } from '@hcengineering/ui'
import { Asset, getEmbeddedLabel, IntlString } from '@hcengineering/platform' import { Asset, getEmbeddedLabel, IntlString } from '@hcengineering/platform'
import activity, { ActivityMessagePreviewType } from '@hcengineering/activity' import activity, { ActivityMessage, ActivityMessagePreviewType } from '@hcengineering/activity'
import { classIcon, DocNavLink } from '@hcengineering/view-resources' import { classIcon, DocNavLink, showMenu } from '@hcengineering/view-resources'
export let message: ActivityMessage | undefined = undefined
export let text: string | undefined = undefined export let text: string | undefined = undefined
export let intlLabel: IntlString | undefined = undefined export let intlLabel: IntlString | undefined = undefined
export let readonly = false export let readonly = false
@ -99,6 +100,12 @@
width = element.clientWidth width = element.clientWidth
}} }}
on:click on:click
on:contextmenu={(evt) => {
showMenu(evt, { object: message, baseMenuClass: activity.class.ActivityMessage }, () => {
isActionsOpened = false
})
isActionsOpened = true
}}
> >
<span class="left overflow-label"> <span class="left overflow-label">
{#if type === 'full'} {#if type === 'full'}

View File

@ -1,34 +0,0 @@
<!--
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { getClient } from '@hcengineering/presentation'
import view from '@hcengineering/view'
import { ActivityMessage } from '@hcengineering/activity'
import ActivityMessageAction from './ActivityMessageAction.svelte'
export let object: ActivityMessage
const client = getClient()
async function toggleMessagePinning (): Promise<void> {
await client.update(object, { isPinned: !object.isPinned })
}
</script>
<ActivityMessageAction
icon={view.icon.Pin}
iconProps={{ fill: object.isPinned ? '#3265cb' : 'currentColor' }}
action={toggleMessagePinning}
/>

View File

@ -29,7 +29,6 @@
export let object: ActivityMessage export let object: ActivityMessage
export let embedded = false export let embedded = false
export let onReply: (() => void) | undefined = undefined
const client = getClient() const client = getClient()
const maxDisplayPersons = 5 const maxDisplayPersons = 5
@ -83,11 +82,6 @@
e.stopPropagation() e.stopPropagation()
e.preventDefault() e.preventDefault()
if (onReply) {
onReply()
return
}
if (replyProvider) { if (replyProvider) {
const fn = await getResource(replyProvider.function) const fn = await getResource(replyProvider.function)
fn(object) fn(object)

View File

@ -1,52 +0,0 @@
<!--
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { createQuery, getClient } from '@hcengineering/presentation'
import activity, { ActivityMessage, SavedMessage } from '@hcengineering/activity'
import preference from '@hcengineering/preference'
import BookmarkBorder from './icons/BookmarkBorder.svelte'
import ActivityMessageAction from './ActivityMessageAction.svelte'
import Bookmark from './icons/Bookmark.svelte'
import { savedMessagesStore } from '../activity'
export let object: ActivityMessage
const client = getClient()
let savedMessage: SavedMessage | undefined = undefined
savedMessagesStore.subscribe((saved) => {
savedMessage = saved.find(({ attachedTo }) => attachedTo === object._id)
})
async function toggleSaveMessage (): Promise<void> {
if (savedMessage !== undefined) {
await client.remove(savedMessage)
savedMessage = undefined
} else {
await client.createDoc(activity.class.SavedMessage, preference.space.Preference, {
attachedTo: object._id
})
}
}
</script>
<ActivityMessageAction
icon={savedMessage ? Bookmark : BookmarkBorder}
size={savedMessage ? 'small' : 'x-small'}
iconProps={{ fill: savedMessage ? 'var(--global-accent-TextColor)' : 'currentColor' }}
action={toggleSaveMessage}
/>

View File

@ -1,30 +0,0 @@
<!--
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { ActivityMessageExtension, ActivityMessageExtensionKind } from '@hcengineering/activity'
import { Component } from '@hcengineering/ui'
export let kind: ActivityMessageExtensionKind
export let extensions: ActivityMessageExtension[] = []
export let props: Record<string, any> = {}
</script>
{#each extensions as extension}
{#each extension.components as component}
{#if component.kind === kind}
<Component is={component.component} {props} showLoading={false} on:close on:open />
{/if}
{/each}
{/each}

View File

@ -39,7 +39,6 @@
export let hideLink = false export let hideLink = false
export let compact = false export let compact = false
export let onClick: (() => void) | undefined = undefined export let onClick: (() => void) | undefined = undefined
export let onReply: (() => void) | undefined = undefined
const client = getClient() const client = getClient()
const hierarchy = client.getHierarchy() const hierarchy = client.getHierarchy()
@ -70,8 +69,7 @@
videoPreload, videoPreload,
hideLink, hideLink,
compact, compact,
onClick, onClick
onReply
}} }}
/> />
{/if} {/if}

View File

@ -13,23 +13,18 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import activity, { import activity, { ActivityMessageViewlet, DisplayActivityMessage } from '@hcengineering/activity'
ActivityMessageExtension,
ActivityMessageViewlet,
DisplayActivityMessage
} from '@hcengineering/activity'
import { Person } from '@hcengineering/contact' import { Person } from '@hcengineering/contact'
import { Avatar, EmployeePresenter, SystemAvatar } from '@hcengineering/contact-resources' import { Avatar, EmployeePresenter, SystemAvatar } from '@hcengineering/contact-resources'
import core from '@hcengineering/core' import core from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation' import { getClient } from '@hcengineering/presentation'
import { Action, Label } from '@hcengineering/ui' import { Action, Icon, Label } from '@hcengineering/ui'
import { getActions, restrictionStore } from '@hcengineering/view-resources' import { getActions, restrictionStore, showMenu } from '@hcengineering/view-resources'
import ReactionsPresenter from '../reactions/ReactionsPresenter.svelte' import ReactionsPresenter from '../reactions/ReactionsPresenter.svelte'
import ActivityMessagePresenter from './ActivityMessagePresenter.svelte' import ActivityMessagePresenter from './ActivityMessagePresenter.svelte'
import ActivityMessageActions from '../ActivityMessageActions.svelte' import ActivityMessageActions from '../ActivityMessageActions.svelte'
import { isReactionMessage } from '../../activityMessagesUtils' import { isReactionMessage } from '../../activityMessagesUtils'
import Bookmark from '../icons/Bookmark.svelte'
import { savedMessagesStore } from '../../activity' import { savedMessagesStore } from '../../activity'
import MessageTimestamp from '../MessageTimestamp.svelte' import MessageTimestamp from '../MessageTimestamp.svelte'
import Replies from '../Replies.svelte' import Replies from '../Replies.svelte'
@ -53,14 +48,12 @@
export let hoverStyles: 'borderedHover' | 'filledHover' = 'borderedHover' export let hoverStyles: 'borderedHover' | 'filledHover' = 'borderedHover'
export let showDatePreposition = false export let showDatePreposition = false
export let onClick: (() => void) | undefined = undefined export let onClick: (() => void) | undefined = undefined
export let onReply: (() => void) | undefined = undefined
const client = getClient() const client = getClient()
let menuActionIds: string[] = [] let menuActionIds: string[] = []
let element: HTMLDivElement | undefined = undefined let element: HTMLDivElement | undefined = undefined
let extensions: ActivityMessageExtension[] = []
let isActionsOpened = false let isActionsOpened = false
let isSaved = false let isSaved = false
@ -85,8 +78,6 @@
setTimeout(scrollToMessage, 100) setTimeout(scrollToMessage, 100)
} }
$: extensions = client.getModel().findAllSync(activity.class.ActivityMessageExtension, { ofMessage: message._class })
function handleActionsOpened (): void { function handleActionsOpened (): void {
isActionsOpened = true isActionsOpened = true
} }
@ -121,6 +112,12 @@
class:borderedHover={hoverStyles === 'borderedHover'} class:borderedHover={hoverStyles === 'borderedHover'}
class:filledHover={hoverStyles === 'filledHover'} class:filledHover={hoverStyles === 'filledHover'}
on:click={onClick} on:click={onClick}
on:contextmenu={(evt) => {
showMenu(evt, { object: message, baseMenuClass: activity.class.ActivityMessage }, () => {
isActionsOpened = false
})
isActionsOpened = true
}}
> >
{#if showNotify && !embedded} {#if showNotify && !embedded}
<div class="notify" /> <div class="notify" />
@ -136,7 +133,7 @@
{/if} {/if}
{#if isSaved} {#if isSaved}
<div class="saveMarker"> <div class="saveMarker">
<Bookmark size="xx-small" /> <Icon icon={activity.icon.BookmarkFilled} size="xx-small" />
</div> </div>
{/if} {/if}
</div> </div>
@ -171,7 +168,7 @@
<slot name="content" /> <slot name="content" />
{#if !hideFooter} {#if !hideFooter}
<Replies {embedded} object={message} {onReply} /> <Replies {embedded} object={message} />
{/if} {/if}
<ReactionsPresenter object={message} {readonly} /> <ReactionsPresenter object={message} {readonly} />
{#if parentMessage && showEmbedded} {#if parentMessage && showEmbedded}
@ -184,11 +181,10 @@
<div class="actions" class:opened={isActionsOpened}> <div class="actions" class:opened={isActionsOpened}>
<ActivityMessageActions <ActivityMessageActions
message={isReactionMessage(message) ? parentMessage : message} message={isReactionMessage(message) ? parentMessage : message}
{extensions}
{actions} {actions}
{withActionMenu} {withActionMenu}
on:open={handleActionsOpened} onOpen={handleActionsOpened}
on:close={handleActionsClosed} onClose={handleActionsClosed}
/> />
</div> </div>
{/if} {/if}

View File

@ -14,7 +14,6 @@
--> -->
<script lang="ts"> <script lang="ts">
import { getClient } from '@hcengineering/presentation'
import { IntlString } from '@hcengineering/platform' import { IntlString } from '@hcengineering/platform'
import activity, { ActivityMessage, ActivityMessagePreviewType } from '@hcengineering/activity' import activity, { ActivityMessage, ActivityMessagePreviewType } from '@hcengineering/activity'
@ -30,17 +29,14 @@
export let message: ActivityMessage export let message: ActivityMessage
export let actions: Action[] = [] export let actions: Action[] = []
const client = getClient()
let previewElement: BasePreview let previewElement: BasePreview
let isCompact = false let isCompact = false
$: extensions = client.getModel().findAllSync(activity.class.ActivityMessageExtension, { ofMessage: message._class })
</script> </script>
<BasePreview <BasePreview
bind:this={previewElement} bind:this={previewElement}
bind:isCompact bind:isCompact
{message}
{text} {text}
{intlLabel} {intlLabel}
{readonly} {readonly}
@ -58,13 +54,14 @@
{/if} {/if}
</svelte:fragment> </svelte:fragment>
<svelte:fragment slot="actions"> <svelte:fragment slot="actions">
<ActivityMessageActions {#if previewElement}
{message} <ActivityMessageActions
{extensions} {message}
{actions} {actions}
withActionMenu={false} withActionMenu={false}
on:open={previewElement.onActionsOpened} onOpen={previewElement.onActionsOpened}
on:close={previewElement.onActionsClosed} onClose={previewElement.onActionsClosed}
/> />
{/if}
</svelte:fragment> </svelte:fragment>
</BasePreview> </BasePreview>

View File

@ -43,7 +43,6 @@
export let hideLink = false export let hideLink = false
export let compact = false export let compact = false
export let onClick: (() => void) | undefined = undefined export let onClick: (() => void) | undefined = undefined
export let onReply: (() => void) | undefined = undefined
const client = getClient() const client = getClient()
const hierarchy = client.getHierarchy() const hierarchy = client.getHierarchy()
@ -112,7 +111,6 @@
{hoverStyles} {hoverStyles}
showDatePreposition showDatePreposition
{onClick} {onClick}
{onReply}
> >
<svelte:fragment slot="header"> <svelte:fragment slot="header">
<span class="header"> <span class="header">

View File

@ -51,7 +51,6 @@
export let hoverStyles: 'borderedHover' | 'filledHover' = 'borderedHover' export let hoverStyles: 'borderedHover' | 'filledHover' = 'borderedHover'
export let hideLink = false export let hideLink = false
export let onClick: (() => void) | undefined = undefined export let onClick: (() => void) | undefined = undefined
export let onReply: (() => void) | undefined = undefined
const client = getClient() const client = getClient()
const hierarchy = client.getHierarchy() const hierarchy = client.getHierarchy()
@ -172,7 +171,6 @@
{hoverStyles} {hoverStyles}
showDatePreposition={hideLink} showDatePreposition={hideLink}
{onClick} {onClick}
{onReply}
> >
<svelte:fragment slot="header"> <svelte:fragment slot="header">
<DocUpdateMessageHeader <DocUpdateMessageHeader

View File

@ -1,26 +0,0 @@
<!--
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
export let size: 'tiny' | 'xx-small' | 'x-small' | 'small' | 'medium' | 'large' = 'small'
export let fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M6 6.00012C6 3.79098 7.79086 2.00012 10 2.00012H22C24.2091 2.00012 26 3.79098 26 6.00012V29.0001C26 29.3689 25.797 29.7078 25.4719 29.8818C25.1467 30.0558 24.7522 30.0367 24.4453 29.8322L16 24.202L7.5547 29.8322C7.24784 30.0367 6.8533 30.0558 6.52814 29.8818C6.20298 29.7078 6 29.3689 6 29.0001V6.00012Z"
/>
</svg>

View File

@ -1,26 +0,0 @@
<!--
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
export let size: 'small' | 'medium' | 'large'
export let fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} viewBox="0 0 20 28" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M0 4.00012C0 1.79098 1.79086 0.00012207 4 0.00012207H16C18.2091 0.00012207 20 1.79098 20 4.00012V27.0001C20 27.3689 19.797 27.7078 19.4719 27.8818C19.1467 28.0558 18.7522 28.0367 18.4453 27.8322L10 22.202L1.5547 27.8322C1.24784 28.0367 0.8533 28.0558 0.528142 27.8818C0.202985 27.7078 0 27.3689 0 27.0001V4.00012ZM4 2.00012C2.89543 2.00012 2 2.89555 2 4.00012V25.1316L9.4453 20.1681C9.7812 19.9441 10.2188 19.9441 10.5547 20.1681L18 25.1316V4.00012C18 2.89555 17.1046 2.00012 16 2.00012H4Z"
/>
</svg>

View File

@ -1,52 +0,0 @@
<!--
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { EmojiPopup, showPopup } from '@hcengineering/ui'
import { createQuery, getClient } from '@hcengineering/presentation'
import activity, { ActivityMessage, Reaction } from '@hcengineering/activity'
import { updateDocReactions } from '../../utils'
import ActivityMessageAction from '../ActivityMessageAction.svelte'
export let object: ActivityMessage | undefined = undefined
const client = getClient()
const reactionsQuery = createQuery()
const dispatch = createEventDispatcher()
let reactions: Reaction[] = []
let isOpened = false
$: if (object?.reactions && object.reactions > 0) {
reactionsQuery.query(activity.class.Reaction, { attachedTo: object._id }, (res?: Reaction[]) => {
reactions = res || []
})
}
function openEmojiPalette (ev: Event) {
dispatch('open')
showPopup(EmojiPopup, {}, ev.target as HTMLElement, (emoji: string) => {
updateDocReactions(client, reactions, object, emoji)
isOpened = false
dispatch('close')
})
isOpened = true
}
</script>
<ActivityMessageAction icon={activity.icon.Emoji} action={openEmojiPalette} opened={isOpened} />

View File

@ -28,6 +28,17 @@ import ActivityInfoMessagePreview from './components/activity-info-message/Activ
import { attributesFilter, pinnedFilter, allFilter, referencesFilter } from './activityMessagesUtils' import { attributesFilter, pinnedFilter, allFilter, referencesFilter } from './activityMessagesUtils'
import { updateReferences } from './references' import { updateReferences } from './references'
import {
addReactionAction,
canPinMessage,
canRemoveFromSaved,
saveForLater,
unpinMessage,
pinMessage,
canSaveForLater,
canUnpinMessage,
removeFromSaved
} from './utils'
export * from './activity' export * from './activity'
export * from './utils' export * from './utils'
@ -42,7 +53,6 @@ export { default as ActivityDocLink } from './components/ActivityDocLink.svelte'
export { default as ReactionPresenter } from './components/reactions/ReactionPresenter.svelte' export { default as ReactionPresenter } from './components/reactions/ReactionPresenter.svelte'
export { default as ActivityMessageNotificationLabel } from './components/activity-message/ActivityMessageNotificationLabel.svelte' export { default as ActivityMessageNotificationLabel } from './components/activity-message/ActivityMessageNotificationLabel.svelte'
export { default as ActivityMessageHeader } from './components/activity-message/ActivityMessageHeader.svelte' export { default as ActivityMessageHeader } from './components/activity-message/ActivityMessageHeader.svelte'
export { default as AddReactionAction } from './components/reactions/AddReactionAction.svelte'
export { default as ActivityMessageAction } from './components/ActivityMessageAction.svelte' export { default as ActivityMessageAction } from './components/ActivityMessageAction.svelte'
export { default as ActivityMessagesFilterPopup } from './components/FilterPopup.svelte' export { default as ActivityMessagesFilterPopup } from './components/FilterPopup.svelte'
export { default as ActivityReferencePresenter } from './components/activity-reference/ActivityReferencePresenter.svelte' export { default as ActivityReferencePresenter } from './components/activity-reference/ActivityReferencePresenter.svelte'
@ -70,7 +80,20 @@ export default async (): Promise<Resources> => ({
AllFilter: allFilter, AllFilter: allFilter,
ReferencesFilter: referencesFilter ReferencesFilter: referencesFilter
}, },
function: {
CanSaveForLater: canSaveForLater,
CanRemoveFromSaved: canRemoveFromSaved,
CanPinMessage: canPinMessage,
CanUnpinMessage: canUnpinMessage
},
backreference: { backreference: {
Update: updateReferences Update: updateReferences
},
actionImpl: {
AddReaction: addReactionAction,
SaveForLater: saveForLater,
RemoveFromSaved: removeFromSaved,
PinMessage: pinMessage,
UnpinMessage: unpinMessage
} }
}) })

View File

@ -20,18 +20,23 @@ import core, {
getCurrentAccount getCurrentAccount
} from '@hcengineering/core' } from '@hcengineering/core'
import { type Asset, type IntlString, getResource, translate } from '@hcengineering/platform' import { type Asset, type IntlString, getResource, translate } from '@hcengineering/platform'
import { getAttributePresenterClass } from '@hcengineering/presentation' import { getAttributePresenterClass, getClient } from '@hcengineering/presentation'
import { import {
type AnyComponent, type AnyComponent,
type AnySvelteComponent, type AnySvelteComponent,
ErrorPresenter, ErrorPresenter,
themeStore, themeStore,
type Location type Location,
getEventPositionElement,
closePopup,
showPopup,
EmojiPopup
} from '@hcengineering/ui' } from '@hcengineering/ui'
import view, { type AttributeModel, type BuildModelKey, type BuildModelOptions } from '@hcengineering/view' import view, { type AttributeModel, type BuildModelKey, type BuildModelOptions } from '@hcengineering/view'
import { getObjectPresenter } from '@hcengineering/view-resources' import { getObjectPresenter } from '@hcengineering/view-resources'
import preference from '@hcengineering/preference'
import { type ActivityKey, activityKey } from './activity' import { type ActivityKey, activityKey, savedMessagesStore } from './activity'
import activity from './plugin' import activity from './plugin'
const valueTypes: ReadonlyArray<Ref<Class<Doc>>> = [ const valueTypes: ReadonlyArray<Ref<Class<Doc>>> = [
@ -427,3 +432,90 @@ export async function updateDocReactions (
export function getMessageFromLoc (loc: Location): Ref<ActivityMessage> | undefined { export function getMessageFromLoc (loc: Location): Ref<ActivityMessage> | undefined {
return (loc.query?.message ?? undefined) as Ref<ActivityMessage> | undefined return (loc.query?.message ?? undefined) as Ref<ActivityMessage> | undefined
} }
interface ActivityMessageActionParams {
onClose?: () => void
onOpen?: () => void
}
export async function addReactionAction (
message?: ActivityMessage,
ev?: MouseEvent,
params?: ActivityMessageActionParams
): Promise<void> {
if (message === undefined || ev === undefined) return
const client = getClient()
const reactions: Reaction[] =
(message.reactions ?? 0) > 0
? await client.findAll<Reaction>(activity.class.Reaction, { attachedTo: message._id })
: []
const element = getEventPositionElement(ev)
closePopup()
showPopup(EmojiPopup, {}, element, (emoji: string) => {
void updateDocReactions(client, reactions, message, emoji)
params?.onClose?.()
})
params?.onOpen?.()
}
export async function saveForLater (message?: ActivityMessage): Promise<void> {
if (message === undefined) return
closePopup()
const client = getClient()
await client.createDoc(activity.class.SavedMessage, preference.space.Preference, {
attachedTo: message._id
})
}
export async function removeFromSaved (message?: ActivityMessage): Promise<void> {
if (message === undefined) return
closePopup()
const client = getClient()
const saved = get(savedMessagesStore).find((saved) => saved.attachedTo === message._id)
if (saved !== undefined) {
await client.removeDoc(saved._class, saved.space, saved._id)
}
}
export async function canSaveForLater (message?: ActivityMessage): Promise<boolean> {
if (message === undefined) return false
const saved = get(savedMessagesStore).find((saved) => saved.attachedTo === message._id)
return saved === undefined
}
export async function canRemoveFromSaved (message?: ActivityMessage): Promise<boolean> {
if (message === undefined) return false
return !(await canSaveForLater(message))
}
export async function canPinMessage (message?: ActivityMessage): Promise<boolean> {
return message !== undefined && message.isPinned !== true
}
export async function canUnpinMessage (message?: ActivityMessage): Promise<boolean> {
return message !== undefined && message.isPinned === true
}
export async function pinMessage (message?: ActivityMessage): Promise<void> {
if (message === undefined) return
closePopup()
const client = getClient()
await client.update(message, { isPinned: true })
}
export async function unpinMessage (message?: ActivityMessage): Promise<void> {
if (message === undefined) return
closePopup()
const client = getClient()
await client.update(message, { isPinned: false })
}

View File

@ -157,16 +157,6 @@ export interface ActivityInfoMessage extends ActivityMessage {
links?: { _class: Ref<Class<Doc>>, _id: Ref<Doc> }[] links?: { _class: Ref<Class<Doc>>, _id: Ref<Doc> }[]
} }
export type ActivityMessageExtensionKind = 'action' | 'footer'
/**
* @public
*/
export interface ActivityMessageExtension extends Doc {
ofMessage: Ref<Class<ActivityMessage>>
components: { kind: ActivityMessageExtensionKind, component: AnyComponent }[]
}
/** /**
* @public * @public
*/ */
@ -333,7 +323,6 @@ export default plugin(activityId, {
ActivityInfoMessage: '' as Ref<Class<ActivityInfoMessage>>, ActivityInfoMessage: '' as Ref<Class<ActivityInfoMessage>>,
ActivityMessageControl: '' as Ref<Class<ActivityMessageControl>>, ActivityMessageControl: '' as Ref<Class<ActivityMessageControl>>,
DocUpdateMessageViewlet: '' as Ref<Class<DocUpdateMessageViewlet>>, DocUpdateMessageViewlet: '' as Ref<Class<DocUpdateMessageViewlet>>,
ActivityMessageExtension: '' as Ref<Class<ActivityMessageExtension>>,
ActivityMessagesFilter: '' as Ref<Class<ActivityMessagesFilter>>, ActivityMessagesFilter: '' as Ref<Class<ActivityMessagesFilter>>,
ActivityExtension: '' as Ref<Class<ActivityExtension>>, ActivityExtension: '' as Ref<Class<ActivityExtension>>,
Reaction: '' as Ref<Class<Reaction>>, Reaction: '' as Ref<Class<Reaction>>,
@ -344,7 +333,8 @@ export default plugin(activityId, {
icon: { icon: {
Activity: '' as Asset, Activity: '' as Asset,
Emoji: '' as Asset, Emoji: '' as Asset,
Bookmark: '' as Asset Bookmark: '' as Asset,
BookmarkFilled: '' as Asset
}, },
string: { string: {
Activity: '' as IntlString, Activity: '' as IntlString,

View File

@ -1,32 +0,0 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { ActivityMessage } from '@hcengineering/activity'
import { ActivityMessageAction } from '@hcengineering/activity-resources'
import chunter from './../plugin'
import { replyToThread } from '../index'
import { canReplyToThread } from '../utils'
export let object: ActivityMessage
function onReply (): void {
replyToThread(object)
}
</script>
{#if canReplyToThread(object)}
<ActivityMessageAction size="small" icon={chunter.icon.Thread} action={onReply} />
{/if}

View File

@ -50,7 +50,6 @@
export let hideLink = false export let hideLink = false
export let compact = false export let compact = false
export let onClick: (() => void) | undefined = undefined export let onClick: (() => void) | undefined = undefined
export let onReply: (() => void) | undefined = undefined
const client = getClient() const client = getClient()
const hierarchy = client.getHierarchy() const hierarchy = client.getHierarchy()
@ -183,7 +182,6 @@
{skipLabel} {skipLabel}
showDatePreposition={hideLink} showDatePreposition={hideLink}
{onClick} {onClick}
{onReply}
> >
<svelte:fragment slot="header"> <svelte:fragment slot="header">
<ChatMessageHeader {object} {parentObject} message={value} {viewlet} {person} {skipLabel} {hideLink} /> <ChatMessageHeader {object} {parentObject} message={value} {viewlet} {person} {skipLabel} {hideLink} />

View File

@ -40,7 +40,6 @@
export let attachmentImageSize: AttachmentImageSize = 'x-large' export let attachmentImageSize: AttachmentImageSize = 'x-large'
export let videoPreload = true export let videoPreload = true
export let onClick: (() => void) | undefined = undefined export let onClick: (() => void) | undefined = undefined
export let onReply: (() => void) | undefined = undefined
const client = getClient() const client = getClient()
</script> </script>
@ -76,6 +75,5 @@
{videoPreload} {videoPreload}
showLinksPreview={false} showLinksPreview={false}
{onClick} {onClick}
{onReply}
/> />
{/if} {/if}

View File

@ -48,7 +48,6 @@ import ChannelIcon from './components/ChannelIcon.svelte'
import ThreadNotificationPresenter from './components/notification/ThreadNotificationPresenter.svelte' import ThreadNotificationPresenter from './components/notification/ThreadNotificationPresenter.svelte'
import ChatMessageNotificationLabel from './components/notification/ChatMessageNotificationLabel.svelte' import ChatMessageNotificationLabel from './components/notification/ChatMessageNotificationLabel.svelte'
import ChatAside from './components/chat/ChatAside.svelte' import ChatAside from './components/chat/ChatAside.svelte'
import ReplyToThreadAction from './components/ReplyToThreadAction.svelte'
import ThreadMessagePreview from './components/threads/ThreadMessagePreview.svelte' import ThreadMessagePreview from './components/threads/ThreadMessagePreview.svelte'
import ChatMessagePreview from './components/chat-message/ChatMessagePreview.svelte' import ChatMessagePreview from './components/chat-message/ChatMessagePreview.svelte'
@ -62,7 +61,8 @@ import {
getUnreadThreadsCount, getUnreadThreadsCount,
canCopyMessageLink, canCopyMessageLink,
leaveChannelAction, leaveChannelAction,
removeChannelAction removeChannelAction,
canReplyToThread
} from './utils' } from './utils'
import { import {
chunterSpaceLinkFragmentProvider, chunterSpaceLinkFragmentProvider,
@ -180,7 +180,6 @@ export default async (): Promise<Resources> => ({
ChatMessageNotificationLabel, ChatMessageNotificationLabel,
ThreadNotificationPresenter, ThreadNotificationPresenter,
ChatAside, ChatAside,
ReplyToThreadAction,
ThreadMessagePreview, ThreadMessagePreview,
ChatMessagePreview ChatMessagePreview
}, },
@ -197,8 +196,9 @@ export default async (): Promise<Resources> => ({
GetChunterSpaceLinkFragment: chunterSpaceLinkFragmentProvider, GetChunterSpaceLinkFragment: chunterSpaceLinkFragmentProvider,
GetUnreadThreadsCount: getUnreadThreadsCount, GetUnreadThreadsCount: getUnreadThreadsCount,
GetThreadLink: getThreadLink, GetThreadLink: getThreadLink,
GetMessageLink: getMessageLocation, ReplyToThread: replyToThread,
ReplyToThread: replyToThread CanReplyToThread: canReplyToThread,
GetMessageLink: getMessageLocation
}, },
actionImpl: { actionImpl: {
ArchiveChannel, ArchiveChannel,
@ -206,6 +206,7 @@ export default async (): Promise<Resources> => ({
ConvertDmToPrivateChannel, ConvertDmToPrivateChannel,
DeleteChatMessage: deleteChatMessage, DeleteChatMessage: deleteChatMessage,
LeaveChannel: leaveChannelAction, LeaveChannel: leaveChannelAction,
RemoveChannel: removeChannelAction RemoveChannel: removeChannelAction,
ReplyToThread: replyToThread
} }
}) })

View File

@ -138,8 +138,6 @@ export default plugin(chunterId, {
ChatMessagePresenter: '' as AnyComponent, ChatMessagePresenter: '' as AnyComponent,
ThreadMessagePresenter: '' as AnyComponent, ThreadMessagePresenter: '' as AnyComponent,
ChatAside: '' as AnyComponent, ChatAside: '' as AnyComponent,
Replies: '' as AnyComponent,
ReplyToThreadAction: '' as AnyComponent,
ChatMessagePreview: '' as AnyComponent, ChatMessagePreview: '' as AnyComponent,
ThreadMessagePreview: '' as AnyComponent ThreadMessagePreview: '' as AnyComponent
}, },

View File

@ -119,6 +119,7 @@
"ToViewCommands": "to view available commands", "ToViewCommands": "to view available commands",
"UnArchive": "Unarchive", "UnArchive": "Unarchive",
"Pin": "Pin", "Pin": "Pin",
"Unpin": "Unpin" "Unpin": "Unpin",
"MoreActions": "More actions"
} }
} }

View File

@ -116,6 +116,7 @@
"ToViewCommands": "чтобы увидеть команды", "ToViewCommands": "чтобы увидеть команды",
"UnArchive": "Разархивировать", "UnArchive": "Разархивировать",
"Pin": "Закрепить", "Pin": "Закрепить",
"Unpin": "Открепить" "Unpin": "Открепить",
"MoreActions": "Больше действий"
} }
} }

View File

@ -194,7 +194,8 @@ const view = plugin(viewId, {
Type: '' as IntlString, Type: '' as IntlString,
UnArchive: '' as IntlString, UnArchive: '' as IntlString,
Save: '' as IntlString, Save: '' as IntlString,
PublicView: '' as IntlString PublicView: '' as IntlString,
MoreActions: '' as IntlString
}, },
icon: { icon: {
Table: '' as Asset, Table: '' as Asset,