Chunter: Direct messages (#1472)

Signed-off-by: Denis Bunakalya <denis.bunakalya@xored.com>
This commit is contained in:
Denis Bunakalya 2022-04-22 06:13:15 +03:00 committed by GitHub
parent ed0a747330
commit 2efc19044e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 441 additions and 105 deletions

View File

@ -16,12 +16,14 @@
import activity from '@anticrm/activity' import activity from '@anticrm/activity'
import type { import type {
Backlink, Backlink,
ChunterSpace,
Channel, Channel,
ChunterMessage, ChunterMessage,
Comment, Comment,
Message, Message,
SavedMessages, SavedMessages,
ThreadMessage ThreadMessage,
DirectMessage
} from '@anticrm/chunter' } from '@anticrm/chunter'
import contact, { Employee } from '@anticrm/contact' import contact, { Employee } from '@anticrm/contact'
import type { Account, Class, Doc, Domain, Ref, Space, Timestamp } from '@anticrm/core' 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_CHUNTER = 'chunter' as Domain
export const DOMAIN_COMMENT = 'comment' as Domain export const DOMAIN_COMMENT = 'comment' as Domain
@Model(chunter.class.Channel, core.class.Space) @Model(chunter.class.ChunterSpace, core.class.Space)
@UX(chunter.string.Channel, chunter.icon.Hashtag) export class TChunterSpace extends TSpace implements ChunterSpace {
export class TChannel extends TSpace implements Channel {
@Prop(TypeTimestamp(), chunter.string.LastMessage) @Prop(TypeTimestamp(), chunter.string.LastMessage)
lastMessage?: Timestamp lastMessage?: Timestamp
@Prop(ArrOf(TypeRef(chunter.class.ChunterMessage)), chunter.string.PinnedMessages) @Prop(ArrOf(TypeRef(chunter.class.ChunterMessage)), chunter.string.PinnedMessages)
pinned?: Ref<ChunterMessage>[] pinned?: Ref<ChunterMessage>[]
}
@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) @Prop(TypeString(), chunter.string.Topic)
@Index(IndexKind.FullText) @Index(IndexKind.FullText)
topic?: string 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) @Model(chunter.class.ChunterMessage, core.class.AttachedDoc, DOMAIN_CHUNTER)
export class TChunterMessage extends TAttachedDoc implements ChunterMessage { export class TChunterMessage extends TAttachedDoc implements ChunterMessage {
@Prop(TypeMarkup(), chunter.string.Content) @Prop(TypeMarkup(), chunter.string.Content)
@ -128,23 +137,49 @@ export class TSavedMessages extends TPreference implements SavedMessages {
} }
export function createModel (builder: Builder): void { export function createModel (builder: Builder): void {
builder.createModel(TChannel, TMessage, TThreadMessage, TChunterMessage, TComment, TBacklink, TSavedMessages) builder.createModel(
builder.mixin(chunter.class.Channel, core.class.Class, workbench.mixin.SpaceView, { TChunterSpace,
view: { TChannel,
class: chunter.class.Message 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, { builder.mixin(chunter.class.Channel, core.class.Class, view.mixin.AttributePresenter, {
presenter: chunter.component.ChannelPresenter presenter: chunter.component.ChannelPresenter
}) })
builder.mixin(chunter.class.Channel, core.class.Class, notification.mixin.SpaceLastEdit, { builder.mixin(chunter.class.DirectMessage, core.class.Class, view.mixin.SpaceHeader, {
lastEditField: 'lastMessage' header: chunter.component.DmHeader
})
builder.mixin(chunter.class.Channel, core.class.Class, view.mixin.ObjectEditor, {
editor: chunter.component.EditChannel
}) })
builder.mixin(chunter.class.Channel, core.class.Class, view.mixin.SpaceHeader, { 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, spaceClass: chunter.class.Channel,
addSpaceLabel: chunter.string.CreateChannel, addSpaceLabel: chunter.string.CreateChannel,
createComponent: chunter.component.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 aside: chunter.component.ThreadView

View File

@ -26,6 +26,7 @@ export default mergeIds(chunterId, chunter, {
component: { component: {
CommentPresenter: '' as AnyComponent, CommentPresenter: '' as AnyComponent,
ChannelPresenter: '' as AnyComponent, ChannelPresenter: '' as AnyComponent,
DmPresenter: '' as AnyComponent,
Threads: '' as AnyComponent, Threads: '' as AnyComponent,
ThreadView: '' as AnyComponent, ThreadView: '' as AnyComponent,
SavedMessages: '' as AnyComponent SavedMessages: '' as AnyComponent

View File

@ -20,7 +20,8 @@ import core, { TClass, TDoc } from '@anticrm/model-core'
import type { Asset, IntlString, Resource, Status } from '@anticrm/platform' import type { Asset, IntlString, Resource, Status } from '@anticrm/platform'
import type { AnyComponent } from '@anticrm/ui' import type { AnyComponent } from '@anticrm/ui'
import type { import type {
Action, ActionTarget, Action,
ActionTarget,
AttributeEditor, AttributeEditor,
AttributePresenter, AttributePresenter,
HTMLPresenter, HTMLPresenter,
@ -32,6 +33,7 @@ import type {
ObjectValidator, ObjectValidator,
PreviewPresenter, PreviewPresenter,
SpaceHeader, SpaceHeader,
SpaceName,
TextPresenter, TextPresenter,
ViewAction, ViewAction,
ViewContext, ViewContext,
@ -67,7 +69,12 @@ export function createAction (
) )
} }
export function actionTarget (builder: Builder, action: Ref<Action>, target: Ref<Class<Doc>>, context: ViewContext): void { export function actionTarget (
builder: Builder,
action: Ref<Action>,
target: Ref<Class<Doc>>,
context: ViewContext
): void {
builder.createDoc(view.class.ActionTarget, core.space.Model, { builder.createDoc(view.class.ActionTarget, core.space.Model, {
target, target,
action, action,
@ -116,6 +123,11 @@ export class TSpaceHeader extends TClass implements SpaceHeader {
header!: AnyComponent header!: AnyComponent
} }
@Mixin(view.mixin.SpaceName, core.class.Class)
export class TSpaceName extends TClass implements SpaceName {
getName!: Resource<(client: Client, space: Space) => Promise<string>>
}
@Mixin(view.mixin.ObjectValidator, core.class.Class) @Mixin(view.mixin.ObjectValidator, core.class.Class)
export class TObjectValidator extends TClass implements ObjectValidator { export class TObjectValidator extends TClass implements ObjectValidator {
validator!: Resource<<T extends Doc>(doc: T, client: Client) => Promise<Status<{}>>> validator!: Resource<<T extends Doc>(doc: T, client: Client) => Promise<Status<{}>>>
@ -189,6 +201,7 @@ export function createModel (builder: Builder): void {
TObjectEditorHeader, TObjectEditorHeader,
THTMLPresenter, THTMLPresenter,
TSpaceHeader, TSpaceHeader,
TSpaceName,
TTextPresenter, TTextPresenter,
TIgnoreActions, TIgnoreActions,
TPreviewPresenter TPreviewPresenter
@ -214,20 +227,34 @@ export function createModel (builder: Builder): void {
view.viewlet.Table 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' }) 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. // 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' }) 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' }) 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' }) 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' }) actionTarget(builder, view.action.MoveRight, core.class.Doc, { mode: 'browser' })
builder.mixin(core.class.Space, core.class.Class, view.mixin.AttributePresenter, { builder.mixin(core.class.Space, core.class.Class, view.mixin.AttributePresenter, {
@ -235,19 +262,32 @@ export function createModel (builder: Builder): void {
}) })
// Selection stuff // 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' }) 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' }) 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' }) actionTarget(builder, view.action.SelectItemNone, core.class.Doc, { mode: 'browser' })
createAction(builder, view.action.ShowActions, view.string.ShowActions, view.actionImpl.ShowActions, { keyBinding: ['meta + keyk'] }) createAction(builder, view.action.ShowActions, view.string.ShowActions, view.actionImpl.ShowActions, {
actionTarget(builder, view.action.ShowActions, core.class.Doc, { mode: ['workbench', 'browser', 'popup', 'panel', 'editor'] }) 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' }) actionTarget(builder, view.action.ShowPreview, core.class.Doc, { mode: 'browser' })
} }

View File

@ -16,9 +16,6 @@
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model' import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model'
export const viewOperation: MigrateOperation = { export const viewOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> { async migrate (client: MigrationClient): Promise<void> {},
async upgrade (client: MigrationUpgradeClient): Promise<void> {}
},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
}
} }

View File

@ -3,17 +3,22 @@
"ApplicationLabelChunter": "Chat", "ApplicationLabelChunter": "Chat",
"LeftComment": "left a comment", "LeftComment": "left a comment",
"Channels": "Channels", "Channels": "Channels",
"DirectMessages": "Direct messages",
"CreateChannel": "New Channel", "CreateChannel": "New Channel",
"NewDirectMessage": "New Direct Message",
"ChannelName": "Name", "ChannelName": "Name",
"ChannelNamePlaceholder": "Channel", "ChannelNamePlaceholder": "Channel",
"ChannelDescription": "Description", "ChannelDescription": "Description",
"MakePrivate": "Make private", "MakePrivate": "Make private",
"MakePrivateDescription": "Only members can see it", "MakePrivateDescription": "Only members can see it",
"Channel": "Channel", "Channel": "Channel",
"DirectMessage": "Direct message",
"EditUpdate": "Save...", "EditUpdate": "Save...",
"EditCancel": "Cancel", "EditCancel": "Cancel",
"Comments" : "Comments", "Comments" : "Comments",
"About": "About",
"Members": "Members", "Members": "Members",
"NoMembers": "No members",
"MentionedIn": "mentioned this ", "MentionedIn": "mentioned this ",
"ContactInfo": "Contact Info", "ContactInfo": "Contact Info",
"Content": "Content", "Content": "Content",

View File

@ -3,17 +3,22 @@
"ApplicationLabelChunter": "Чат", "ApplicationLabelChunter": "Чат",
"LeftComment": "оставил(а) комментарий", "LeftComment": "оставил(а) комментарий",
"Channels": "Каналы", "Channels": "Каналы",
"DirectMessages": "Личные сообщения",
"CreateChannel": "Создать канал", "CreateChannel": "Создать канал",
"NewDirectMessage": "Создать личное сообщение",
"ChannelName": "Название", "ChannelName": "Название",
"ChannelNamePlaceholder": "Канал", "ChannelNamePlaceholder": "Канал",
"ChannelDescription": "Описание", "ChannelDescription": "Описание",
"MakePrivate": "Сделать личным", "MakePrivate": "Сделать личным",
"MakePrivateDescription": "Только пользователи могут видеть это", "MakePrivateDescription": "Только пользователи могут видеть это",
"Channel": "Канал ", "Channel": "Канал ",
"DirectMessage": "Личное сообщение",
"EditUpdate": "Сохранить...", "EditUpdate": "Сохранить...",
"EditCancel": "Отменить", "EditCancel": "Отменить",
"Comments" : "Комментарии", "Comments" : "Комментарии",
"About": "Информация",
"Members": "Участники", "Members": "Участники",
"NoMembers": "Нет участников",
"MentionedIn": "упомянул(а) ", "MentionedIn": "упомянул(а) ",
"ContactInfo": "Контактная информация", "ContactInfo": "Контактная информация",
"Content": "Содержимое", "Content": "Содержимое",

View File

@ -74,7 +74,7 @@
(res) => { (res) => {
messages = res messages = res
newMessagesPos = newMessagesStart(messages) newMessagesPos = newMessagesStart(messages)
notificationClient.updateLastView(space, chunter.class.Channel) notificationClient.updateLastView(space, chunter.class.ChunterSpace)
}, },
{ {
lookup: { lookup: {

View File

@ -14,26 +14,16 @@
--> -->
<script lang="ts"> <script lang="ts">
import type { Channel } from '@anticrm/chunter' import type { Channel } from '@anticrm/chunter'
import { Ref, Space } from '@anticrm/core'
import { getClient } from '@anticrm/presentation' import { getClient } from '@anticrm/presentation'
import { getCurrentLocation, Icon, locationToUrl } from '@anticrm/ui' import { Icon } from '@anticrm/ui'
import chunter from '../plugin'
import { getSpaceLink } from '../utils'
export let value: Channel export let value: Channel
const client = getClient() const client = getClient()
$: icon = client.getHierarchy().getClass(value._class).icon $: icon = client.getHierarchy().getClass(value._class).icon
$: link = getSpaceLink(value._id)
function getLink (id: Ref<Space>): string {
const loc = getCurrentLocation()
loc.path[1] = chunter.app.Chunter
loc.path[2] = id
loc.path.length = 3
loc.fragment = undefined
return locationToUrl(loc)
}
$: link = getLink(value._id)
</script> </script>
{#if value} {#if value}

View File

@ -40,7 +40,7 @@
space, space,
{ {
attachedTo: space, attachedTo: space,
attachedToClass: chunter.class.Channel, attachedToClass: chunter.class.ChunterSpace,
collection: 'messages', collection: 'messages',
content: message, content: message,
createOn: 0, createOn: 0,
@ -50,11 +50,11 @@
_id _id
) )
tx.attributes.createOn = tx.modifiedOn 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) await client.tx(tx)
// Create an backlink to document // 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() _id = generateId()
} }
@ -68,7 +68,7 @@
const pinnedQuery = createQuery() const pinnedQuery = createQuery()
let pinnedIds: Ref<ChunterMessage>[] = [] let pinnedIds: Ref<ChunterMessage>[] = []
pinnedQuery.query( pinnedQuery.query(
chunter.class.Channel, chunter.class.ChunterSpace,
{ _id: space }, { _id: space },
(res) => { (res) => {
pinnedIds = res[0]?.pinned ?? [] pinnedIds = res[0]?.pinned ?? []

View File

@ -0,0 +1,57 @@
<!--
// Copyright © 2022 Anticrm Platform Contributors.
//
// 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 contact, { Employee } from '@anticrm/contact'
import core, { getCurrentAccount, Ref } from '@anticrm/core'
import { getClient, SpaceCreateCard, UserBoxList } from '@anticrm/presentation'
import chunter from '../plugin'
const dispatch = createEventDispatcher()
const client = getClient()
const myAccId = getCurrentAccount()._id
let employeeIds: Ref<Employee>[] = []
function createDirectMessage () {
client.findAll(contact.class.EmployeeAccount, { employee: { $in: employeeIds } }).then((employeeAccounts) => {
client.createDoc(chunter.class.DirectMessage, core.space.Space, {
name: '',
description: '',
private: true,
archived: false,
members: [myAccId, ...employeeAccounts.filter((ea) => ea._id !== myAccId).map((ea) => ea._id)]
})
})
}
</script>
<SpaceCreateCard
label={chunter.string.NewDirectMessage}
okAction={createDirectMessage}
canSave={employeeIds.length > 0}
on:close={() => {
dispatch('close')
}}
>
<UserBoxList
_class={contact.class.Employee}
label={chunter.string.Members}
noItems={chunter.string.NoMembers}
on:update={(evt) => (employeeIds = evt.detail)}
/>
</SpaceCreateCard>

View File

@ -0,0 +1,46 @@
<!--
// Copyright © 2022 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 { DirectMessage } from '@anticrm/chunter'
import type { Ref } from '@anticrm/core'
import { createQuery, getClient } from '@anticrm/presentation'
import { showPanel } from '@anticrm/ui'
import chunter from '../plugin'
import { classIcon, getDmName } from '../utils'
import Header from './Header.svelte'
export let spaceId: Ref<DirectMessage> | undefined
const client = getClient()
const query = createQuery()
let dm: DirectMessage | undefined
$: query.query(chunter.class.DirectMessage, { _id: spaceId }, (result) => {
dm = result[0]
})
async function onSpaceEdit (): Promise<void> {
if (dm === undefined) return
showPanel(chunter.component.EditChannel, dm._id, dm._class, 'right')
}
</script>
<div class="ac-header divide full">
{#if dm}
{#await getDmName(client, dm) then name}
<Header icon={classIcon(client, dm._class)} label={name} description={''} on:click={onSpaceEdit} />
{/await}
{/if}
</div>

View File

@ -0,0 +1,40 @@
<!--
// Copyright © 2022 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 type { DirectMessage } from '@anticrm/chunter'
import { getClient } from '@anticrm/presentation'
import { Icon } from '@anticrm/ui'
import { getSpaceLink, getDmName } from '../utils'
export let dm: DirectMessage
const client = getClient()
$: icon = client.getHierarchy().getClass(dm._class).icon
$: link = getSpaceLink(dm._id)
</script>
{#if dm}
{#await getDmName(client, dm) then name}
<a class="flex-presenter" href={link}>
<div class="icon">
{#if icon}
<Icon {icon} size={'small'} />
{/if}
</div>
<span class="label">{name}</span>
</a>
{/await}
{/if}

View File

@ -14,7 +14,7 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import { Channel } from '@anticrm/chunter' import { ChunterSpace } from '@anticrm/chunter'
import type { Class, Ref } from '@anticrm/core' import type { Class, Ref } from '@anticrm/core'
import type { IntlString } from '@anticrm/platform' import type { IntlString } from '@anticrm/platform'
import { createQuery, getClient, Members } from '@anticrm/presentation' import { createQuery, getClient, Members } from '@anticrm/presentation'
@ -25,10 +25,10 @@
import EditChannelDescriptionTab from './EditChannelDescriptionTab.svelte' import EditChannelDescriptionTab from './EditChannelDescriptionTab.svelte'
import EditChannelSettingsTab from './EditChannelSettingsTab.svelte' import EditChannelSettingsTab from './EditChannelSettingsTab.svelte'
export let _id: Ref<Channel> export let _id: Ref<ChunterSpace>
export let _class: Ref<Class<Channel>> export let _class: Ref<Class<ChunterSpace>>
let channel: Channel | undefined let channel: ChunterSpace
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -36,11 +36,15 @@
const clazz = client.getHierarchy().getClass(_class) const clazz = client.getHierarchy().getClass(_class)
const query = createQuery() const query = createQuery()
$: query.query(chunter.class.Channel, { _id }, (result) => { $: query.query(chunter.class.ChunterSpace, { _id }, (result) => {
channel = result[0] channel = result[0]
}) })
const tabLabels: IntlString[] = [chunter.string.Channel, chunter.string.Members, chunter.string.Settings] const tabLabels: IntlString[] = [
chunter.string.About,
chunter.string.Members,
...(_class === chunter.class.Channel ? [chunter.string.Settings] : [])
]
let selectedTabIndex = 0 let selectedTabIndex = 0
</script> </script>

View File

@ -16,13 +16,13 @@
<script lang="ts"> <script lang="ts">
import attachment, { Attachment } from '@anticrm/attachment' import attachment, { Attachment } from '@anticrm/attachment'
import { AttachmentPresenter } from '@anticrm/attachment-resources' import { AttachmentPresenter } from '@anticrm/attachment-resources'
import { Channel } from '@anticrm/chunter' import { ChunterSpace } from '@anticrm/chunter'
import { Doc, SortingOrder } from '@anticrm/core' import { Doc, SortingOrder } from '@anticrm/core'
import { createQuery } from '@anticrm/presentation' import { createQuery } from '@anticrm/presentation'
import { Menu } from '@anticrm/view-resources' import { Menu } from '@anticrm/view-resources'
import { showPopup, IconMoreV, Label } from '@anticrm/ui' import { showPopup, IconMoreV, Label } from '@anticrm/ui'
export let channel: Channel | undefined export let channel: ChunterSpace | undefined
const query = createQuery() const query = createQuery()
let visibleAttachments: Attachment[] | undefined let visibleAttachments: Attachment[] | undefined

View File

@ -14,7 +14,7 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import { Channel } from '@anticrm/chunter' import { Channel, ChunterSpace } from '@anticrm/chunter'
import { getCurrentAccount } from '@anticrm/core' import { getCurrentAccount } from '@anticrm/core'
import { getClient } from '@anticrm/presentation' import { getClient } from '@anticrm/presentation'
import { Button, EditBox } from '@anticrm/ui' import { Button, EditBox } from '@anticrm/ui'
@ -22,14 +22,21 @@
import chunter from '../plugin' import chunter from '../plugin'
import EditChannelDescriptionAttachments from './EditChannelDescriptionAttachments.svelte' import EditChannelDescriptionAttachments from './EditChannelDescriptionAttachments.svelte'
export let channel: Channel export let channel: ChunterSpace
const client = getClient() const client = getClient()
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
function isCommonChannel (channel?: ChunterSpace): channel is Channel {
return channel?._class === chunter.class.Channel
}
function onTopicChange (ev: Event) { function onTopicChange (ev: Event) {
if (!isCommonChannel(channel)) {
return
}
const newTopic = (ev.target as HTMLInputElement).value const newTopic = (ev.target as HTMLInputElement).value
client.update(channel!, { topic: newTopic }) client.update(channel, { topic: newTopic })
} }
function onDescriptionChange (ev: Event) { function onDescriptionChange (ev: Event) {
@ -47,6 +54,7 @@
{#if channel} {#if channel}
<div class="flex-col flex-gap-3"> <div class="flex-col flex-gap-3">
{#if isCommonChannel(channel)}
<EditBox <EditBox
label={chunter.string.Topic} label={chunter.string.Topic}
bind:value={channel.topic} bind:value={channel.topic}
@ -71,6 +79,7 @@
leaveChannel() leaveChannel()
}} }}
/> />
{/if}
<EditChannelDescriptionAttachments {channel} /> <EditChannelDescriptionAttachments {channel} />
</div> </div>
{/if} {/if}

View File

@ -13,7 +13,7 @@
const pinnedQuery = createQuery() const pinnedQuery = createQuery()
let pinnedIds: Ref<ChunterMessage>[] = [] let pinnedIds: Ref<ChunterMessage>[] = []
pinnedQuery.query( pinnedQuery.query(
chunter.class.Channel, chunter.class.ChunterSpace,
{ _id: space }, { _id: space },
(res) => { (res) => {
pinnedIds = res[0]?.pinned ?? [] pinnedIds = res[0]?.pinned ?? []

View File

@ -15,7 +15,7 @@
<script lang="ts"> <script lang="ts">
import attachment from '@anticrm/attachment' import attachment from '@anticrm/attachment'
import { AttachmentRefInput } from '@anticrm/attachment-resources' import { AttachmentRefInput } from '@anticrm/attachment-resources'
import type { Channel, Message, ThreadMessage } from '@anticrm/chunter' import type { ChunterSpace, Message, ThreadMessage } from '@anticrm/chunter'
import contact, { Employee, EmployeeAccount, formatName } from '@anticrm/contact' import contact, { Employee, EmployeeAccount, formatName } from '@anticrm/contact'
import core, { FindOptions, generateId, getCurrentAccount, Ref, SortingOrder, TxFactory } from '@anticrm/core' import core, { FindOptions, generateId, getCurrentAccount, Ref, SortingOrder, TxFactory } from '@anticrm/core'
import { NotificationClientImpl } from '@anticrm/notification-resources' import { NotificationClientImpl } from '@anticrm/notification-resources'
@ -24,6 +24,7 @@
import { createBacklinks } from '../backlinks' import { createBacklinks } from '../backlinks'
import chunter from '../plugin' import chunter from '../plugin'
import ChannelPresenter from './ChannelPresenter.svelte' import ChannelPresenter from './ChannelPresenter.svelte'
import DmPresenter from './DmPresenter.svelte'
import MsgView from './Message.svelte' import MsgView from './Message.svelte'
const client = getClient() const client = getClient()
@ -152,22 +153,24 @@
await client.tx(tx) await client.tx(tx)
// Create an backlink to document // Create an backlink to document
await createBacklinks(client, parent.space, chunter.class.Channel, commentId, message) await createBacklinks(client, parent.space, chunter.class.ChunterSpace, commentId, message)
commentId = generateId() commentId = generateId()
} }
let comments: ThreadMessage[] = [] let comments: ThreadMessage[] = []
async function getChannel (_id: Ref<Channel>): Promise<Channel | undefined> { async function getChannel (_id: Ref<ChunterSpace>): Promise<ChunterSpace | undefined> {
return await client.findOne(chunter.class.Channel, { _id }) return await client.findOne(chunter.class.ChunterSpace, { _id })
} }
</script> </script>
<div class="ml-8 mt-4"> <div class="ml-8 mt-4">
{#if parent} {#if parent}
{#await getChannel(parent.space) then channel} {#await getChannel(parent.space) then channel}
{#if channel} {#if channel?._class === chunter.class.Channel}
<ChannelPresenter value={channel} /> <ChannelPresenter value={channel} />
{:else if channel}
<DmPresenter dm={channel} />
{/if} {/if}
{/await} {/await}
{#await getParticipants(comments, parent, employees) then participants} {#await getParticipants(comments, parent, employees) then participants}
@ -208,7 +211,7 @@
overflow: hidden; overflow: hidden;
margin: 1rem 1rem 0px; margin: 1rem 1rem 0px;
background-color: var(--theme-border-modal); background-color: var(--theme-border-modal);
border-radius: .75rem; border-radius: 0.75rem;
border: 1px solid var(--theme-zone-border); border: 1px solid var(--theme-zone-border);
} }

View File

@ -100,7 +100,7 @@
) )
pinnedQuery.query( pinnedQuery.query(
chunter.class.Channel, chunter.class.ChunterSpace,
{ _id: currentSpace }, { _id: currentSpace },
(res) => { (res) => {
pinnedIds = res[0]?.pinned ?? [] pinnedIds = res[0]?.pinned ?? []
@ -153,7 +153,7 @@
await client.tx(tx) await client.tx(tx)
// Create an backlink to document // 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() commentId = generateId()
} }

View File

@ -14,7 +14,7 @@
// //
import core from '@anticrm/core' 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 { NotificationClientImpl } from '@anticrm/notification-resources'
import { Resources } from '@anticrm/platform' import { Resources } from '@anticrm/platform'
import { getClient, MessageBox } from '@anticrm/presentation' 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 TxBacklinkReference from './components/activity/TxBacklinkReference.svelte'
import TxCommentCreate from './components/activity/TxCommentCreate.svelte' import TxCommentCreate from './components/activity/TxCommentCreate.svelte'
import ChannelPresenter from './components/ChannelPresenter.svelte' import ChannelPresenter from './components/ChannelPresenter.svelte'
import DmPresenter from './components/DmPresenter.svelte'
import ChannelView from './components/ChannelView.svelte' import ChannelView from './components/ChannelView.svelte'
import ChannelHeader from './components/ChannelHeader.svelte' import ChannelHeader from './components/ChannelHeader.svelte'
import DmHeader from './components/DmHeader.svelte'
import CommentInput from './components/CommentInput.svelte' import CommentInput from './components/CommentInput.svelte'
import CommentPresenter from './components/CommentPresenter.svelte' import CommentPresenter from './components/CommentPresenter.svelte'
import CommentsPresenter from './components/CommentsPresenter.svelte' import CommentsPresenter from './components/CommentsPresenter.svelte'
import CreateChannel from './components/CreateChannel.svelte' import CreateChannel from './components/CreateChannel.svelte'
import CreateDirectMessage from './components/CreateDirectMessage.svelte'
import EditChannel from './components/EditChannel.svelte' import EditChannel from './components/EditChannel.svelte'
import ThreadView from './components/ThreadView.svelte' import ThreadView from './components/ThreadView.svelte'
import Threads from './components/Threads.svelte' import Threads from './components/Threads.svelte'
import SavedMessages from './components/SavedMessages.svelte' import SavedMessages from './components/SavedMessages.svelte'
import preference from '@anticrm/preference' import preference from '@anticrm/preference'
import { getDmName } from './utils'
export { CommentsPresenter } export { CommentsPresenter }
async function MarkUnread (object: Message): Promise<void> { async function MarkUnread (object: Message): Promise<void> {
const client = NotificationClientImpl.getClient() 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<void> { async function MarkCommentUnread (object: ThreadMessage): Promise<void> {
@ -70,7 +75,7 @@ async function UnsubscribeMessage (object: Message): Promise<void> {
async function PinMessage (message: ChunterMessage): Promise<void> { async function PinMessage (message: ChunterMessage): Promise<void> {
const client = getClient() const client = getClient()
await client.updateDoc<Channel>(chunter.class.Channel, core.space.Space, message.space, { await client.updateDoc<ChunterSpace>(chunter.class.ChunterSpace, core.space.Space, message.space, {
$push: { pinned: message._id } $push: { pinned: message._id }
}) })
} }
@ -78,7 +83,7 @@ async function PinMessage (message: ChunterMessage): Promise<void> {
export async function UnpinMessage (message: ChunterMessage): Promise<void> { export async function UnpinMessage (message: ChunterMessage): Promise<void> {
const client = getClient() const client = getClient()
await client.updateDoc<Channel>(chunter.class.Channel, core.space.Space, message.space, { await client.updateDoc<ChunterSpace>(chunter.class.ChunterSpace, core.space.Space, message.space, {
$pull: { pinned: message._id } $pull: { pinned: message._id }
}) })
} }
@ -149,16 +154,22 @@ export default async (): Promise<Resources> => ({
component: { component: {
CommentInput, CommentInput,
CreateChannel, CreateChannel,
CreateDirectMessage,
ChannelHeader, ChannelHeader,
DmHeader,
ChannelView, ChannelView,
CommentPresenter, CommentPresenter,
CommentsPresenter, CommentsPresenter,
ChannelPresenter, ChannelPresenter,
DmPresenter,
EditChannel, EditChannel,
Threads, Threads,
ThreadView, ThreadView,
SavedMessages SavedMessages
}, },
function: {
GetDmName: getDmName
},
activity: { activity: {
TxCommentCreate, TxCommentCreate,
TxBacklinkCreate, TxBacklinkCreate,

View File

@ -14,7 +14,8 @@
// //
import chunter, { chunterId } from '@anticrm/chunter' 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 { mergeIds } from '@anticrm/platform'
import type { AnyComponent } from '@anticrm/ui' import type { AnyComponent } from '@anticrm/ui'
import { ViewAction } from '@anticrm/view' import { ViewAction } from '@anticrm/view'
@ -22,10 +23,15 @@ import { ViewAction } from '@anticrm/view'
export default mergeIds(chunterId, chunter, { export default mergeIds(chunterId, chunter, {
component: { component: {
CreateChannel: '' as AnyComponent, CreateChannel: '' as AnyComponent,
CreateDirectMessage: '' as AnyComponent,
ChannelHeader: '' as AnyComponent, ChannelHeader: '' as AnyComponent,
DmHeader: '' as AnyComponent,
ChannelView: '' as AnyComponent, ChannelView: '' as AnyComponent,
EditChannel: '' as AnyComponent EditChannel: '' as AnyComponent
}, },
function: {
GetDmName: '' as Resource<(client: Client, space: Space) => Promise<string>>
},
actionImpl: { actionImpl: {
SubscribeMessage: '' as ViewAction, SubscribeMessage: '' as ViewAction,
UnsubscribeMessage: '' as ViewAction, UnsubscribeMessage: '' as ViewAction,
@ -36,14 +42,19 @@ export default mergeIds(chunterId, chunter, {
}, },
string: { string: {
Channel: '' as IntlString, Channel: '' as IntlString,
DirectMessage: '' as IntlString,
Channels: '' as IntlString, Channels: '' as IntlString,
DirectMessages: '' as IntlString,
CreateChannel: '' as IntlString, CreateChannel: '' as IntlString,
NewDirectMessage: '' as IntlString,
ChannelName: '' as IntlString, ChannelName: '' as IntlString,
ChannelNamePlaceholder: '' as IntlString, ChannelNamePlaceholder: '' as IntlString,
ChannelDescription: '' as IntlString, ChannelDescription: '' as IntlString,
MakePrivate: '' as IntlString, MakePrivate: '' as IntlString,
MakePrivateDescription: '' as IntlString, MakePrivateDescription: '' as IntlString,
About: '' as IntlString,
Members: '' as IntlString, Members: '' as IntlString,
NoMembers: '' as IntlString,
In: '' as IntlString, In: '' as IntlString,
Replies: '' as IntlString, Replies: '' as IntlString,
Topic: '' as IntlString, Topic: '' as IntlString,

View File

@ -1,6 +1,9 @@
import contact, { EmployeeAccount } from '@anticrm/contact' import contact, { EmployeeAccount, formatName } from '@anticrm/contact'
import { Account, Class, Client, Obj, Ref } from '@anticrm/core' import { Account, Class, Client, Obj, Ref, Space, getCurrentAccount } from '@anticrm/core'
import { Asset } from '@anticrm/platform' import { Asset } from '@anticrm/platform'
import { getCurrentLocation, locationToUrl } from '@anticrm/ui'
import chunter from './plugin'
export async function getUser ( export async function getUser (
client: Client, client: Client,
@ -35,3 +38,28 @@ export function isToday (time: number): boolean {
export function classIcon (client: Client, _class: Ref<Class<Obj>>): Asset | undefined { export function classIcon (client: Client, _class: Ref<Class<Obj>>): Asset | undefined {
return client.getHierarchy().getClass(_class).icon return client.getHierarchy().getClass(_class).icon
} }
export async function getDmName (client: Client, dm: Space): Promise<string> {
const myAccId = getCurrentAccount()._id
const employeeAccounts = await client.findAll(contact.class.EmployeeAccount, {
_id: { $in: dm.members as Array<Ref<EmployeeAccount>> }
})
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<Space>): string {
const loc = getCurrentLocation()
loc.path[1] = chunter.app.Chunter
loc.path[2] = id
loc.path.length = 3
loc.fragment = undefined
return locationToUrl(loc)
}

View File

@ -23,12 +23,23 @@ import { AnyComponent } from '@anticrm/ui'
/** /**
* @public * @public
*/ */
export interface Channel extends Space { export interface ChunterSpace extends Space {
lastMessage?: Timestamp lastMessage?: Timestamp
pinned?: Ref<ChunterMessage>[] pinned?: Ref<ChunterMessage>[]
}
/**
* @public
*/
export interface Channel extends ChunterSpace {
topic?: string topic?: string
} }
/**
* @public
*/
export interface DirectMessage extends ChunterSpace {}
/** /**
* @public * @public
*/ */
@ -113,8 +124,10 @@ export default plugin(chunterId, {
ThreadMessage: '' as Ref<Class<ThreadMessage>>, ThreadMessage: '' as Ref<Class<ThreadMessage>>,
Backlink: '' as Ref<Class<Backlink>>, Backlink: '' as Ref<Class<Backlink>>,
Comment: '' as Ref<Class<Comment>>, Comment: '' as Ref<Class<Comment>>,
ChunterSpace: '' as Ref<Class<ChunterSpace>>,
Channel: '' as Ref<Class<Channel>>, Channel: '' as Ref<Class<Channel>>,
SavedMessages: '' as Ref<Class<SavedMessages>> SavedMessages: '' as Ref<Class<SavedMessages>>,
DirectMessage: '' as Ref<Class<DirectMessage>>
}, },
space: { space: {
Backlinks: '' as Ref<Space> Backlinks: '' as Ref<Space>

View File

@ -59,6 +59,13 @@ export interface SpaceHeader extends Class<Doc> {
header: AnyComponent header: AnyComponent
} }
/**
* @public
*/
export interface SpaceName extends Class<Doc> {
getName: Resource<(client: Client, space: Space) => Promise<string>>
}
/** /**
* @public * @public
*/ */
@ -244,6 +251,7 @@ const view = plugin(viewId, {
ObjectValidator: '' as Ref<Mixin<ObjectValidator>>, ObjectValidator: '' as Ref<Mixin<ObjectValidator>>,
ObjectFactory: '' as Ref<Mixin<ObjectFactory>>, ObjectFactory: '' as Ref<Mixin<ObjectFactory>>,
SpaceHeader: '' as Ref<Mixin<SpaceHeader>>, SpaceHeader: '' as Ref<Mixin<SpaceHeader>>,
SpaceName: '' as Ref<Mixin<SpaceName>>,
IgnoreActions: '' as Ref<Mixin<IgnoreActions>>, IgnoreActions: '' as Ref<Mixin<IgnoreActions>>,
HTMLPresenter: '' as Ref<Mixin<HTMLPresenter>>, HTMLPresenter: '' as Ref<Mixin<HTMLPresenter>>,
TextPresenter: '' as Ref<Mixin<TextPresenter>>, TextPresenter: '' as Ref<Mixin<TextPresenter>>,

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import core, { Doc, Ref, SortingOrder, Space } from '@anticrm/core' import core, { Doc, Ref, SortingOrder, Space, getCurrentAccount } from '@anticrm/core'
import { getResource } from '@anticrm/platform' import { getResource } from '@anticrm/platform'
import { createQuery, getClient } from '@anticrm/presentation' import { createQuery, getClient } from '@anticrm/presentation'
import { Scroller } from '@anticrm/ui' import { Scroller } from '@anticrm/ui'
@ -34,6 +34,7 @@
const client = getClient() const client = getClient()
const hierarchy = client.getHierarchy() const hierarchy = client.getHierarchy()
const query = createQuery() const query = createQuery()
const myAccId = getCurrentAccount()._id
let spaces: Space[] = [] let spaces: Space[] = []
let starred: Space[] = [] let starred: Space[] = []
@ -77,7 +78,7 @@
topSpecials = [] topSpecials = []
bottomSpecials = [] bottomSpecials = []
} }
shownSpaces = spaces.filter((sp) => !sp.archived && !preferences.has(sp._id)) shownSpaces = spaces.filter((sp) => !sp.archived && !preferences.has(sp._id) && (!sp.members.length || sp.members.includes(myAccId)))
starred = spaces.filter((sp) => preferences.has(sp._id)) starred = spaces.filter((sp) => preferences.has(sp._id))
} }

View File

@ -132,6 +132,20 @@
return lastView < value return lastView < value
} }
async function getName (space: Space) {
const clazz = hierarchy.getClass(space._class)
const nameMixin = hierarchy.as(clazz, view.mixin.SpaceName)
if (nameMixin.getName) {
const getSpaceName = await getResource(nameMixin.getName);
const name = await getSpaceName(client, space)
return name
}
return space.name
}
function getParentActions(): Action[] { function getParentActions(): Action[] {
return hasSpaceBrowser ? [browseSpaces, addSpace] : [addSpace] return hasSpaceBrowser ? [browseSpaces, addSpace] : [addSpace]
} }
@ -155,18 +169,20 @@
{/each} {/each}
</TreeNode> </TreeNode>
{:else} {:else}
<TreeItem {#await getName(space) then name}
indent={'ml-4'} <TreeItem
_id={space._id} indent={'ml-4'}
title={space.name} _id={space._id}
icon={classIcon(client, space._class)} title={name}
selected={currentSpace === space._id} icon={classIcon(client, space._class)}
actions={() => getActions(space)} selected={currentSpace === space._id}
bold={isChanged(space, $lastViews)} actions={() => getActions(space)}
on:click={() => { bold={isChanged(space, $lastViews)}
selectSpace(space._id) on:click={() => {
}} selectSpace(space._id)
/> }}
/>
{/await}
{/if} {/if}
{/each} {/each}
</TreeNode> </TreeNode>

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
// //
import chunter, { Channel, Comment, Message, ThreadMessage } from '@anticrm/chunter' import chunter, { ChunterSpace, Comment, Message, ThreadMessage } from '@anticrm/chunter'
import { EmployeeAccount } from '@anticrm/contact' import { EmployeeAccount } from '@anticrm/contact'
import core, { import core, {
Class, Class,
@ -39,7 +39,7 @@ import workbench from '@anticrm/workbench'
* @public * @public
*/ */
export function channelHTMLPresenter (doc: Doc): string { export function channelHTMLPresenter (doc: Doc): string {
const channel = doc as Channel const channel = doc as ChunterSpace
const front = getMetadata(login.metadata.FrontUrl) ?? '' const front = getMetadata(login.metadata.FrontUrl) ?? ''
return `<a href="${front}/${workbench.component.WorkbenchApp}/${chunter.app.Chunter}/${channel._id}">${channel.name}</a>` return `<a href="${front}/${workbench.component.WorkbenchApp}/${chunter.app.Chunter}/${channel._id}">${channel.name}</a>`
} }
@ -48,7 +48,7 @@ export function channelHTMLPresenter (doc: Doc): string {
* @public * @public
*/ */
export function channelTextPresenter (doc: Doc): string { export function channelTextPresenter (doc: Doc): string {
const channel = doc as Channel const channel = doc as ChunterSpace
return `${channel.name}` return `${channel.name}`
} }
@ -145,12 +145,12 @@ export async function MessageCreate (tx: Tx, control: TriggerControl): Promise<T
const message = doc as Message const message = doc as Message
const channel = (await control.findAll(chunter.class.Channel, { const channel = (await control.findAll(chunter.class.ChunterSpace, {
_id: message.space _id: message.space
}, { limit: 1 }))[0] }, { limit: 1 }))[0]
if (channel.lastMessage === undefined || channel.lastMessage < message.createOn) { if (channel.lastMessage === undefined || channel.lastMessage < message.createOn) {
const res = control.txFactory.createTxUpdateDoc<Channel>(channel._class, channel.space, channel._id, { const res = control.txFactory.createTxUpdateDoc<ChunterSpace>(channel._class, channel.space, channel._id, {
lastMessage: message.createOn lastMessage: message.createOn
}) })
return [res] return [res]
@ -165,7 +165,7 @@ export async function MessageDelete (tx: Tx, control: TriggerControl): Promise<T
const hierarchy = control.hierarchy const hierarchy = control.hierarchy
if (tx._class !== core.class.TxCollectionCUD) return [] if (tx._class !== core.class.TxCollectionCUD) return []
const rmTx = (tx as TxCollectionCUD<Channel, Message>).tx const rmTx = (tx as TxCollectionCUD<ChunterSpace, Message>).tx
if (!hierarchy.isDerived(rmTx.objectClass, chunter.class.Message)) { if (!hierarchy.isDerived(rmTx.objectClass, chunter.class.Message)) {
return [] return []
} }
@ -175,7 +175,7 @@ export async function MessageDelete (tx: Tx, control: TriggerControl): Promise<T
const message = TxProcessor.createDoc2Doc(createTx as TxCreateDoc<Message>) const message = TxProcessor.createDoc2Doc(createTx as TxCreateDoc<Message>)
const channel = (await control.findAll(chunter.class.Channel, { const channel = (await control.findAll(chunter.class.ChunterSpace, {
_id: message.space _id: message.space
}, { limit: 1 }))[0] }, { limit: 1 }))[0]
@ -185,7 +185,7 @@ export async function MessageDelete (tx: Tx, control: TriggerControl): Promise<T
}) })
const lastMessageDate = messages.reduce((maxDate, mess) => mess.createOn > maxDate ? mess.createOn : maxDate, 0) const lastMessageDate = messages.reduce((maxDate, mess) => mess.createOn > maxDate ? mess.createOn : maxDate, 0)
const updateTx = control.txFactory.createTxUpdateDoc<Channel>(channel._class, channel.space, channel._id, { const updateTx = control.txFactory.createTxUpdateDoc<ChunterSpace>(channel._class, channel.space, channel._id, {
lastMessage: lastMessageDate > 0 ? lastMessageDate : undefined lastMessage: lastMessageDate > 0 ? lastMessageDate : undefined
}) })

View File

@ -31,7 +31,17 @@ export default plugin(serverChunterId, {
ChunterTrigger: '' as Resource<TriggerFunc> ChunterTrigger: '' as Resource<TriggerFunc>
}, },
function: { function: {
CommentRemove: '' as Resource<(doc: Doc, hiearachy: Hierarchy, findAll: <T extends Doc> (clazz: Ref<Class<T>>, query: DocumentQuery<T>, options?: FindOptions<T>) => Promise<FindResult<T>>) => Promise<Doc[]>>, CommentRemove: '' as Resource<
(
doc: Doc,
hiearachy: Hierarchy,
findAll: <T extends Doc>(
clazz: Ref<Class<T>>,
query: DocumentQuery<T>,
options?: FindOptions<T>
) => Promise<FindResult<T>>
) => Promise<Doc[]>
>,
ChannelHTMLPresenter: '' as Resource<(doc: Doc) => string>, ChannelHTMLPresenter: '' as Resource<(doc: Doc) => string>,
ChannelTextPresenter: '' as Resource<(doc: Doc) => string> ChannelTextPresenter: '' as Resource<(doc: Doc) => string>
} }