mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 11:01:54 +03:00
UBERF-5527: add context menu for activity and inbox (#5373)
Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
parent
577c948d29
commit
7405b01d04
@ -21,8 +21,6 @@ import {
|
||||
type ActivityInfoMessage,
|
||||
type ActivityMessage,
|
||||
type ActivityMessageControl,
|
||||
type ActivityMessageExtension,
|
||||
type ActivityMessageExtensionKind,
|
||||
type ActivityMessagePreview,
|
||||
type ActivityMessagesFilter,
|
||||
type ActivityReference,
|
||||
@ -70,7 +68,7 @@ import {
|
||||
} from '@hcengineering/model'
|
||||
import { TAttachedDoc, TClass, TDoc } from '@hcengineering/model-core'
|
||||
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 type { Asset, IntlString, Resource } from '@hcengineering/platform'
|
||||
import { type AnyComponent } from '@hcengineering/ui/src/types'
|
||||
@ -210,15 +208,6 @@ export class TDocUpdateMessageViewlet extends TDoc implements DocUpdateMessageVi
|
||||
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)
|
||||
export class TActivityExtension extends TDoc implements ActivityExtension {
|
||||
@Prop(TypeRef(core.class.Class), core.string.Class)
|
||||
@ -275,7 +264,6 @@ export function createModel (builder: Builder): void {
|
||||
TTxViewlet,
|
||||
TActivityDoc,
|
||||
TActivityMessagesFilter,
|
||||
TActivityMessageExtension,
|
||||
TActivityMessage,
|
||||
TDocUpdateMessage,
|
||||
TDocUpdateMessageViewlet,
|
||||
@ -426,6 +414,113 @@ export function createModel (builder: Builder): void {
|
||||
{ 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
|
||||
|
@ -16,7 +16,13 @@ import { activityId, type ActivityMessage, type DocUpdateMessageViewlet } from '
|
||||
import activity from '@hcengineering/activity-resources/src/plugin'
|
||||
import { type IntlString, mergeIds, type Resource } from '@hcengineering/platform'
|
||||
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'
|
||||
|
||||
export default mergeIds(activityId, activity, {
|
||||
@ -24,7 +30,10 @@ export default mergeIds(activityId, activity, {
|
||||
Attributes: '' as IntlString,
|
||||
Pinned: '' as IntlString,
|
||||
Emoji: '' as IntlString,
|
||||
Replies: '' as IntlString
|
||||
Replies: '' as IntlString,
|
||||
AddReaction: '' as IntlString,
|
||||
SaveForLater: '' as IntlString,
|
||||
RemoveFromLater: '' as IntlString
|
||||
},
|
||||
filter: {
|
||||
AttributesFilter: '' as Resource<(message: ActivityMessage, _class?: Ref<Doc>) => boolean>,
|
||||
@ -35,9 +44,28 @@ export default mergeIds(activityId, activity, {
|
||||
ids: {
|
||||
ReactionAddedActivityViewlet: '' as Ref<DocUpdateMessageViewlet>,
|
||||
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: {
|
||||
Activity: '' as Ref<ActionCategory>
|
||||
},
|
||||
actionImpl: {
|
||||
AddReaction: '' as ViewAction,
|
||||
SaveForLater: '' as ViewAction,
|
||||
RemoveFromSaved: '' as ViewAction,
|
||||
PinMessage: '' as ViewAction,
|
||||
UnpinMessage: '' as ViewAction
|
||||
}
|
||||
})
|
||||
|
@ -677,46 +677,6 @@ export function createModel (builder: Builder, options = { addApplication: true
|
||||
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, {
|
||||
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, {
|
||||
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
|
||||
|
@ -40,13 +40,15 @@ export default mergeIds(chunterId, chunter, {
|
||||
ArchiveChannel: '' as Ref<Action>,
|
||||
UnarchiveChannel: '' as Ref<Action>,
|
||||
ConvertToPrivate: '' as Ref<Action>,
|
||||
CopyChatMessageLink: '' as Ref<Action<Doc, any>>
|
||||
CopyChatMessageLink: '' as Ref<Action<Doc, any>>,
|
||||
ReplyToThreadAction: '' as Ref<Action>
|
||||
},
|
||||
actionImpl: {
|
||||
ArchiveChannel: '' as ViewAction,
|
||||
UnarchiveChannel: '' as ViewAction,
|
||||
ConvertDmToPrivateChannel: '' as ViewAction,
|
||||
DeleteChatMessage: '' as ViewAction
|
||||
DeleteChatMessage: '' as ViewAction,
|
||||
ReplyToThread: '' as ViewAction
|
||||
},
|
||||
category: {
|
||||
Chunter: '' as Ref<ActionCategory>
|
||||
@ -100,8 +102,9 @@ export default mergeIds(chunterId, chunter, {
|
||||
CanCopyMessageLink: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>,
|
||||
GetChunterSpaceLinkFragment: '' 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: {
|
||||
ChatMessagesFilter: '' as Resource<(message: ActivityMessage, _class?: Ref<Doc>) => boolean>
|
||||
|
@ -111,6 +111,7 @@ export function closePopup (category?: string): void {
|
||||
} else {
|
||||
for (let i = popups.length - 1; i >= 0; i--) {
|
||||
if (popups[i].options.fixed !== true) {
|
||||
popups[i].onClose?.(undefined)
|
||||
popups.splice(i, 1)
|
||||
break
|
||||
}
|
||||
|
@ -239,6 +239,7 @@ export type IconSize =
|
||||
| 'inline'
|
||||
| 'tiny'
|
||||
| 'card'
|
||||
| 'xx-small'
|
||||
| 'x-small'
|
||||
| 'smaller'
|
||||
| 'small'
|
||||
@ -258,6 +259,7 @@ export function getIconSize2x (size: IconSize): IconSize {
|
||||
switch (size) {
|
||||
case 'inline':
|
||||
case 'tiny':
|
||||
case 'xx-small':
|
||||
case 'x-small':
|
||||
case 'small':
|
||||
case 'card':
|
||||
|
@ -2,13 +2,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"/>
|
||||
</symbol>
|
||||
<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="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 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 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" />
|
||||
</symbol>
|
||||
<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="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 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 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" />
|
||||
</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>
|
||||
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.8 KiB |
@ -40,6 +40,9 @@
|
||||
"Mentions": "Mentions",
|
||||
"MentionedYouIn": "Mentioned you in {title}",
|
||||
"Messages": "Messages",
|
||||
"Thread": "Thread"
|
||||
"Thread": "Thread",
|
||||
"AddReaction": "Add reaction",
|
||||
"SaveForLater": "Save for later",
|
||||
"RemoveFromLater": "Remove from saved"
|
||||
}
|
||||
}
|
@ -40,6 +40,9 @@
|
||||
"Mentions": "Упоминания",
|
||||
"MentionedYouIn": "Упомянул(а) вас в {title}",
|
||||
"Messages": "Cообщения",
|
||||
"Thread": "Обсуждение"
|
||||
"Thread": "Обсуждение",
|
||||
"AddReaction": "Добавить реакцию",
|
||||
"SaveForLater": "Cохранить",
|
||||
"RemoveFromLater": "Удалить из сохраненных"
|
||||
}
|
||||
}
|
@ -20,5 +20,6 @@ const icons = require('../assets/icons.svg') as string // eslint-disable-line
|
||||
loadMetadata(activity.icon, {
|
||||
Activity: `${icons}#activity`,
|
||||
Emoji: `${icons}#emoji`,
|
||||
Bookmark: `${icons}#bookmark`
|
||||
Bookmark: `${icons}#bookmark`,
|
||||
BookmarkFilled: `${icons}#bookmark-filled`
|
||||
})
|
||||
|
@ -14,9 +14,10 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { type AnySvelteComponent, ButtonIcon, IconSize } from '@hcengineering/ui'
|
||||
import { Asset } from '@hcengineering/platform'
|
||||
import { Asset, IntlString } from '@hcengineering/platform'
|
||||
import { ComponentType } from 'svelte'
|
||||
|
||||
export let label: IntlString
|
||||
export let icon: Asset | AnySvelteComponent | ComponentType
|
||||
export let iconProps: any | undefined = undefined
|
||||
export let size: IconSize = 'small'
|
||||
@ -30,4 +31,13 @@
|
||||
}
|
||||
</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 }}
|
||||
/>
|
||||
|
@ -13,60 +13,95 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import activity, { ActivityMessage, ActivityMessageExtension } from '@hcengineering/activity'
|
||||
import activity, { ActivityMessage } from '@hcengineering/activity'
|
||||
import { Action, IconMoreV, showPopup } from '@hcengineering/ui'
|
||||
import { Menu } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { getActions, Menu } from '@hcengineering/view-resources'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import view, { Action as ViewAction } from '@hcengineering/view'
|
||||
|
||||
import ActivityMessageAction from './ActivityMessageAction.svelte'
|
||||
import PinMessageAction from './PinMessageAction.svelte'
|
||||
import SaveMessageAction from './SaveMessageAction.svelte'
|
||||
import ActivityMessageExtensionComponent from './activity-message/ActivityMessageExtension.svelte'
|
||||
import AddReactionAction from './reactions/AddReactionAction.svelte'
|
||||
import { savedMessagesStore } from '../activity'
|
||||
|
||||
export let message: ActivityMessage | undefined
|
||||
export let extensions: ActivityMessageExtension[] = []
|
||||
export let actions: Action[] = []
|
||||
export let withActionMenu = true
|
||||
export let onOpen: () => void
|
||||
export let onClose: () => void
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
|
||||
let inlineActions: ViewAction[] = []
|
||||
let isActionMenuOpened = false
|
||||
|
||||
$: void updateInlineActions(message)
|
||||
|
||||
savedMessagesStore.subscribe(() => {
|
||||
void updateInlineActions(message)
|
||||
})
|
||||
|
||||
function handleActionMenuOpened (): void {
|
||||
isActionMenuOpened = true
|
||||
dispatch('open')
|
||||
onOpen()
|
||||
}
|
||||
|
||||
function handleActionMenuClosed (): void {
|
||||
isActionMenuOpened = false
|
||||
dispatch('close')
|
||||
onClose()
|
||||
}
|
||||
|
||||
function showMenu (ev: MouseEvent): void {
|
||||
const excludedActions = inlineActions.map(({ _id }) => _id)
|
||||
|
||||
showPopup(
|
||||
Menu,
|
||||
{
|
||||
object: message,
|
||||
actions,
|
||||
baseMenuClass: activity.class.ActivityMessage
|
||||
baseMenuClass: activity.class.ActivityMessage,
|
||||
excludedActions
|
||||
},
|
||||
ev.target as HTMLElement,
|
||||
handleActionMenuClosed
|
||||
)
|
||||
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>
|
||||
|
||||
{#if message}
|
||||
<div class="root">
|
||||
<AddReactionAction object={message} on:open on:close />
|
||||
<ActivityMessageExtensionComponent kind="action" {extensions} props={{ object: message }} on:close on:open />
|
||||
<PinMessageAction object={message} />
|
||||
<SaveMessageAction object={message} />
|
||||
{#each inlineActions as inline}
|
||||
{#if inline.icon}
|
||||
{#await getResource(inline.action) then action}
|
||||
<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}
|
||||
<ActivityMessageAction size="small" icon={IconMoreV} opened={isActionMenuOpened} action={showMenu} />
|
||||
<ActivityMessageAction
|
||||
size="small"
|
||||
icon={IconMoreV}
|
||||
opened={isActionMenuOpened}
|
||||
action={showMenu}
|
||||
label={view.string.MoreActions}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -26,9 +26,10 @@
|
||||
import core, { Account, Doc, Ref, Timestamp } from '@hcengineering/core'
|
||||
import { Icon, Label, resizeObserver, TimeSince, tooltip } from '@hcengineering/ui'
|
||||
import { Asset, getEmbeddedLabel, IntlString } from '@hcengineering/platform'
|
||||
import activity, { ActivityMessagePreviewType } from '@hcengineering/activity'
|
||||
import { classIcon, DocNavLink } from '@hcengineering/view-resources'
|
||||
import activity, { ActivityMessage, ActivityMessagePreviewType } from '@hcengineering/activity'
|
||||
import { classIcon, DocNavLink, showMenu } from '@hcengineering/view-resources'
|
||||
|
||||
export let message: ActivityMessage | undefined = undefined
|
||||
export let text: string | undefined = undefined
|
||||
export let intlLabel: IntlString | undefined = undefined
|
||||
export let readonly = false
|
||||
@ -99,6 +100,12 @@
|
||||
width = element.clientWidth
|
||||
}}
|
||||
on:click
|
||||
on:contextmenu={(evt) => {
|
||||
showMenu(evt, { object: message, baseMenuClass: activity.class.ActivityMessage }, () => {
|
||||
isActionsOpened = false
|
||||
})
|
||||
isActionsOpened = true
|
||||
}}
|
||||
>
|
||||
<span class="left overflow-label">
|
||||
{#if type === 'full'}
|
||||
|
@ -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}
|
||||
/>
|
@ -29,7 +29,6 @@
|
||||
|
||||
export let object: ActivityMessage
|
||||
export let embedded = false
|
||||
export let onReply: (() => void) | undefined = undefined
|
||||
|
||||
const client = getClient()
|
||||
const maxDisplayPersons = 5
|
||||
@ -83,11 +82,6 @@
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
|
||||
if (onReply) {
|
||||
onReply()
|
||||
return
|
||||
}
|
||||
|
||||
if (replyProvider) {
|
||||
const fn = await getResource(replyProvider.function)
|
||||
fn(object)
|
||||
|
@ -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}
|
||||
/>
|
@ -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}
|
@ -39,7 +39,6 @@
|
||||
export let hideLink = false
|
||||
export let compact = false
|
||||
export let onClick: (() => void) | undefined = undefined
|
||||
export let onReply: (() => void) | undefined = undefined
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
@ -70,8 +69,7 @@
|
||||
videoPreload,
|
||||
hideLink,
|
||||
compact,
|
||||
onClick,
|
||||
onReply
|
||||
onClick
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
@ -13,23 +13,18 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import activity, {
|
||||
ActivityMessageExtension,
|
||||
ActivityMessageViewlet,
|
||||
DisplayActivityMessage
|
||||
} from '@hcengineering/activity'
|
||||
import activity, { ActivityMessageViewlet, DisplayActivityMessage } from '@hcengineering/activity'
|
||||
import { Person } from '@hcengineering/contact'
|
||||
import { Avatar, EmployeePresenter, SystemAvatar } from '@hcengineering/contact-resources'
|
||||
import core from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Action, Label } from '@hcengineering/ui'
|
||||
import { getActions, restrictionStore } from '@hcengineering/view-resources'
|
||||
import { Action, Icon, Label } from '@hcengineering/ui'
|
||||
import { getActions, restrictionStore, showMenu } from '@hcengineering/view-resources'
|
||||
|
||||
import ReactionsPresenter from '../reactions/ReactionsPresenter.svelte'
|
||||
import ActivityMessagePresenter from './ActivityMessagePresenter.svelte'
|
||||
import ActivityMessageActions from '../ActivityMessageActions.svelte'
|
||||
import { isReactionMessage } from '../../activityMessagesUtils'
|
||||
import Bookmark from '../icons/Bookmark.svelte'
|
||||
import { savedMessagesStore } from '../../activity'
|
||||
import MessageTimestamp from '../MessageTimestamp.svelte'
|
||||
import Replies from '../Replies.svelte'
|
||||
@ -53,14 +48,12 @@
|
||||
export let hoverStyles: 'borderedHover' | 'filledHover' = 'borderedHover'
|
||||
export let showDatePreposition = false
|
||||
export let onClick: (() => void) | undefined = undefined
|
||||
export let onReply: (() => void) | undefined = undefined
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let menuActionIds: string[] = []
|
||||
|
||||
let element: HTMLDivElement | undefined = undefined
|
||||
let extensions: ActivityMessageExtension[] = []
|
||||
let isActionsOpened = false
|
||||
|
||||
let isSaved = false
|
||||
@ -85,8 +78,6 @@
|
||||
setTimeout(scrollToMessage, 100)
|
||||
}
|
||||
|
||||
$: extensions = client.getModel().findAllSync(activity.class.ActivityMessageExtension, { ofMessage: message._class })
|
||||
|
||||
function handleActionsOpened (): void {
|
||||
isActionsOpened = true
|
||||
}
|
||||
@ -121,6 +112,12 @@
|
||||
class:borderedHover={hoverStyles === 'borderedHover'}
|
||||
class:filledHover={hoverStyles === 'filledHover'}
|
||||
on:click={onClick}
|
||||
on:contextmenu={(evt) => {
|
||||
showMenu(evt, { object: message, baseMenuClass: activity.class.ActivityMessage }, () => {
|
||||
isActionsOpened = false
|
||||
})
|
||||
isActionsOpened = true
|
||||
}}
|
||||
>
|
||||
{#if showNotify && !embedded}
|
||||
<div class="notify" />
|
||||
@ -136,7 +133,7 @@
|
||||
{/if}
|
||||
{#if isSaved}
|
||||
<div class="saveMarker">
|
||||
<Bookmark size="xx-small" />
|
||||
<Icon icon={activity.icon.BookmarkFilled} size="xx-small" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@ -171,7 +168,7 @@
|
||||
<slot name="content" />
|
||||
|
||||
{#if !hideFooter}
|
||||
<Replies {embedded} object={message} {onReply} />
|
||||
<Replies {embedded} object={message} />
|
||||
{/if}
|
||||
<ReactionsPresenter object={message} {readonly} />
|
||||
{#if parentMessage && showEmbedded}
|
||||
@ -184,11 +181,10 @@
|
||||
<div class="actions" class:opened={isActionsOpened}>
|
||||
<ActivityMessageActions
|
||||
message={isReactionMessage(message) ? parentMessage : message}
|
||||
{extensions}
|
||||
{actions}
|
||||
{withActionMenu}
|
||||
on:open={handleActionsOpened}
|
||||
on:close={handleActionsClosed}
|
||||
onOpen={handleActionsOpened}
|
||||
onClose={handleActionsClosed}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -14,7 +14,6 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import activity, { ActivityMessage, ActivityMessagePreviewType } from '@hcengineering/activity'
|
||||
|
||||
@ -30,17 +29,14 @@
|
||||
export let message: ActivityMessage
|
||||
export let actions: Action[] = []
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let previewElement: BasePreview
|
||||
let isCompact = false
|
||||
|
||||
$: extensions = client.getModel().findAllSync(activity.class.ActivityMessageExtension, { ofMessage: message._class })
|
||||
</script>
|
||||
|
||||
<BasePreview
|
||||
bind:this={previewElement}
|
||||
bind:isCompact
|
||||
{message}
|
||||
{text}
|
||||
{intlLabel}
|
||||
{readonly}
|
||||
@ -58,13 +54,14 @@
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<ActivityMessageActions
|
||||
{message}
|
||||
{extensions}
|
||||
{actions}
|
||||
withActionMenu={false}
|
||||
on:open={previewElement.onActionsOpened}
|
||||
on:close={previewElement.onActionsClosed}
|
||||
/>
|
||||
{#if previewElement}
|
||||
<ActivityMessageActions
|
||||
{message}
|
||||
{actions}
|
||||
withActionMenu={false}
|
||||
onOpen={previewElement.onActionsOpened}
|
||||
onClose={previewElement.onActionsClosed}
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</BasePreview>
|
||||
|
@ -43,7 +43,6 @@
|
||||
export let hideLink = false
|
||||
export let compact = false
|
||||
export let onClick: (() => void) | undefined = undefined
|
||||
export let onReply: (() => void) | undefined = undefined
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
@ -112,7 +111,6 @@
|
||||
{hoverStyles}
|
||||
showDatePreposition
|
||||
{onClick}
|
||||
{onReply}
|
||||
>
|
||||
<svelte:fragment slot="header">
|
||||
<span class="header">
|
||||
|
@ -51,7 +51,6 @@
|
||||
export let hoverStyles: 'borderedHover' | 'filledHover' = 'borderedHover'
|
||||
export let hideLink = false
|
||||
export let onClick: (() => void) | undefined = undefined
|
||||
export let onReply: (() => void) | undefined = undefined
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
@ -172,7 +171,6 @@
|
||||
{hoverStyles}
|
||||
showDatePreposition={hideLink}
|
||||
{onClick}
|
||||
{onReply}
|
||||
>
|
||||
<svelte:fragment slot="header">
|
||||
<DocUpdateMessageHeader
|
||||
|
@ -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>
|
@ -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>
|
@ -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} />
|
@ -28,6 +28,17 @@ import ActivityInfoMessagePreview from './components/activity-info-message/Activ
|
||||
|
||||
import { attributesFilter, pinnedFilter, allFilter, referencesFilter } from './activityMessagesUtils'
|
||||
import { updateReferences } from './references'
|
||||
import {
|
||||
addReactionAction,
|
||||
canPinMessage,
|
||||
canRemoveFromSaved,
|
||||
saveForLater,
|
||||
unpinMessage,
|
||||
pinMessage,
|
||||
canSaveForLater,
|
||||
canUnpinMessage,
|
||||
removeFromSaved
|
||||
} from './utils'
|
||||
|
||||
export * from './activity'
|
||||
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 ActivityMessageNotificationLabel } from './components/activity-message/ActivityMessageNotificationLabel.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 ActivityMessagesFilterPopup } from './components/FilterPopup.svelte'
|
||||
export { default as ActivityReferencePresenter } from './components/activity-reference/ActivityReferencePresenter.svelte'
|
||||
@ -70,7 +80,20 @@ export default async (): Promise<Resources> => ({
|
||||
AllFilter: allFilter,
|
||||
ReferencesFilter: referencesFilter
|
||||
},
|
||||
function: {
|
||||
CanSaveForLater: canSaveForLater,
|
||||
CanRemoveFromSaved: canRemoveFromSaved,
|
||||
CanPinMessage: canPinMessage,
|
||||
CanUnpinMessage: canUnpinMessage
|
||||
},
|
||||
backreference: {
|
||||
Update: updateReferences
|
||||
},
|
||||
actionImpl: {
|
||||
AddReaction: addReactionAction,
|
||||
SaveForLater: saveForLater,
|
||||
RemoveFromSaved: removeFromSaved,
|
||||
PinMessage: pinMessage,
|
||||
UnpinMessage: unpinMessage
|
||||
}
|
||||
})
|
||||
|
@ -20,18 +20,23 @@ import core, {
|
||||
getCurrentAccount
|
||||
} from '@hcengineering/core'
|
||||
import { type Asset, type IntlString, getResource, translate } from '@hcengineering/platform'
|
||||
import { getAttributePresenterClass } from '@hcengineering/presentation'
|
||||
import { getAttributePresenterClass, getClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
type AnyComponent,
|
||||
type AnySvelteComponent,
|
||||
ErrorPresenter,
|
||||
themeStore,
|
||||
type Location
|
||||
type Location,
|
||||
getEventPositionElement,
|
||||
closePopup,
|
||||
showPopup,
|
||||
EmojiPopup
|
||||
} from '@hcengineering/ui'
|
||||
import view, { type AttributeModel, type BuildModelKey, type BuildModelOptions } from '@hcengineering/view'
|
||||
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'
|
||||
|
||||
const valueTypes: ReadonlyArray<Ref<Class<Doc>>> = [
|
||||
@ -427,3 +432,90 @@ export async function updateDocReactions (
|
||||
export function getMessageFromLoc (loc: Location): 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 })
|
||||
}
|
||||
|
@ -157,16 +157,6 @@ export interface ActivityInfoMessage extends ActivityMessage {
|
||||
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
|
||||
*/
|
||||
@ -333,7 +323,6 @@ export default plugin(activityId, {
|
||||
ActivityInfoMessage: '' as Ref<Class<ActivityInfoMessage>>,
|
||||
ActivityMessageControl: '' as Ref<Class<ActivityMessageControl>>,
|
||||
DocUpdateMessageViewlet: '' as Ref<Class<DocUpdateMessageViewlet>>,
|
||||
ActivityMessageExtension: '' as Ref<Class<ActivityMessageExtension>>,
|
||||
ActivityMessagesFilter: '' as Ref<Class<ActivityMessagesFilter>>,
|
||||
ActivityExtension: '' as Ref<Class<ActivityExtension>>,
|
||||
Reaction: '' as Ref<Class<Reaction>>,
|
||||
@ -344,7 +333,8 @@ export default plugin(activityId, {
|
||||
icon: {
|
||||
Activity: '' as Asset,
|
||||
Emoji: '' as Asset,
|
||||
Bookmark: '' as Asset
|
||||
Bookmark: '' as Asset,
|
||||
BookmarkFilled: '' as Asset
|
||||
},
|
||||
string: {
|
||||
Activity: '' as IntlString,
|
||||
|
@ -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}
|
@ -50,7 +50,6 @@
|
||||
export let hideLink = false
|
||||
export let compact = false
|
||||
export let onClick: (() => void) | undefined = undefined
|
||||
export let onReply: (() => void) | undefined = undefined
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
@ -183,7 +182,6 @@
|
||||
{skipLabel}
|
||||
showDatePreposition={hideLink}
|
||||
{onClick}
|
||||
{onReply}
|
||||
>
|
||||
<svelte:fragment slot="header">
|
||||
<ChatMessageHeader {object} {parentObject} message={value} {viewlet} {person} {skipLabel} {hideLink} />
|
||||
|
@ -40,7 +40,6 @@
|
||||
export let attachmentImageSize: AttachmentImageSize = 'x-large'
|
||||
export let videoPreload = true
|
||||
export let onClick: (() => void) | undefined = undefined
|
||||
export let onReply: (() => void) | undefined = undefined
|
||||
|
||||
const client = getClient()
|
||||
</script>
|
||||
@ -76,6 +75,5 @@
|
||||
{videoPreload}
|
||||
showLinksPreview={false}
|
||||
{onClick}
|
||||
{onReply}
|
||||
/>
|
||||
{/if}
|
||||
|
@ -48,7 +48,6 @@ import ChannelIcon from './components/ChannelIcon.svelte'
|
||||
import ThreadNotificationPresenter from './components/notification/ThreadNotificationPresenter.svelte'
|
||||
import ChatMessageNotificationLabel from './components/notification/ChatMessageNotificationLabel.svelte'
|
||||
import ChatAside from './components/chat/ChatAside.svelte'
|
||||
import ReplyToThreadAction from './components/ReplyToThreadAction.svelte'
|
||||
import ThreadMessagePreview from './components/threads/ThreadMessagePreview.svelte'
|
||||
import ChatMessagePreview from './components/chat-message/ChatMessagePreview.svelte'
|
||||
|
||||
@ -62,7 +61,8 @@ import {
|
||||
getUnreadThreadsCount,
|
||||
canCopyMessageLink,
|
||||
leaveChannelAction,
|
||||
removeChannelAction
|
||||
removeChannelAction,
|
||||
canReplyToThread
|
||||
} from './utils'
|
||||
import {
|
||||
chunterSpaceLinkFragmentProvider,
|
||||
@ -180,7 +180,6 @@ export default async (): Promise<Resources> => ({
|
||||
ChatMessageNotificationLabel,
|
||||
ThreadNotificationPresenter,
|
||||
ChatAside,
|
||||
ReplyToThreadAction,
|
||||
ThreadMessagePreview,
|
||||
ChatMessagePreview
|
||||
},
|
||||
@ -197,8 +196,9 @@ export default async (): Promise<Resources> => ({
|
||||
GetChunterSpaceLinkFragment: chunterSpaceLinkFragmentProvider,
|
||||
GetUnreadThreadsCount: getUnreadThreadsCount,
|
||||
GetThreadLink: getThreadLink,
|
||||
GetMessageLink: getMessageLocation,
|
||||
ReplyToThread: replyToThread
|
||||
ReplyToThread: replyToThread,
|
||||
CanReplyToThread: canReplyToThread,
|
||||
GetMessageLink: getMessageLocation
|
||||
},
|
||||
actionImpl: {
|
||||
ArchiveChannel,
|
||||
@ -206,6 +206,7 @@ export default async (): Promise<Resources> => ({
|
||||
ConvertDmToPrivateChannel,
|
||||
DeleteChatMessage: deleteChatMessage,
|
||||
LeaveChannel: leaveChannelAction,
|
||||
RemoveChannel: removeChannelAction
|
||||
RemoveChannel: removeChannelAction,
|
||||
ReplyToThread: replyToThread
|
||||
}
|
||||
})
|
||||
|
@ -138,8 +138,6 @@ export default plugin(chunterId, {
|
||||
ChatMessagePresenter: '' as AnyComponent,
|
||||
ThreadMessagePresenter: '' as AnyComponent,
|
||||
ChatAside: '' as AnyComponent,
|
||||
Replies: '' as AnyComponent,
|
||||
ReplyToThreadAction: '' as AnyComponent,
|
||||
ChatMessagePreview: '' as AnyComponent,
|
||||
ThreadMessagePreview: '' as AnyComponent
|
||||
},
|
||||
|
@ -119,6 +119,7 @@
|
||||
"ToViewCommands": "to view available commands",
|
||||
"UnArchive": "Unarchive",
|
||||
"Pin": "Pin",
|
||||
"Unpin": "Unpin"
|
||||
"Unpin": "Unpin",
|
||||
"MoreActions": "More actions"
|
||||
}
|
||||
}
|
||||
|
@ -116,6 +116,7 @@
|
||||
"ToViewCommands": "чтобы увидеть команды",
|
||||
"UnArchive": "Разархивировать",
|
||||
"Pin": "Закрепить",
|
||||
"Unpin": "Открепить"
|
||||
"Unpin": "Открепить",
|
||||
"MoreActions": "Больше действий"
|
||||
}
|
||||
}
|
||||
|
@ -194,7 +194,8 @@ const view = plugin(viewId, {
|
||||
Type: '' as IntlString,
|
||||
UnArchive: '' as IntlString,
|
||||
Save: '' as IntlString,
|
||||
PublicView: '' as IntlString
|
||||
PublicView: '' as IntlString,
|
||||
MoreActions: '' as IntlString
|
||||
},
|
||||
icon: {
|
||||
Table: '' as Asset,
|
||||
|
Loading…
Reference in New Issue
Block a user