From 2efc19044edcb86715f69e3c746a1efb21a23e2f Mon Sep 17 00:00:00 2001 From: Denis Bunakalya Date: Fri, 22 Apr 2022 06:13:15 +0300 Subject: [PATCH] Chunter: Direct messages (#1472) Signed-off-by: Denis Bunakalya --- models/chunter/src/index.ts | 71 +++++++++++++++---- models/chunter/src/plugin.ts | 1 + models/view/src/index.ts | 68 ++++++++++++++---- models/view/src/migration.ts | 7 +- plugins/chunter-assets/lang/en.json | 5 ++ plugins/chunter-assets/lang/ru.json | 5 ++ .../src/components/Channel.svelte | 2 +- .../src/components/ChannelPresenter.svelte | 18 ++--- .../src/components/ChannelView.svelte | 8 +-- .../src/components/CreateDirectMessage.svelte | 57 +++++++++++++++ .../src/components/DmHeader.svelte | 46 ++++++++++++ .../src/components/DmPresenter.svelte | 40 +++++++++++ .../src/components/EditChannel.svelte | 16 +++-- .../EditChannelDescriptionAttachments.svelte | 4 +- .../EditChannelDescriptionTab.svelte | 15 +++- .../src/components/PinnedMessagesPopup.svelte | 2 +- .../src/components/Thread.svelte | 15 ++-- .../src/components/ThreadView.svelte | 4 +- plugins/chunter-resources/src/index.ts | 19 +++-- plugins/chunter-resources/src/plugin.ts | 13 +++- plugins/chunter-resources/src/utils.ts | 32 ++++++++- plugins/chunter/src/index.ts | 17 ++++- plugins/view/src/index.ts | 8 +++ .../src/components/Navigator.svelte | 5 +- .../src/components/navigator/SpacesNav.svelte | 40 +++++++---- server-plugins/chunter-resources/src/index.ts | 16 ++--- server-plugins/chunter/src/index.ts | 12 +++- 27 files changed, 441 insertions(+), 105 deletions(-) create mode 100644 plugins/chunter-resources/src/components/CreateDirectMessage.svelte create mode 100644 plugins/chunter-resources/src/components/DmHeader.svelte create mode 100644 plugins/chunter-resources/src/components/DmPresenter.svelte diff --git a/models/chunter/src/index.ts b/models/chunter/src/index.ts index 0516087a78..1bb080d891 100644 --- a/models/chunter/src/index.ts +++ b/models/chunter/src/index.ts @@ -16,12 +16,14 @@ import activity from '@anticrm/activity' import type { Backlink, + ChunterSpace, Channel, ChunterMessage, Comment, Message, SavedMessages, - ThreadMessage + ThreadMessage, + DirectMessage } from '@anticrm/chunter' import contact, { Employee } from '@anticrm/contact' import type { Account, Class, Doc, Domain, Ref, Space, Timestamp } from '@anticrm/core' @@ -50,20 +52,27 @@ import preference, { TPreference } from '@anticrm/model-preference' export const DOMAIN_CHUNTER = 'chunter' as Domain export const DOMAIN_COMMENT = 'comment' as Domain -@Model(chunter.class.Channel, core.class.Space) -@UX(chunter.string.Channel, chunter.icon.Hashtag) -export class TChannel extends TSpace implements Channel { +@Model(chunter.class.ChunterSpace, core.class.Space) +export class TChunterSpace extends TSpace implements ChunterSpace { @Prop(TypeTimestamp(), chunter.string.LastMessage) lastMessage?: Timestamp @Prop(ArrOf(TypeRef(chunter.class.ChunterMessage)), chunter.string.PinnedMessages) pinned?: Ref[] +} +@Model(chunter.class.Channel, chunter.class.ChunterSpace) +@UX(chunter.string.Channel, chunter.icon.Hashtag) +export class TChannel extends TChunterSpace implements Channel { @Prop(TypeString(), chunter.string.Topic) @Index(IndexKind.FullText) topic?: string } +@Model(chunter.class.DirectMessage, chunter.class.ChunterSpace) +@UX(chunter.string.DirectMessage, contact.icon.Person) +export class TDirectMessage extends TChunterSpace implements DirectMessage {} + @Model(chunter.class.ChunterMessage, core.class.AttachedDoc, DOMAIN_CHUNTER) export class TChunterMessage extends TAttachedDoc implements ChunterMessage { @Prop(TypeMarkup(), chunter.string.Content) @@ -128,23 +137,49 @@ export class TSavedMessages extends TPreference implements SavedMessages { } export function createModel (builder: Builder): void { - builder.createModel(TChannel, TMessage, TThreadMessage, TChunterMessage, TComment, TBacklink, TSavedMessages) - builder.mixin(chunter.class.Channel, core.class.Class, workbench.mixin.SpaceView, { - view: { - class: chunter.class.Message - } + builder.createModel( + TChunterSpace, + TChannel, + TMessage, + TThreadMessage, + TChunterMessage, + TComment, + TBacklink, + TDirectMessage, + TSavedMessages + ) + const spaceClasses = [chunter.class.Channel, chunter.class.DirectMessage] + + spaceClasses.forEach((spaceClass) => { + builder.mixin(spaceClass, core.class.Class, workbench.mixin.SpaceView, { + view: { + class: chunter.class.Message + } + }) + + builder.mixin(spaceClass, core.class.Class, notification.mixin.SpaceLastEdit, { + lastEditField: 'lastMessage' + }) + + builder.mixin(spaceClass, core.class.Class, view.mixin.ObjectEditor, { + editor: chunter.component.EditChannel + }) + }) + + builder.mixin(chunter.class.DirectMessage, core.class.Class, view.mixin.SpaceName, { + getName: chunter.function.GetDmName + }) + + builder.mixin(chunter.class.DirectMessage, core.class.Class, view.mixin.AttributePresenter, { + presenter: chunter.component.DmPresenter }) builder.mixin(chunter.class.Channel, core.class.Class, view.mixin.AttributePresenter, { presenter: chunter.component.ChannelPresenter }) - builder.mixin(chunter.class.Channel, core.class.Class, notification.mixin.SpaceLastEdit, { - lastEditField: 'lastMessage' - }) - - builder.mixin(chunter.class.Channel, core.class.Class, view.mixin.ObjectEditor, { - editor: chunter.component.EditChannel + builder.mixin(chunter.class.DirectMessage, core.class.Class, view.mixin.SpaceHeader, { + header: chunter.component.DmHeader }) builder.mixin(chunter.class.Channel, core.class.Class, view.mixin.SpaceHeader, { @@ -302,6 +337,12 @@ export function createModel (builder: Builder): void { spaceClass: chunter.class.Channel, addSpaceLabel: chunter.string.CreateChannel, createComponent: chunter.component.CreateChannel + }, + { + label: chunter.string.DirectMessages, + spaceClass: chunter.class.DirectMessage, + addSpaceLabel: chunter.string.NewDirectMessage, + createComponent: chunter.component.CreateDirectMessage } ], aside: chunter.component.ThreadView diff --git a/models/chunter/src/plugin.ts b/models/chunter/src/plugin.ts index 1407bee569..68dcceda5f 100644 --- a/models/chunter/src/plugin.ts +++ b/models/chunter/src/plugin.ts @@ -26,6 +26,7 @@ export default mergeIds(chunterId, chunter, { component: { CommentPresenter: '' as AnyComponent, ChannelPresenter: '' as AnyComponent, + DmPresenter: '' as AnyComponent, Threads: '' as AnyComponent, ThreadView: '' as AnyComponent, SavedMessages: '' as AnyComponent diff --git a/models/view/src/index.ts b/models/view/src/index.ts index eb0c87104c..4dce8a40ec 100644 --- a/models/view/src/index.ts +++ b/models/view/src/index.ts @@ -20,7 +20,8 @@ import core, { TClass, TDoc } from '@anticrm/model-core' import type { Asset, IntlString, Resource, Status } from '@anticrm/platform' import type { AnyComponent } from '@anticrm/ui' import type { - Action, ActionTarget, + Action, + ActionTarget, AttributeEditor, AttributePresenter, HTMLPresenter, @@ -32,6 +33,7 @@ import type { ObjectValidator, PreviewPresenter, SpaceHeader, + SpaceName, TextPresenter, ViewAction, ViewContext, @@ -67,7 +69,12 @@ export function createAction ( ) } -export function actionTarget (builder: Builder, action: Ref, target: Ref>, context: ViewContext): void { +export function actionTarget ( + builder: Builder, + action: Ref, + target: Ref>, + context: ViewContext +): void { builder.createDoc(view.class.ActionTarget, core.space.Model, { target, action, @@ -116,6 +123,11 @@ export class TSpaceHeader extends TClass implements SpaceHeader { header!: AnyComponent } +@Mixin(view.mixin.SpaceName, core.class.Class) +export class TSpaceName extends TClass implements SpaceName { + getName!: Resource<(client: Client, space: Space) => Promise> +} + @Mixin(view.mixin.ObjectValidator, core.class.Class) export class TObjectValidator extends TClass implements ObjectValidator { validator!: Resource<(doc: T, client: Client) => Promise>> @@ -189,6 +201,7 @@ export function createModel (builder: Builder): void { TObjectEditorHeader, THTMLPresenter, TSpaceHeader, + TSpaceName, TTextPresenter, TIgnoreActions, TPreviewPresenter @@ -214,20 +227,34 @@ export function createModel (builder: Builder): void { view.viewlet.Table ) - createAction(builder, view.action.Delete, view.string.Delete, view.actionImpl.Delete, { icon: view.icon.Delete, keyBinding: ['Meta + Backspace'] }) + createAction(builder, view.action.Delete, view.string.Delete, view.actionImpl.Delete, { + icon: view.icon.Delete, + keyBinding: ['Meta + Backspace'] + }) actionTarget(builder, view.action.Delete, core.class.Doc, { mode: ['context', 'browser'], group: 'tools' }) - createAction(builder, view.action.Move, view.string.Move, view.actionImpl.Move, { icon: view.icon.Move, singleInput: true }) + createAction(builder, view.action.Move, view.string.Move, view.actionImpl.Move, { + icon: view.icon.Move, + singleInput: true + }) // Keyboard actions. - createAction(builder, view.action.MoveUp, view.string.MoveUp, view.actionImpl.MoveUp, { keyBinding: ['ArrowUp', 'keyK'] }) + createAction(builder, view.action.MoveUp, view.string.MoveUp, view.actionImpl.MoveUp, { + keyBinding: ['ArrowUp', 'keyK'] + }) actionTarget(builder, view.action.MoveUp, core.class.Doc, { mode: 'browser' }) - createAction(builder, view.action.MoveDown, view.string.MoveDown, view.actionImpl.MoveDown, { keyBinding: ['ArrowDown', 'keyJ'] }) + createAction(builder, view.action.MoveDown, view.string.MoveDown, view.actionImpl.MoveDown, { + keyBinding: ['ArrowDown', 'keyJ'] + }) actionTarget(builder, view.action.MoveDown, core.class.Doc, { mode: 'browser' }) - createAction(builder, view.action.MoveLeft, view.string.MoveLeft, view.actionImpl.MoveLeft, { keyBinding: ['ArrowLeft'] }) + createAction(builder, view.action.MoveLeft, view.string.MoveLeft, view.actionImpl.MoveLeft, { + keyBinding: ['ArrowLeft'] + }) actionTarget(builder, view.action.MoveLeft, core.class.Doc, { mode: 'browser' }) - createAction(builder, view.action.MoveRight, view.string.MoveRight, view.actionImpl.MoveRight, { keyBinding: ['ArrowRight'] }) + createAction(builder, view.action.MoveRight, view.string.MoveRight, view.actionImpl.MoveRight, { + keyBinding: ['ArrowRight'] + }) actionTarget(builder, view.action.MoveRight, core.class.Doc, { mode: 'browser' }) builder.mixin(core.class.Space, core.class.Class, view.mixin.AttributePresenter, { @@ -235,19 +262,32 @@ export function createModel (builder: Builder): void { }) // Selection stuff - createAction(builder, view.action.SelectItem, view.string.SelectItem, view.actionImpl.SelectItem, { keyBinding: ['keyX'] }) + createAction(builder, view.action.SelectItem, view.string.SelectItem, view.actionImpl.SelectItem, { + keyBinding: ['keyX'] + }) actionTarget(builder, view.action.SelectItem, core.class.Doc, { mode: 'browser' }) - createAction(builder, view.action.SelectItemAll, view.string.SelectItemAll, view.actionImpl.SelectItemAll, { keyBinding: ['meta + keyA'] }) + createAction(builder, view.action.SelectItemAll, view.string.SelectItemAll, view.actionImpl.SelectItemAll, { + keyBinding: ['meta + keyA'] + }) actionTarget(builder, view.action.SelectItemAll, core.class.Doc, { mode: 'browser' }) - createAction(builder, view.action.SelectItemNone, view.string.SelectItemNone, view.actionImpl.SelectItemNone, { keyBinding: ['escape'] }) + createAction(builder, view.action.SelectItemNone, view.string.SelectItemNone, view.actionImpl.SelectItemNone, { + keyBinding: ['escape'] + }) actionTarget(builder, view.action.SelectItemNone, core.class.Doc, { mode: 'browser' }) - createAction(builder, view.action.ShowActions, view.string.ShowActions, view.actionImpl.ShowActions, { keyBinding: ['meta + keyk'] }) - actionTarget(builder, view.action.ShowActions, core.class.Doc, { mode: ['workbench', 'browser', 'popup', 'panel', 'editor'] }) + createAction(builder, view.action.ShowActions, view.string.ShowActions, view.actionImpl.ShowActions, { + keyBinding: ['meta + keyk'] + }) + actionTarget(builder, view.action.ShowActions, core.class.Doc, { + mode: ['workbench', 'browser', 'popup', 'panel', 'editor'] + }) - createAction(builder, view.action.ShowPreview, view.string.ShowPreview, view.actionImpl.ShowPreview, { keyBinding: ['Space'], singleInput: true }) + createAction(builder, view.action.ShowPreview, view.string.ShowPreview, view.actionImpl.ShowPreview, { + keyBinding: ['Space'], + singleInput: true + }) actionTarget(builder, view.action.ShowPreview, core.class.Doc, { mode: 'browser' }) } diff --git a/models/view/src/migration.ts b/models/view/src/migration.ts index a2032b3a70..63487bf549 100644 --- a/models/view/src/migration.ts +++ b/models/view/src/migration.ts @@ -16,9 +16,6 @@ import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model' export const viewOperation: MigrateOperation = { - async migrate (client: MigrationClient): Promise { - - }, - async upgrade (client: MigrationUpgradeClient): Promise { - } + async migrate (client: MigrationClient): Promise {}, + async upgrade (client: MigrationUpgradeClient): Promise {} } diff --git a/plugins/chunter-assets/lang/en.json b/plugins/chunter-assets/lang/en.json index 25c87b7565..fdd967c1ca 100644 --- a/plugins/chunter-assets/lang/en.json +++ b/plugins/chunter-assets/lang/en.json @@ -3,17 +3,22 @@ "ApplicationLabelChunter": "Chat", "LeftComment": "left a comment", "Channels": "Channels", + "DirectMessages": "Direct messages", "CreateChannel": "New Channel", + "NewDirectMessage": "New Direct Message", "ChannelName": "Name", "ChannelNamePlaceholder": "Channel", "ChannelDescription": "Description", "MakePrivate": "Make private", "MakePrivateDescription": "Only members can see it", "Channel": "Channel", + "DirectMessage": "Direct message", "EditUpdate": "Save...", "EditCancel": "Cancel", "Comments" : "Comments", + "About": "About", "Members": "Members", + "NoMembers": "No members", "MentionedIn": "mentioned this ", "ContactInfo": "Contact Info", "Content": "Content", diff --git a/plugins/chunter-assets/lang/ru.json b/plugins/chunter-assets/lang/ru.json index cc23b0bdbf..1d62f7d1e0 100644 --- a/plugins/chunter-assets/lang/ru.json +++ b/plugins/chunter-assets/lang/ru.json @@ -3,17 +3,22 @@ "ApplicationLabelChunter": "Чат", "LeftComment": "оставил(а) комментарий", "Channels": "Каналы", + "DirectMessages": "Личные сообщения", "CreateChannel": "Создать канал", + "NewDirectMessage": "Создать личное сообщение", "ChannelName": "Название", "ChannelNamePlaceholder": "Канал", "ChannelDescription": "Описание", "MakePrivate": "Сделать личным", "MakePrivateDescription": "Только пользователи могут видеть это", "Channel": "Канал ", + "DirectMessage": "Личное сообщение", "EditUpdate": "Сохранить...", "EditCancel": "Отменить", "Comments" : "Комментарии", + "About": "Информация", "Members": "Участники", + "NoMembers": "Нет участников", "MentionedIn": "упомянул(а) ", "ContactInfo": "Контактная информация", "Content": "Содержимое", diff --git a/plugins/chunter-resources/src/components/Channel.svelte b/plugins/chunter-resources/src/components/Channel.svelte index e0b90cb04c..65da2c84df 100644 --- a/plugins/chunter-resources/src/components/Channel.svelte +++ b/plugins/chunter-resources/src/components/Channel.svelte @@ -74,7 +74,7 @@ (res) => { messages = res newMessagesPos = newMessagesStart(messages) - notificationClient.updateLastView(space, chunter.class.Channel) + notificationClient.updateLastView(space, chunter.class.ChunterSpace) }, { lookup: { diff --git a/plugins/chunter-resources/src/components/ChannelPresenter.svelte b/plugins/chunter-resources/src/components/ChannelPresenter.svelte index d959806c86..47d4353a9a 100644 --- a/plugins/chunter-resources/src/components/ChannelPresenter.svelte +++ b/plugins/chunter-resources/src/components/ChannelPresenter.svelte @@ -14,26 +14,16 @@ --> {#if value} diff --git a/plugins/chunter-resources/src/components/ChannelView.svelte b/plugins/chunter-resources/src/components/ChannelView.svelte index 4663758ea3..0dd3c3784a 100644 --- a/plugins/chunter-resources/src/components/ChannelView.svelte +++ b/plugins/chunter-resources/src/components/ChannelView.svelte @@ -40,7 +40,7 @@ space, { attachedTo: space, - attachedToClass: chunter.class.Channel, + attachedToClass: chunter.class.ChunterSpace, collection: 'messages', content: message, createOn: 0, @@ -50,11 +50,11 @@ _id ) tx.attributes.createOn = tx.modifiedOn - await notificationClient.updateLastView(space, chunter.class.Channel, tx.modifiedOn, true) + await notificationClient.updateLastView(space, chunter.class.ChunterSpace, tx.modifiedOn, true) await client.tx(tx) // Create an backlink to document - await createBacklinks(client, space, chunter.class.Channel, _id, message) + await createBacklinks(client, space, chunter.class.ChunterSpace, _id, message) _id = generateId() } @@ -68,7 +68,7 @@ const pinnedQuery = createQuery() let pinnedIds: Ref[] = [] pinnedQuery.query( - chunter.class.Channel, + chunter.class.ChunterSpace, { _id: space }, (res) => { pinnedIds = res[0]?.pinned ?? [] diff --git a/plugins/chunter-resources/src/components/CreateDirectMessage.svelte b/plugins/chunter-resources/src/components/CreateDirectMessage.svelte new file mode 100644 index 0000000000..28db233cae --- /dev/null +++ b/plugins/chunter-resources/src/components/CreateDirectMessage.svelte @@ -0,0 +1,57 @@ + + + + 0} + on:close={() => { + dispatch('close') + }} +> + (employeeIds = evt.detail)} + /> + diff --git a/plugins/chunter-resources/src/components/DmHeader.svelte b/plugins/chunter-resources/src/components/DmHeader.svelte new file mode 100644 index 0000000000..bf1ff63e8a --- /dev/null +++ b/plugins/chunter-resources/src/components/DmHeader.svelte @@ -0,0 +1,46 @@ + + + +
+ {#if dm} + {#await getDmName(client, dm) then name} +
+ {/await} + {/if} +
diff --git a/plugins/chunter-resources/src/components/DmPresenter.svelte b/plugins/chunter-resources/src/components/DmPresenter.svelte new file mode 100644 index 0000000000..c40b6d89e1 --- /dev/null +++ b/plugins/chunter-resources/src/components/DmPresenter.svelte @@ -0,0 +1,40 @@ + + + +{#if dm} + {#await getDmName(client, dm) then name} + +
+ {#if icon} + + {/if} +
+ {name} +
+ {/await} +{/if} diff --git a/plugins/chunter-resources/src/components/EditChannel.svelte b/plugins/chunter-resources/src/components/EditChannel.svelte index bc535d2091..32a4d13f70 100644 --- a/plugins/chunter-resources/src/components/EditChannel.svelte +++ b/plugins/chunter-resources/src/components/EditChannel.svelte @@ -14,7 +14,7 @@ // limitations under the License. --> diff --git a/plugins/chunter-resources/src/components/EditChannelDescriptionAttachments.svelte b/plugins/chunter-resources/src/components/EditChannelDescriptionAttachments.svelte index 8cc386b32b..3d558f0f91 100644 --- a/plugins/chunter-resources/src/components/EditChannelDescriptionAttachments.svelte +++ b/plugins/chunter-resources/src/components/EditChannelDescriptionAttachments.svelte @@ -16,13 +16,13 @@
{#if parent} {#await getChannel(parent.space) then channel} - {#if channel} + {#if channel?._class === chunter.class.Channel} + {:else if channel} + {/if} {/await} {#await getParticipants(comments, parent, employees) then participants} @@ -208,7 +211,7 @@ overflow: hidden; margin: 1rem 1rem 0px; background-color: var(--theme-border-modal); - border-radius: .75rem; + border-radius: 0.75rem; border: 1px solid var(--theme-zone-border); } diff --git a/plugins/chunter-resources/src/components/ThreadView.svelte b/plugins/chunter-resources/src/components/ThreadView.svelte index 07f2f7dea4..1a12ff5ae5 100644 --- a/plugins/chunter-resources/src/components/ThreadView.svelte +++ b/plugins/chunter-resources/src/components/ThreadView.svelte @@ -100,7 +100,7 @@ ) pinnedQuery.query( - chunter.class.Channel, + chunter.class.ChunterSpace, { _id: currentSpace }, (res) => { pinnedIds = res[0]?.pinned ?? [] @@ -153,7 +153,7 @@ await client.tx(tx) // Create an backlink to document - await createBacklinks(client, currentSpace, chunter.class.Channel, commentId, message) + await createBacklinks(client, currentSpace, chunter.class.ChunterSpace, commentId, message) commentId = generateId() } diff --git a/plugins/chunter-resources/src/index.ts b/plugins/chunter-resources/src/index.ts index 772bf3be41..6a877429a7 100644 --- a/plugins/chunter-resources/src/index.ts +++ b/plugins/chunter-resources/src/index.ts @@ -14,7 +14,7 @@ // import core from '@anticrm/core' -import chunter, { Channel, ChunterMessage, Message, ThreadMessage } from '@anticrm/chunter' +import chunter, { ChunterSpace, Channel, ChunterMessage, Message, ThreadMessage } from '@anticrm/chunter' import { NotificationClientImpl } from '@anticrm/notification-resources' import { Resources } from '@anticrm/platform' import { getClient, MessageBox } from '@anticrm/presentation' @@ -23,23 +23,28 @@ import TxBacklinkCreate from './components/activity/TxBacklinkCreate.svelte' import TxBacklinkReference from './components/activity/TxBacklinkReference.svelte' import TxCommentCreate from './components/activity/TxCommentCreate.svelte' import ChannelPresenter from './components/ChannelPresenter.svelte' +import DmPresenter from './components/DmPresenter.svelte' import ChannelView from './components/ChannelView.svelte' import ChannelHeader from './components/ChannelHeader.svelte' +import DmHeader from './components/DmHeader.svelte' import CommentInput from './components/CommentInput.svelte' import CommentPresenter from './components/CommentPresenter.svelte' import CommentsPresenter from './components/CommentsPresenter.svelte' import CreateChannel from './components/CreateChannel.svelte' +import CreateDirectMessage from './components/CreateDirectMessage.svelte' import EditChannel from './components/EditChannel.svelte' import ThreadView from './components/ThreadView.svelte' import Threads from './components/Threads.svelte' import SavedMessages from './components/SavedMessages.svelte' import preference from '@anticrm/preference' +import { getDmName } from './utils' + export { CommentsPresenter } async function MarkUnread (object: Message): Promise { const client = NotificationClientImpl.getClient() - await client.updateLastView(object.space, chunter.class.Channel, object.createOn - 1, true) + await client.updateLastView(object.space, chunter.class.ChunterSpace, object.createOn - 1, true) } async function MarkCommentUnread (object: ThreadMessage): Promise { @@ -70,7 +75,7 @@ async function UnsubscribeMessage (object: Message): Promise { async function PinMessage (message: ChunterMessage): Promise { const client = getClient() - await client.updateDoc(chunter.class.Channel, core.space.Space, message.space, { + await client.updateDoc(chunter.class.ChunterSpace, core.space.Space, message.space, { $push: { pinned: message._id } }) } @@ -78,7 +83,7 @@ async function PinMessage (message: ChunterMessage): Promise { export async function UnpinMessage (message: ChunterMessage): Promise { const client = getClient() - await client.updateDoc(chunter.class.Channel, core.space.Space, message.space, { + await client.updateDoc(chunter.class.ChunterSpace, core.space.Space, message.space, { $pull: { pinned: message._id } }) } @@ -149,16 +154,22 @@ export default async (): Promise => ({ component: { CommentInput, CreateChannel, + CreateDirectMessage, ChannelHeader, + DmHeader, ChannelView, CommentPresenter, CommentsPresenter, ChannelPresenter, + DmPresenter, EditChannel, Threads, ThreadView, SavedMessages }, + function: { + GetDmName: getDmName + }, activity: { TxCommentCreate, TxBacklinkCreate, diff --git a/plugins/chunter-resources/src/plugin.ts b/plugins/chunter-resources/src/plugin.ts index 9fc5aa3ff8..90013fc258 100644 --- a/plugins/chunter-resources/src/plugin.ts +++ b/plugins/chunter-resources/src/plugin.ts @@ -14,7 +14,8 @@ // import chunter, { chunterId } from '@anticrm/chunter' -import type { IntlString } from '@anticrm/platform' +import type { Client, Space } from '@anticrm/core' +import type { IntlString, Resource } from '@anticrm/platform' import { mergeIds } from '@anticrm/platform' import type { AnyComponent } from '@anticrm/ui' import { ViewAction } from '@anticrm/view' @@ -22,10 +23,15 @@ import { ViewAction } from '@anticrm/view' export default mergeIds(chunterId, chunter, { component: { CreateChannel: '' as AnyComponent, + CreateDirectMessage: '' as AnyComponent, ChannelHeader: '' as AnyComponent, + DmHeader: '' as AnyComponent, ChannelView: '' as AnyComponent, EditChannel: '' as AnyComponent }, + function: { + GetDmName: '' as Resource<(client: Client, space: Space) => Promise> + }, actionImpl: { SubscribeMessage: '' as ViewAction, UnsubscribeMessage: '' as ViewAction, @@ -36,14 +42,19 @@ export default mergeIds(chunterId, chunter, { }, string: { Channel: '' as IntlString, + DirectMessage: '' as IntlString, Channels: '' as IntlString, + DirectMessages: '' as IntlString, CreateChannel: '' as IntlString, + NewDirectMessage: '' as IntlString, ChannelName: '' as IntlString, ChannelNamePlaceholder: '' as IntlString, ChannelDescription: '' as IntlString, MakePrivate: '' as IntlString, MakePrivateDescription: '' as IntlString, + About: '' as IntlString, Members: '' as IntlString, + NoMembers: '' as IntlString, In: '' as IntlString, Replies: '' as IntlString, Topic: '' as IntlString, diff --git a/plugins/chunter-resources/src/utils.ts b/plugins/chunter-resources/src/utils.ts index 4c9ec435a1..95978c5640 100644 --- a/plugins/chunter-resources/src/utils.ts +++ b/plugins/chunter-resources/src/utils.ts @@ -1,6 +1,9 @@ -import contact, { EmployeeAccount } from '@anticrm/contact' -import { Account, Class, Client, Obj, Ref } from '@anticrm/core' +import contact, { EmployeeAccount, formatName } from '@anticrm/contact' +import { Account, Class, Client, Obj, Ref, Space, getCurrentAccount } from '@anticrm/core' import { Asset } from '@anticrm/platform' +import { getCurrentLocation, locationToUrl } from '@anticrm/ui' + +import chunter from './plugin' export async function getUser ( client: Client, @@ -35,3 +38,28 @@ export function isToday (time: number): boolean { export function classIcon (client: Client, _class: Ref>): Asset | undefined { return client.getHierarchy().getClass(_class).icon } + +export async function getDmName (client: Client, dm: Space): Promise { + const myAccId = getCurrentAccount()._id + + const employeeAccounts = await client.findAll(contact.class.EmployeeAccount, { + _id: { $in: dm.members as Array> } + }) + + const name = (dm.members.length > 1 ? employeeAccounts.filter((a) => a._id !== myAccId) : employeeAccounts) + .map((a) => formatName(a.name)) + .join(', ') + + return name +} + +export function getSpaceLink (id: Ref): string { + const loc = getCurrentLocation() + + loc.path[1] = chunter.app.Chunter + loc.path[2] = id + loc.path.length = 3 + loc.fragment = undefined + + return locationToUrl(loc) +} diff --git a/plugins/chunter/src/index.ts b/plugins/chunter/src/index.ts index 1ec261ed70..9ef898bc0e 100644 --- a/plugins/chunter/src/index.ts +++ b/plugins/chunter/src/index.ts @@ -23,12 +23,23 @@ import { AnyComponent } from '@anticrm/ui' /** * @public */ -export interface Channel extends Space { +export interface ChunterSpace extends Space { lastMessage?: Timestamp pinned?: Ref[] +} + +/** + * @public + */ +export interface Channel extends ChunterSpace { topic?: string } +/** + * @public + */ +export interface DirectMessage extends ChunterSpace {} + /** * @public */ @@ -113,8 +124,10 @@ export default plugin(chunterId, { ThreadMessage: '' as Ref>, Backlink: '' as Ref>, Comment: '' as Ref>, + ChunterSpace: '' as Ref>, Channel: '' as Ref>, - SavedMessages: '' as Ref> + SavedMessages: '' as Ref>, + DirectMessage: '' as Ref> }, space: { Backlinks: '' as Ref diff --git a/plugins/view/src/index.ts b/plugins/view/src/index.ts index 3509c6af62..c57c528d3f 100644 --- a/plugins/view/src/index.ts +++ b/plugins/view/src/index.ts @@ -59,6 +59,13 @@ export interface SpaceHeader extends Class { header: AnyComponent } +/** + * @public + */ +export interface SpaceName extends Class { + getName: Resource<(client: Client, space: Space) => Promise> +} + /** * @public */ @@ -244,6 +251,7 @@ const view = plugin(viewId, { ObjectValidator: '' as Ref>, ObjectFactory: '' as Ref>, SpaceHeader: '' as Ref>, + SpaceName: '' as Ref>, IgnoreActions: '' as Ref>, HTMLPresenter: '' as Ref>, TextPresenter: '' as Ref>, diff --git a/plugins/workbench-resources/src/components/Navigator.svelte b/plugins/workbench-resources/src/components/Navigator.svelte index 47c4728894..a841924f25 100644 --- a/plugins/workbench-resources/src/components/Navigator.svelte +++ b/plugins/workbench-resources/src/components/Navigator.svelte @@ -13,7 +13,7 @@ // limitations under the License. -->