mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-23 19:44:59 +03:00
Chunter: pin messages (#1396)
Signed-off-by: budaeva <irina.budaeva@xored.com>
This commit is contained in:
parent
da45faf7d1
commit
b6156fa52e
@ -35,6 +35,9 @@ export class TChannel extends TSpace implements Channel {
|
||||
@Prop(TypeTimestamp(), chunter.string.LastMessage)
|
||||
lastMessage?: Timestamp
|
||||
|
||||
@Prop(ArrOf(TypeRef(chunter.class.ChunterMessage)), chunter.string.PinnedMessages)
|
||||
pinned?: Ref<ChunterMessage>[]
|
||||
|
||||
@Prop(TypeString(), chunter.string.Topic)
|
||||
@Index(IndexKind.FullText)
|
||||
topic?: string
|
||||
|
@ -48,7 +48,8 @@ export default mergeIds(chunterId, chunter, {
|
||||
CreateBy: '' as IntlString,
|
||||
Create: '' as IntlString,
|
||||
MarkUnread: '' as IntlString,
|
||||
LastMessage: '' as IntlString
|
||||
LastMessage: '' as IntlString,
|
||||
PinnedMessages: '' as IntlString
|
||||
},
|
||||
viewlet: {
|
||||
Chat: '' as Ref<ViewletDescriptor>
|
||||
|
@ -31,6 +31,9 @@
|
||||
"MarkUnread": "Mark unread",
|
||||
"GetNewReplies": "Get notified about new replies",
|
||||
"TurnOffReplies": "Turn off notifications for replies",
|
||||
"PinMessage": "Pin message",
|
||||
"UnpinMessage": "Unpin message",
|
||||
"Pinned": "Pinned:",
|
||||
"EditMessage": "Edit message",
|
||||
"DeleteMessage": "Delete message"
|
||||
}
|
||||
|
@ -30,6 +30,9 @@
|
||||
"MarkUnread": "Отметить как непрочитанное",
|
||||
"GetNewReplies": "Получать уведомления о новых ответах",
|
||||
"TurnOffReplies": "Выключить уведомления об ответах",
|
||||
"PinMessage": "Закрепить сообщение",
|
||||
"UnpinMessage": "Открепить сообщение",
|
||||
"Pinned": "Закреплено:",
|
||||
"EditMessage": "Редактировать сообщение",
|
||||
"DeleteMessage": "Удалить сообщение"
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import attachment from '@anticrm/attachment'
|
||||
import type { Message } from '@anticrm/chunter'
|
||||
import type { ChunterMessage, Message } from '@anticrm/chunter'
|
||||
import contact, { Employee } from '@anticrm/contact'
|
||||
import core, { Doc, Ref, Space, WithLookup } from '@anticrm/core'
|
||||
import { NotificationClientImpl } from '@anticrm/notification-resources'
|
||||
@ -23,7 +23,9 @@
|
||||
import chunter from '../plugin'
|
||||
import ChannelSeparator from './ChannelSeparator.svelte'
|
||||
import MessageComponent from './Message.svelte'
|
||||
|
||||
export let space: Ref<Space> | undefined
|
||||
export let pinnedIds: Ref<ChunterMessage>[]
|
||||
|
||||
let div: HTMLDivElement | undefined
|
||||
let autoscroll: boolean = false
|
||||
@ -111,7 +113,7 @@
|
||||
{#if newMessagesPos === i}
|
||||
<ChannelSeparator title={chunter.string.New} line reverse isNew />
|
||||
{/if}
|
||||
<MessageComponent {message} {employees} on:openThread />
|
||||
<MessageComponent {message} {employees} on:openThread isPinned={pinnedIds.includes(message._id)} />
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -14,14 +14,15 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { AttachmentRefInput } from '@anticrm/attachment-resources'
|
||||
import { Message } from '@anticrm/chunter'
|
||||
import { ChunterMessage, Message } from '@anticrm/chunter'
|
||||
import { generateId, getCurrentAccount, Ref, Space, TxFactory } from '@anticrm/core'
|
||||
import { NotificationClientImpl } from '@anticrm/notification-resources'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import { getCurrentLocation, navigate } from '@anticrm/ui'
|
||||
import { createBacklinks } from '../backlinks'
|
||||
import chunter from '../plugin'
|
||||
import Channel from './Channel.svelte'
|
||||
import PinnedMessages from './PinnedMessages.svelte'
|
||||
|
||||
export let space: Ref<Space>
|
||||
|
||||
@ -63,13 +64,26 @@
|
||||
loc.path[3] = _id
|
||||
navigate(loc)
|
||||
}
|
||||
|
||||
const pinnedQuery = createQuery()
|
||||
let pinnedIds: Ref<ChunterMessage>[] = []
|
||||
pinnedQuery.query(
|
||||
chunter.class.Channel,
|
||||
{ _id: space },
|
||||
(res) => {
|
||||
pinnedIds = res[0]?.pinned ?? []
|
||||
},
|
||||
{ limit: 1 }
|
||||
)
|
||||
</script>
|
||||
|
||||
<PinnedMessages {space} {pinnedIds} />
|
||||
<Channel
|
||||
{space}
|
||||
on:openThread={(e) => {
|
||||
openThread(e.detail)
|
||||
}}
|
||||
{pinnedIds}
|
||||
/>
|
||||
<div class="reference">
|
||||
<AttachmentRefInput {space} {_class} objectId={_id} on:message={onMessage} />
|
||||
|
@ -25,6 +25,7 @@
|
||||
import { Action } from '@anticrm/view'
|
||||
import { getActions } from '@anticrm/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { UnpinMessage } from '../index';
|
||||
import chunter from '../plugin'
|
||||
import { getTime } from '../utils'
|
||||
// import Share from './icons/Share.svelte'
|
||||
@ -37,6 +38,7 @@
|
||||
export let message: WithLookup<Message>
|
||||
export let employees: Map<Ref<Employee>, Employee>
|
||||
export let thread: boolean = false
|
||||
export let isPinned: boolean = false
|
||||
|
||||
$: employee = getEmployee(message)
|
||||
$: attachments = (message.$lookup?.attachments ?? []) as Attachment[]
|
||||
@ -59,6 +61,16 @@
|
||||
action: chunter.actionImpl.SubscribeMessage
|
||||
} as Action)
|
||||
|
||||
$: pinActions = isPinned
|
||||
? ({
|
||||
label: chunter.string.UnpinMessage,
|
||||
action: chunter.actionImpl.UnpinMessage
|
||||
} as Action)
|
||||
: ({
|
||||
label: chunter.string.PinMessage,
|
||||
action: chunter.actionImpl.PinMessage
|
||||
} as Action)
|
||||
|
||||
$: isEditing = false;
|
||||
|
||||
const editAction = {
|
||||
@ -68,12 +80,20 @@
|
||||
|
||||
const deleteAction = {
|
||||
label: chunter.string.DeleteMessage,
|
||||
action: async () => await client.remove(message)
|
||||
action: async () => {
|
||||
(await client.findAll(chunter.class.ThreadMessage, {attachedTo: message._id})).forEach(c => {
|
||||
UnpinMessage(c)
|
||||
})
|
||||
UnpinMessage(message)
|
||||
await client.remove(message)
|
||||
}
|
||||
}
|
||||
|
||||
const showMenu = async (ev: Event): Promise<void> => {
|
||||
const actions = await getActions(client, message, chunter.class.Message)
|
||||
actions.push(subscribeAction)
|
||||
actions.push(pinActions)
|
||||
|
||||
showPopup(
|
||||
Menu,
|
||||
{
|
||||
|
@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
import { ChunterMessage } from '@anticrm/chunter'
|
||||
import { Ref, Space } from '@anticrm/core'
|
||||
import { Label, showPopup } from '@anticrm/ui'
|
||||
import PinnedMessagesPopup from './PinnedMessagesPopup.svelte'
|
||||
import chunter from '../plugin'
|
||||
|
||||
export let space: Ref<Space>
|
||||
export let pinnedIds: Ref<ChunterMessage>[]
|
||||
|
||||
function showMessages (ev: MouseEvent & { currentTarget: EventTarget & HTMLDivElement }) {
|
||||
showPopup(PinnedMessagesPopup, { space }, ev.target)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if pinnedIds.length > 0}
|
||||
<div class="bottom-divider over-underline pt-2 pb-2 container">
|
||||
<div on:click={(ev) => showMessages(ev)}>
|
||||
<Label label={chunter.string.Pinned} />
|
||||
{pinnedIds.length}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
padding-left: 2.5rem;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,116 @@
|
||||
<script lang="ts">
|
||||
import chunter, { ChunterMessage } from '@anticrm/chunter'
|
||||
import contact, { Employee, EmployeeAccount, formatName } from '@anticrm/contact'
|
||||
import { Ref, Space } from '@anticrm/core'
|
||||
import { Avatar, createQuery, getClient, MessageViewer } from '@anticrm/presentation'
|
||||
import { IconClose } from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { UnpinMessage } from '../index'
|
||||
import { getTime } from '../utils'
|
||||
|
||||
export let space: Ref<Space>
|
||||
|
||||
const pinnedQuery = createQuery()
|
||||
let pinnedIds: Ref<ChunterMessage>[] = []
|
||||
pinnedQuery.query(
|
||||
chunter.class.Channel,
|
||||
{ _id: space },
|
||||
(res) => {
|
||||
pinnedIds = res[0]?.pinned ?? []
|
||||
},
|
||||
{ limit: 1 }
|
||||
)
|
||||
|
||||
const messagesQuery = createQuery()
|
||||
let pinnedMessages: ChunterMessage[] = []
|
||||
|
||||
$: pinnedIds &&
|
||||
messagesQuery.query(chunter.class.ChunterMessage, { _id: { $in: pinnedIds } }, (res) => {
|
||||
pinnedMessages = res
|
||||
})
|
||||
|
||||
const employeeAccoutsQuery = createQuery()
|
||||
let employeeAcounts: EmployeeAccount[]
|
||||
|
||||
employeeAccoutsQuery.query(contact.class.EmployeeAccount, {}, (res) => (employeeAcounts = res))
|
||||
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function getAvatar (_id?: Ref<Employee>): Promise<string | undefined | null> {
|
||||
if (_id === undefined) return (await client.findOne(contact.class.Employee, { _id }))?.avatar
|
||||
}
|
||||
|
||||
function getEmployee (message: ChunterMessage): EmployeeAccount | undefined {
|
||||
return employeeAcounts?.find((e) => e._id === message.createBy)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="antiPopup vScroll popup">
|
||||
{#each pinnedMessages as message}
|
||||
<div class="message">
|
||||
<div class="header">
|
||||
{#await getEmployee(message) then employeeAccount}
|
||||
{#await getAvatar(employeeAccount?.employee) then avatar}
|
||||
<div class="avatar">
|
||||
<Avatar size={'medium'} {avatar} />
|
||||
</div>
|
||||
{/await}
|
||||
<span class="name">
|
||||
{formatName(employeeAccount?.name ?? '')}
|
||||
</span>
|
||||
{/await}
|
||||
<div
|
||||
class="cross"
|
||||
on:click={async () => {
|
||||
if (pinnedIds.length === 1)
|
||||
dispatch('close')
|
||||
UnpinMessage(message)
|
||||
}}
|
||||
>
|
||||
<IconClose size="small" />
|
||||
</div>
|
||||
</div>
|
||||
<MessageViewer message={message.content} />
|
||||
<span class="time">{getTime(message.createOn)}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.popup {
|
||||
padding: 1.25rem 1.25rem 1.25rem;
|
||||
max-height: 20rem;
|
||||
color: var(--theme-caption-color);
|
||||
}
|
||||
|
||||
.message {
|
||||
padding: 0.75rem 1rem 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
box-shadow: inherit;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.name {
|
||||
font-weight: 500;
|
||||
margin-left: 1rem;
|
||||
flex-grow: 2;
|
||||
}
|
||||
|
||||
.cross {
|
||||
opacity: 0.4;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.time {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
</style>
|
@ -24,6 +24,7 @@
|
||||
import { ActionIcon, IconMoreH, Menu, showPopup } from '@anticrm/ui'
|
||||
import { Action } from '@anticrm/view'
|
||||
import { getActions } from '@anticrm/view-resources'
|
||||
import { UnpinMessage } from '../index';
|
||||
import chunter from '../plugin'
|
||||
import { getTime } from '../utils'
|
||||
// import Share from './icons/Share.svelte'
|
||||
@ -33,6 +34,7 @@
|
||||
|
||||
export let message: WithLookup<ThreadMessage>
|
||||
export let employees: Map<Ref<Employee>, Employee>
|
||||
export let isPinned: boolean = false
|
||||
|
||||
$: attachments = (message.$lookup?.attachments ?? []) as Attachment[]
|
||||
|
||||
@ -53,6 +55,16 @@
|
||||
action: chunter.actionImpl.SubscribeComment
|
||||
} as Action)
|
||||
|
||||
$: pinActions = isPinned
|
||||
? ({
|
||||
label: chunter.string.UnpinMessage,
|
||||
action: chunter.actionImpl.UnpinMessage
|
||||
} as Action)
|
||||
: ({
|
||||
label: chunter.string.PinMessage,
|
||||
action: chunter.actionImpl.PinMessage
|
||||
} as Action)
|
||||
|
||||
$: isEditing = false;
|
||||
|
||||
const editAction = {
|
||||
@ -62,12 +74,16 @@
|
||||
|
||||
const deleteAction = {
|
||||
label: chunter.string.DeleteMessage,
|
||||
action: async () => await client.removeDoc(message._class, message.space, message._id)
|
||||
action: async () => {
|
||||
await client.removeDoc(message._class, message.space, message._id)
|
||||
UnpinMessage(message)
|
||||
}
|
||||
}
|
||||
|
||||
const showMenu = async (ev: Event): Promise<void> => {
|
||||
const actions = await getActions(client, message, chunter.class.ThreadMessage)
|
||||
actions.push(subscribeAction)
|
||||
actions.push(pinActions)
|
||||
showPopup(
|
||||
Menu,
|
||||
{
|
||||
|
@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import attachment from '@anticrm/attachment'
|
||||
import { AttachmentRefInput } from '@anticrm/attachment-resources'
|
||||
import type { ThreadMessage, Message } from '@anticrm/chunter'
|
||||
import type { ThreadMessage, Message, ChunterMessage, Channel } from '@anticrm/chunter'
|
||||
import contact, { Employee } from '@anticrm/contact'
|
||||
import core, { Doc, generateId, getCurrentAccount, Ref, Space, TxFactory } from '@anticrm/core'
|
||||
import { NotificationClientImpl } from '@anticrm/notification-resources'
|
||||
@ -57,6 +57,9 @@
|
||||
createBy: core.class.Account
|
||||
}
|
||||
|
||||
const pinnedQuery = createQuery()
|
||||
let pinnedIds: Ref<ChunterMessage>[] = []
|
||||
|
||||
$: updateQueries(_id)
|
||||
|
||||
function updateQueries (id: Ref<Message>) {
|
||||
@ -96,6 +99,15 @@
|
||||
lookup
|
||||
}
|
||||
)
|
||||
|
||||
pinnedQuery.query(
|
||||
chunter.class.Channel,
|
||||
{ _id: currentSpace },
|
||||
(res) => {
|
||||
pinnedIds = res[0]?.pinned ?? []
|
||||
},
|
||||
{ limit: 1 }
|
||||
)
|
||||
}
|
||||
|
||||
let employees: Map<Ref<Employee>, Employee> = new Map<Ref<Employee>, Employee>()
|
||||
@ -182,7 +194,11 @@
|
||||
{#if newMessagesPos === i}
|
||||
<ChannelSeparator title={chunter.string.New} line reverse isNew />
|
||||
{/if}
|
||||
<ThreadComment message={comment} {employees} />
|
||||
<ThreadComment
|
||||
message={comment}
|
||||
{employees}
|
||||
isPinned={pinnedIds.includes(comment._id)}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -13,9 +13,11 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import chunter, { Message, ThreadMessage } from '@anticrm/chunter'
|
||||
import core from '@anticrm/core'
|
||||
import chunter, { Channel, ChunterMessage, Message, ThreadMessage } from '@anticrm/chunter'
|
||||
import { NotificationClientImpl } from '@anticrm/notification-resources'
|
||||
import { Resources } from '@anticrm/platform'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import TxBacklinkCreate from './components/activity/TxBacklinkCreate.svelte'
|
||||
import TxBacklinkReference from './components/activity/TxBacklinkReference.svelte'
|
||||
import TxCommentCreate from './components/activity/TxCommentCreate.svelte'
|
||||
@ -62,6 +64,22 @@ async function UnsubscribeComment (object: ThreadMessage): Promise<void> {
|
||||
await client.unsubscribe(object.attachedTo)
|
||||
}
|
||||
|
||||
async function PinMessage (message: ChunterMessage): Promise<void> {
|
||||
const client = getClient()
|
||||
|
||||
await client.updateDoc<Channel>(chunter.class.Channel, core.space.Space, message.space, {
|
||||
$push: { pinned: message._id }
|
||||
})
|
||||
}
|
||||
|
||||
export async function UnpinMessage (message: ChunterMessage): Promise<void> {
|
||||
const client = getClient()
|
||||
|
||||
await client.updateDoc<Channel>(chunter.class.Channel, core.space.Space, message.space, {
|
||||
$pull: { pinned: message._id }
|
||||
})
|
||||
}
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
CommentInput,
|
||||
@ -85,6 +103,8 @@ export default async (): Promise<Resources> => ({
|
||||
SubscribeMessage,
|
||||
SubscribeComment,
|
||||
UnsubscribeMessage,
|
||||
UnsubscribeComment
|
||||
UnsubscribeComment,
|
||||
PinMessage,
|
||||
UnpinMessage
|
||||
}
|
||||
})
|
||||
|
@ -30,7 +30,9 @@ export default mergeIds(chunterId, chunter, {
|
||||
SubscribeMessage: '' as Resource<(object: Doc) => Promise<void>>,
|
||||
SubscribeComment: '' as Resource<(object: Doc) => Promise<void>>,
|
||||
UnsubscribeMessage: '' as Resource<(object: Doc) => Promise<void>>,
|
||||
UnsubscribeComment: '' as Resource<(object: Doc) => Promise<void>>
|
||||
UnsubscribeComment: '' as Resource<(object: Doc) => Promise<void>>,
|
||||
PinMessage: '' as Resource<(object: Doc) => Promise<void>>,
|
||||
UnpinMessage: '' as Resource<(object: Doc) => Promise<void>>
|
||||
},
|
||||
string: {
|
||||
Channel: '' as IntlString,
|
||||
@ -51,6 +53,9 @@ export default mergeIds(chunterId, chunter, {
|
||||
New: '' as IntlString,
|
||||
GetNewReplies: '' as IntlString,
|
||||
TurnOffReplies: '' as IntlString,
|
||||
PinMessage: '' as IntlString,
|
||||
UnpinMessage: '' as IntlString,
|
||||
Pinned: '' as IntlString,
|
||||
DeleteMessage: '' as IntlString,
|
||||
EditMessage: '' as IntlString
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import { AnyComponent } from '@anticrm/ui'
|
||||
*/
|
||||
export interface Channel extends Space {
|
||||
lastMessage?: Timestamp
|
||||
pinned?: Ref<ChunterMessage>[]
|
||||
topic?: string
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user