Chunter: pin messages (#1396)

Signed-off-by: budaeva <irina.budaeva@xored.com>
This commit is contained in:
budaeva 2022-04-14 15:53:33 +07:00 committed by GitHub
parent da45faf7d1
commit b6156fa52e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 261 additions and 12 deletions

View File

@ -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

View File

@ -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>

View File

@ -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"
}

View File

@ -30,6 +30,9 @@
"MarkUnread": "Отметить как непрочитанное",
"GetNewReplies": "Получать уведомления о новых ответах",
"TurnOffReplies": "Выключить уведомления об ответах",
"PinMessage": "Закрепить сообщение",
"UnpinMessage": "Открепить сообщение",
"Pinned": "Закреплено:",
"EditMessage": "Редактировать сообщение",
"DeleteMessage": "Удалить сообщение"
}

View File

@ -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>

View File

@ -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} />

View File

@ -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,
{

View File

@ -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>

View File

@ -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>

View File

@ -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,
{

View File

@ -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>

View File

@ -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
}
})

View File

@ -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
}

View File

@ -24,6 +24,7 @@ import { AnyComponent } from '@anticrm/ui'
*/
export interface Channel extends Space {
lastMessage?: Timestamp
pinned?: Ref<ChunterMessage>[]
topic?: string
}