diff --git a/communication b/communication
index f7d1b98df4..407c52ca43 160000
--- a/communication
+++ b/communication
@@ -1 +1 @@
-Subproject commit f7d1b98df46f277396ba9b22b084adf28203ac84
+Subproject commit 407c52ca437011ab2bb988c2cf7d103d6b83ff8a
diff --git a/models/card/src/index.ts b/models/card/src/index.ts
index ed2364a393..e123ef4e05 100644
--- a/models/card/src/index.ts
+++ b/models/card/src/index.ts
@@ -19,6 +19,8 @@ import {
type CardSection,
type CardSpace,
type CardViewDefaults,
+ type CreateCardExtension,
+ type CanCreateCardResource,
DOMAIN_CARD,
type FavoriteCard,
type MasterTag,
@@ -168,6 +170,12 @@ export class TFavoriteCard extends TPreference implements FavoriteCard {
application!: string
}
+@Mixin(card.mixin.CreateCardExtension, card.class.MasterTag)
+export class TCreateCardExtension extends TMasterTag implements CreateCardExtension {
+ component?: AnyComponent
+ canCreate?: CanCreateCardResource
+}
+
export * from './migration'
const listConfig: (BuildModelKey | string)[] = [
@@ -337,7 +345,8 @@ export function createModel (builder: Builder): void {
TRole,
TCardSection,
TCardViewDefaults,
- TFavoriteCard
+ TFavoriteCard,
+ TCreateCardExtension
)
defineTabs(builder)
diff --git a/models/communication/src/types.ts b/models/communication/src/types.ts
index 7de5ec70ac..08d8df9e7d 100644
--- a/models/communication/src/types.ts
+++ b/models/communication/src/types.ts
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { type Builder, Model, TypeAny, TypeNumber } from '@hcengineering/model'
+import { ArrOf, type Builder, Model, TypeAny, TypeNumber, TypeRef } from '@hcengineering/model'
import core, { TAttachedDoc, TConfiguration, TDoc } from '@hcengineering/model-core'
import { type Class, type Domain, DOMAIN_MODEL, type Ref } from '@hcengineering/core'
import { type Asset, type IntlString } from '@hcengineering/platform'
@@ -25,17 +25,19 @@ import {
type Poll,
type CustomActivityPresenter,
type GuestCommunicationSettings,
- type AppletGetTitleFnResource
+ type AppletGetTitleFnResource,
+ MessagesNavigationAnchors
} from '@hcengineering/communication'
import { PaletteColorIndexes } from '@hcengineering/ui/src/colors'
import { type AppletType } from '@hcengineering/communication-types'
-import { createSystemType } from '@hcengineering/model-card'
+import card, { createSystemType } from '@hcengineering/model-card'
import type { AnyComponent } from '@hcengineering/ui'
-import { type PersonSpace } from '@hcengineering/contact'
-
-import communication from './plugin'
+import contact, { type PersonSpace } from '@hcengineering/contact'
import { type Card, type MasterTag } from '@hcengineering/card'
import { DOMAIN_SETTING } from '@hcengineering/setting'
+import view from '@hcengineering/model-view'
+
+import communication from './plugin'
export const DOMAIN_POLL = 'poll' as Domain
@@ -86,6 +88,48 @@ export class TGuestCommunicationSettings extends TConfiguration implements Guest
export function buildTypes (builder: Builder): void {
builder.createModel(TMessageAction, TApplet, TPollAnswer, TCustomActivityPresenter, TGuestCommunicationSettings)
+ defineDirect(builder)
+ definePoll(builder)
+}
+
+function defineDirect (builder: Builder): void {
+ createSystemType(
+ builder,
+ communication.type.Direct,
+ contact.icon.Contacts,
+ communication.string.Direct,
+ communication.string.Directs,
+ {
+ defaultSection: communication.ids.CardMessagesSection,
+ defaultNavigation: MessagesNavigationAnchors.LatestMessages
+ },
+ PaletteColorIndexes.Lavander
+ )
+
+ builder.createDoc(core.class.Attribute, core.space.Model, {
+ name: 'members',
+ readonly: true, // TODO: remove
+ attributeOf: communication.type.Direct,
+ type: ArrOf(TypeRef(contact.class.Person)),
+ label: communication.string.Members
+ })
+
+ builder.mixin(communication.type.Direct, core.class.Class, view.mixin.ObjectIcon, {
+ component: communication.component.DirectIcon
+ })
+ builder.mixin(communication.type.Direct, core.class.Class, card.mixin.CreateCardExtension, {
+ component: communication.component.CreateDirect,
+ canCreate: communication.function.CanCreateDirect,
+ disableTitle: true,
+ hideSpace: true
+ })
+
+ builder.mixin(communication.type.Direct, core.class.Class, view.mixin.IgnoreActions, {
+ actions: [view.action.Delete]
+ })
+}
+
+function definePoll (builder: Builder): void {
createSystemType(
builder,
communication.type.Poll,
diff --git a/models/server-card/package.json b/models/server-card/package.json
index eb29b579b9..7bdeb65303 100644
--- a/models/server-card/package.json
+++ b/models/server-card/package.json
@@ -33,6 +33,7 @@
"@hcengineering/model": "^0.6.11",
"@hcengineering/platform": "^0.6.11",
"@hcengineering/card": "^0.6.0",
+ "@hcengineering/communication": "^0.6.0",
"@hcengineering/server-card": "^0.6.0",
"@hcengineering/server-core": "^0.6.1"
}
diff --git a/models/server-card/src/index.ts b/models/server-card/src/index.ts
index 0d4d909e3e..9008fbee70 100644
--- a/models/server-card/src/index.ts
+++ b/models/server-card/src/index.ts
@@ -19,6 +19,7 @@ import core from '@hcengineering/core'
import serverCore from '@hcengineering/server-core'
import serverCard from '@hcengineering/server-card'
import card from '@hcengineering/card'
+import communication from '@hcengineering/communication'
export { serverCardId } from '@hcengineering/server-card'
@@ -85,6 +86,24 @@ export function createModel (builder: Builder): void {
}
})
+ builder.createDoc(serverCore.class.Trigger, core.space.Model, {
+ trigger: serverCard.trigger.OnDirectCreate,
+ isAsync: false,
+ txMatch: {
+ _class: core.class.TxCreateDoc,
+ objectClass: communication.type.Direct
+ }
+ })
+
+ builder.createDoc(serverCore.class.Trigger, core.space.Model, {
+ trigger: serverCard.trigger.OnThreadCreate,
+ isAsync: false,
+ txMatch: {
+ _class: core.class.TxCreateDoc,
+ objectClass: card.class.Card
+ }
+ })
+
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverCard.trigger.OnCardUpdate,
isAsync: true,
diff --git a/packages/theme/styles/_lumia-colors.scss b/packages/theme/styles/_lumia-colors.scss
index dd669f1911..541ce1e218 100644
--- a/packages/theme/styles/_lumia-colors.scss
+++ b/packages/theme/styles/_lumia-colors.scss
@@ -43,7 +43,7 @@
/* Dark Theme */
.theme-dark {
- --global-ui-BackgroundColor: #A5BDFF0D;
+ --global-ui-BackgroundColor: 1530720D;
--global-ui-BorderColor: #A5BDFF1A;
--global-ui-hover-BackgroundColor: #A5BDFF1A;
--global-ui-active-BackgroundColor: #A5BDFF26;
@@ -134,6 +134,8 @@
--love-active-call-filter: blur(17px);
--global-offline-color: #d1d5de;
+
+ --avatar-counter-BackgroundColor: #141523
}
/* Light Theme */
@@ -229,4 +231,6 @@
--love-active-call-transform: scaleY(0.3) scaleX(0.42);
--global-offline-color: #5A667E;
+
+ --avatar-counter-BackgroundColor: #F1F1F5;
}
diff --git a/packages/theme/styles/panel.scss b/packages/theme/styles/panel.scss
index 1a827e7c23..44aca5c82e 100644
--- a/packages/theme/styles/panel.scss
+++ b/packages/theme/styles/panel.scss
@@ -1,14 +1,14 @@
//
// 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.
//
@@ -115,7 +115,7 @@
background-color: var(--theme-navpanel-selected);
border: 1px solid var(--global-subtle-ui-BorderColor);
border-radius: var(--extra-small-BorderRadius);
-
+
&.folder {
background-color: var(--theme-statusbar-color);
border-color: var(--global-surface-01-BorderColor);
@@ -131,7 +131,7 @@
color: var(--global-tertiary-TextColor);
border-radius: var(--extra-small-BorderRadius);
}
- &__tools {
+ &__tools, &__actions {
display: none;
align-items: center;
flex-shrink: 0;
@@ -153,6 +153,11 @@
&:hover {
.hulyNavGroup-header__tools { display: flex; }
}
+
+ &:hover {
+ .hulyNavGroup-header__actions { display: flex; }
+ }
+
&.showMenu,
&.highlighted,
&.selected {
@@ -392,7 +397,7 @@
&__mobile {
flex-grow: 1;
}
-
+
&__main {
flex-grow: 2;
flex-basis: 760px;
@@ -474,7 +479,7 @@
&__aside {
width: 25%;
min-width: var(--panel-aside-width);
-
+
&.float {
position: absolute;
flex-direction: row;
@@ -576,7 +581,7 @@
width: 100%;
visibility: hidden;
}
-
+
.popupPanel-pageFooter {
@include watermark;
diff --git a/packages/ui/src/components/NavGroup.svelte b/packages/ui/src/components/NavGroup.svelte
index 45f8acb356..0bb1680e8e 100644
--- a/packages/ui/src/components/NavGroup.svelte
+++ b/packages/ui/src/components/NavGroup.svelte
@@ -155,6 +155,11 @@
{/if}
+ {#if $$slots.actions}
+
+ {/if}
{#if selected && type === 'nested-selectable'}
{/if}
diff --git a/packages/ui/src/components/NavItem.svelte b/packages/ui/src/components/NavItem.svelte
index 14b9bd4551..a9edbd175b 100644
--- a/packages/ui/src/components/NavItem.svelte
+++ b/packages/ui/src/components/NavItem.svelte
@@ -116,8 +116,10 @@
{/if}
{#if visibleIcon || (type === 'type-tag' && color)}
-
- {#if type !== 'type-tag' && visibleIcon}
+
+ {#if $$slots.icon}
+
+ {:else if type !== 'type-tag' && visibleIcon}
{:else if type === 'type-tag'}
diff --git a/plugins/card-assets/lang/cs.json b/plugins/card-assets/lang/cs.json
index c7074ef264..860a67b973 100644
--- a/plugins/card-assets/lang/cs.json
+++ b/plugins/card-assets/lang/cs.json
@@ -39,6 +39,7 @@
"Labels": "Štítky",
"Properties": "Vlastnosti",
"NoChildren": "Žádné děti",
- "ConfigDescription": "Rozšíření pro správu znalostí ve stylu databáze."
+ "ConfigDescription": "Rozšíření pro správu znalostí ve stylu databáze.",
+ "AddCollaborators": "Přidat spolupracovníky"
}
-}
\ No newline at end of file
+}
diff --git a/plugins/card-assets/lang/de.json b/plugins/card-assets/lang/de.json
index ed2e7bc770..b90952828c 100644
--- a/plugins/card-assets/lang/de.json
+++ b/plugins/card-assets/lang/de.json
@@ -39,6 +39,7 @@
"Labels": "Labels",
"Properties": "Eigenschaften",
"NoChildren": "Keine Kinder",
- "ConfigDescription": "Erweiterung für Datenbank-basiertes Wissensmanagement."
+ "ConfigDescription": "Erweiterung für Datenbank-basiertes Wissensmanagement.",
+ "AddCollaborators": "Mitarbeiter hinzufügen"
}
}
diff --git a/plugins/card-assets/lang/en.json b/plugins/card-assets/lang/en.json
index 29e648d738..466dc57be2 100644
--- a/plugins/card-assets/lang/en.json
+++ b/plugins/card-assets/lang/en.json
@@ -39,6 +39,7 @@
"Labels": "Labels",
"Properties": "Properties",
"NoChildren": "No children",
- "ConfigDescription": "Extension for database-style knowledge management."
+ "ConfigDescription": "Extension for database-style knowledge management.",
+ "AddCollaborators": "Add collaborators"
}
-}
\ No newline at end of file
+}
diff --git a/plugins/card-assets/lang/es.json b/plugins/card-assets/lang/es.json
index 45c80d7eff..6fa79d1980 100644
--- a/plugins/card-assets/lang/es.json
+++ b/plugins/card-assets/lang/es.json
@@ -39,6 +39,7 @@
"Labels": "Etiquetas",
"Properties": "Propiedades",
"NoChildren": "No hay hijos",
- "ConfigDescription": "Extensión para la gestión del conocimiento al estilo de una base de datos."
+ "ConfigDescription": "Extensión para la gestión del conocimiento al estilo de una base de datos.",
+ "AddCollaborators": "Agregar colaboradores"
}
-}
\ No newline at end of file
+}
diff --git a/plugins/card-assets/lang/fr.json b/plugins/card-assets/lang/fr.json
index a9a875f380..93ba3ae1e8 100644
--- a/plugins/card-assets/lang/fr.json
+++ b/plugins/card-assets/lang/fr.json
@@ -39,6 +39,7 @@
"Labels": "Étiquettes",
"Properties": "Propriétés",
"NoChildren": "Pas d'enfants",
- "ConfigDescription": "Extension pour la gestion des connaissances de style base de données."
+ "ConfigDescription": "Extension pour la gestion des connaissances de style base de données.",
+ "AddCollaborators": "Ajouter des collaborateurs"
}
-}
\ No newline at end of file
+}
diff --git a/plugins/card-assets/lang/it.json b/plugins/card-assets/lang/it.json
index 33b67b254d..f76cdfcfa0 100644
--- a/plugins/card-assets/lang/it.json
+++ b/plugins/card-assets/lang/it.json
@@ -39,6 +39,7 @@
"Properties": "Proprietà",
"NoChildren": "Nessun figlio",
"NumberTypes": "{count, plural, one {# tipo} other {# tipi}}",
- "ConfigDescription": "Estensione per la gestione della conoscenza in stile database."
+ "ConfigDescription": "Estensione per la gestione della conoscenza in stile database.",
+ "AddCollaborators": "Aggiungi collaboratori"
}
-}
\ No newline at end of file
+}
diff --git a/plugins/card-assets/lang/ja.json b/plugins/card-assets/lang/ja.json
index 0fb052e535..5f7aae6e84 100644
--- a/plugins/card-assets/lang/ja.json
+++ b/plugins/card-assets/lang/ja.json
@@ -39,6 +39,7 @@
"Labels": "ラベル",
"Properties": "プロパティ",
"NoChildren": "子がありません",
- "ConfigDescription": "データベーススタイルのナレッジマネジメント用の拡張機能。"
+ "ConfigDescription": "データベーススタイルのナレッジマネジメント用の拡張機能。",
+ "AddCollaborators": "コラボレーターを追加"
}
-}
\ No newline at end of file
+}
diff --git a/plugins/card-assets/lang/pt.json b/plugins/card-assets/lang/pt.json
index a63dac2be1..b3a249a991 100644
--- a/plugins/card-assets/lang/pt.json
+++ b/plugins/card-assets/lang/pt.json
@@ -39,6 +39,7 @@
"Labels": "Etiquetas",
"Properties": "Propriedades",
"NoChildren": "Sem filhos",
- "ConfigDescription": "Extensão para gerenciamento de conhecimento no estilo de banco de dados."
+ "ConfigDescription": "Extensão para gerenciamento de conhecimento no estilo de banco de dados.",
+ "AddCollaborators": "Adicionar colaboradores"
}
-}
\ No newline at end of file
+}
diff --git a/plugins/card-assets/lang/ru.json b/plugins/card-assets/lang/ru.json
index f9f58c848f..038a7bf414 100644
--- a/plugins/card-assets/lang/ru.json
+++ b/plugins/card-assets/lang/ru.json
@@ -39,6 +39,7 @@
"Labels": "Метки",
"Properties": "Свойства",
"NoChildren": "Нет потомков",
- "ConfigDescription": "Расширение для управления знаниями в стиле базы данных."
+ "ConfigDescription": "Расширение для управления знаниями в стиле базы данных.",
+ "AddCollaborators": "Добавить сотрудников"
}
-}
\ No newline at end of file
+}
diff --git a/plugins/card-assets/lang/zh.json b/plugins/card-assets/lang/zh.json
index 361139770b..add5438230 100644
--- a/plugins/card-assets/lang/zh.json
+++ b/plugins/card-assets/lang/zh.json
@@ -39,6 +39,7 @@
"Labels": "标签",
"Properties": "属性",
"NoChildren": "没有子级",
- "ConfigDescription": "用于数据库风格知识管理的扩展。"
+ "ConfigDescription": "用于数据库风格知识管理的扩展。",
+ "AddCollaborators": "添加协作者"
}
-}
\ No newline at end of file
+}
diff --git a/plugins/card-resources/src/components/CardCollaborators.svelte b/plugins/card-resources/src/components/CardCollaborators.svelte
new file mode 100644
index 0000000000..8c822d3f2e
--- /dev/null
+++ b/plugins/card-resources/src/components/CardCollaborators.svelte
@@ -0,0 +1,110 @@
+
+
+
+
+
+ dispatch('add')}
+ />
+
+
+ {#each employees as employee, index (employee._id)}
+
+
+
+ {#if !disableRemoveFor.includes(employee._id)}
+
+ {
+ dispatch('remove', employee._id)
+ }}
+ />
+
+ {/if}
+
+
+ {/each}
+
+
+
+
diff --git a/plugins/card-resources/src/components/CardIcon.svelte b/plugins/card-resources/src/components/CardIcon.svelte
index 760aedb831..0852083ed4 100644
--- a/plugins/card-resources/src/components/CardIcon.svelte
+++ b/plugins/card-resources/src/components/CardIcon.svelte
@@ -15,7 +15,7 @@
-{#if editable}
+{#if iconMixin && iconMixin._id !== card.class.Card}
+
+{:else if editable}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0 && _space != null}
+ onCancel={handleCancel}
+ on:close
+>
+
+
+
+ {#if space == null && !(extension?.hideSpace ?? false)}
+
+
+
+
+ {/if}
+
+
{
+ collaborators = collaborators.filter((id) => id !== ev.detail)
+ }}
+ />
+
+ {#if extension?.component}
+
+ {/if}
+
+
+
+
diff --git a/plugins/card-resources/src/components/navigator-next/NavigatorCard.svelte b/plugins/card-resources/src/components/navigator-next/NavigatorCard.svelte
index 5fe7997f8d..52a69406c9 100644
--- a/plugins/card-resources/src/components/navigator-next/NavigatorCard.svelte
+++ b/plugins/card-resources/src/components/navigator-next/NavigatorCard.svelte
@@ -27,6 +27,7 @@
import cardPlugin from '../../plugin'
import { CardsNavigatorConfig } from '../../types'
import { getCardIconInfo } from '../../utils'
+ import { CardIcon } from '../../index'
export let type: Ref | undefined = undefined
export let card: Card
@@ -110,6 +111,9 @@
}}
on:contextmenu
>
+
+
+
{#each actions as action}
{#if action.icon}
diff --git a/plugins/card-resources/src/components/navigator-next/NavigatorType.svelte b/plugins/card-resources/src/components/navigator-next/NavigatorType.svelte
index 990aa42d43..cead7c301c 100644
--- a/plugins/card-resources/src/components/navigator-next/NavigatorType.svelte
+++ b/plugins/card-resources/src/components/navigator-next/NavigatorType.svelte
@@ -15,15 +15,16 @@
{#if level > -1}
@@ -95,7 +107,7 @@
isFold
visible={active}
type="selectable-header"
- actions={getActions()}
+ {actions}
on:click={(e) => {
e.stopPropagation()
e.preventDefault()
@@ -104,6 +116,22 @@
>
+
+ {#each actions as action}
+ {
+ e.stopPropagation()
+ e.preventDefault()
+ void action.action(action.props, e)
+ }}
+ />
+ {/each}
+
diff --git a/plugins/card-resources/src/plugin.ts b/plugins/card-resources/src/plugin.ts
index ee9dedf47c..c95f4c8057 100644
--- a/plugins/card-resources/src/plugin.ts
+++ b/plugins/card-resources/src/plugin.ts
@@ -99,6 +99,7 @@ export default mergeIds(cardId, card, {
CreateSpace: '' as IntlString,
NumberTypes: '' as IntlString,
Properties: '' as IntlString,
- NoChildren: '' as IntlString
+ NoChildren: '' as IntlString,
+ AddCollaborators: '' as IntlString
}
})
diff --git a/plugins/card-resources/src/types.ts b/plugins/card-resources/src/types.ts
index 7b6f8758b3..49eebc6df2 100644
--- a/plugins/card-resources/src/types.ts
+++ b/plugins/card-resources/src/types.ts
@@ -20,7 +20,7 @@ interface BaseNavigatorConfig {
types: Array[>
groupBySpace?: boolean
savedViews?: boolean
- allowCreate?: boolean // for now works only when groupBySpace is true
+ allowCreate?: boolean
preorder?: Array<{ type: Ref], order: number }>
}
diff --git a/plugins/card-resources/src/utils.ts b/plugins/card-resources/src/utils.ts
index 9494d173d5..ca06f49ad9 100644
--- a/plugins/card-resources/src/utils.ts
+++ b/plugins/card-resources/src/utils.ts
@@ -19,7 +19,10 @@ import core, {
type Doc,
type DocumentQuery,
fillDefaults,
+ generateId,
type Hierarchy,
+ makeCollabId,
+ type Markup,
type MarkupBlobRef,
type Ref,
type RelatedDocument,
@@ -28,7 +31,13 @@ import core, {
type TxOperations,
type WithLookup
} from '@hcengineering/core'
-import { getClient, IconWithEmoji, MessageBox, type ObjectSearchResult } from '@hcengineering/presentation'
+import {
+ createMarkup,
+ getClient,
+ IconWithEmoji,
+ MessageBox,
+ type ObjectSearchResult
+} from '@hcengineering/presentation'
import {
getCurrentResolvedLocation,
getPanelURI,
@@ -45,6 +54,7 @@ import { translate } from '@hcengineering/platform'
import { makeRank } from '@hcengineering/rank'
import { Analytics } from '@hcengineering/analytics'
import { createWidgetTab } from '@hcengineering/workbench-resources'
+import { EmptyMarkup, isEmptyMarkup } from '@hcengineering/text'
import CardSearchItem from './components/CardSearchItem.svelte'
import CreateSpace from './components/navigator/CreateSpace.svelte'
@@ -189,23 +199,35 @@ const toCardObjectSearchResult = (e: WithLookup): ObjectSearchResult => ({
component: CardSearchItem
})
-export async function createCard (type: Ref, space: Ref): Promise[> {
+export async function createCard (
+ type: Ref],
+ space: Ref,
+ data: Partial> = {},
+ contentMarkup: Markup = EmptyMarkup,
+ id?: Ref
+): Promise[> {
const client = getClient()
const hierarchy = client.getHierarchy()
const lastOne = await client.findOne(card.class.Card, {}, { sort: { rank: SortingOrder.Descending } })
- const title = await translate(card.string.Card, {})
+ const title = data.title ?? (await translate(card.string.Card, {}))
- const data: Data] = {
+ const _id = id ?? generateId()
+ const content = isEmptyMarkup(contentMarkup)
+ ? ('' as MarkupBlobRef)
+ : await createMarkup(makeCollabId(type, _id, 'content'), contentMarkup)
+
+ const _data: Data = {
+ parentInfo: [],
+ blobs: {},
+ ...data,
title,
rank: makeRank(lastOne?.rank, undefined),
- content: '' as MarkupBlobRef,
- parentInfo: [],
- blobs: {}
+ content
}
- const filledData = fillDefaults(hierarchy, data, type)
+ const filledData = fillDefaults(hierarchy, _data, type)
- const _id = await client.createDoc(type, space, filledData)
+ await client.createDoc(type, space, filledData, _id)
Analytics.handleEvent(CardEvents.CardCreated)
return _id
diff --git a/plugins/card/src/index.ts b/plugins/card/src/index.ts
index 5159b9d774..9c34b3668a 100644
--- a/plugins/card/src/index.ts
+++ b/plugins/card/src/index.ts
@@ -16,6 +16,7 @@ import {
Blobs,
Class,
CollectionSize,
+ Data,
Doc,
Domain,
MarkupBlobRef,
@@ -98,6 +99,16 @@ export interface FavoriteCard extends Preference {
application: string
}
+export interface CreateCardExtension extends MasterTag {
+ component?: AnyComponent
+ canCreate?: CanCreateCardResource
+ disableTitle?: boolean
+ hideSpace?: boolean
+}
+
+export type CanCreateCardFn = (space: Ref, data: Partial>) => Promise>
+export type CanCreateCardResource = Resource
+
/**
* @public
*/
@@ -120,7 +131,8 @@ const cardPlugin = plugin(cardId, {
FavoriteCard: '' as Ref>
},
mixin: {
- CardViewDefaults: '' as Ref>
+ CardViewDefaults: '' as Ref>,
+ CreateCardExtension: '' as Ref>
},
space: {
Default: '' as Ref
diff --git a/plugins/chat-resources/package.json b/plugins/chat-resources/package.json
index 6ec6722416..edcde98e2f 100644
--- a/plugins/chat-resources/package.json
+++ b/plugins/chat-resources/package.json
@@ -43,6 +43,7 @@
"@hcengineering/card-resources": "^0.6.0",
"@hcengineering/chat": "^0.6.0",
"@hcengineering/communication-types": "^0.1.0",
+ "@hcengineering/communication": "^0.6.0",
"@hcengineering/contact": "^0.6.24",
"@hcengineering/contact-resources": "^0.6.0",
"@hcengineering/core": "^0.6.32",
diff --git a/plugins/chat-resources/src/components/ChatNavigation.svelte b/plugins/chat-resources/src/components/ChatNavigation.svelte
index 5a3fa557ae..666e161383 100644
--- a/plugins/chat-resources/src/components/ChatNavigation.svelte
+++ b/plugins/chat-resources/src/components/ChatNavigation.svelte
@@ -19,6 +19,7 @@
import { SubscriptionLabelID } from '@hcengineering/communication-types'
import chat, { chatId } from '@hcengineering/chat'
import { Navigator } from '@hcengineering/card-resources'
+ import communication from '@hcengineering/communication'
export let card: Card | undefined = undefined
export let type: Ref | undefined = undefined
@@ -36,11 +37,16 @@
labelFilter: [SubscriptionLabelID],
preorder: [
{ type: chat.masterTag.Thread, order: 1 },
- { type: chat.masterTag.Channel, order: 2 }
+ { type: chat.masterTag.Channel, order: 2 },
+ { type: communication.type.Direct, order: 3 }
],
- fixedTypes: [chat.masterTag.Thread, chat.masterTag.Channel],
+ fixedTypes: [chat.masterTag.Thread, chat.masterTag.Channel, communication.type.Direct],
+ specialSorting: {
+ [communication.type.Direct]: 'alphabetical'
+ },
+ allowCreate: true,
defaultSorting: 'recent',
- lookback: '1w',
+ lookback: '2w',
showTypeIcon: false,
showCardIcon: true
}}
diff --git a/plugins/communication-assets/lang/cs.json b/plugins/communication-assets/lang/cs.json
index 0c826e1d46..2189fce06f 100644
--- a/plugins/communication-assets/lang/cs.json
+++ b/plugins/communication-assets/lang/cs.json
@@ -85,6 +85,9 @@
"Voted": "Hlasováno",
"VotedFor": "Hlasováno pro",
"RevokedVote": "Zrušeno hlasování",
- "AnonymousQuiz": "Anonymní test"
+ "AnonymousQuiz": "Anonymní test",
+ "Direct": "Přímý",
+ "Directs": "Přímé",
+ "Members": "Členové"
}
}
diff --git a/plugins/communication-assets/lang/de.json b/plugins/communication-assets/lang/de.json
index 50b03bbd48..f762a8c6c5 100644
--- a/plugins/communication-assets/lang/de.json
+++ b/plugins/communication-assets/lang/de.json
@@ -85,6 +85,9 @@
"Voted": "Abgestimmt",
"VotedFor": "Für abgestimmt",
"RevokedVote": "Stimme widerrufen",
- "AnonymousQuiz": "Anonyme quiz"
+ "AnonymousQuiz": "Anonyme quiz",
+ "Direct": "Direkt",
+ "Directs": "Direkte",
+ "Members": "Mitglieder"
}
}
diff --git a/plugins/communication-assets/lang/en.json b/plugins/communication-assets/lang/en.json
index 78196f98a3..c0ea66a94c 100644
--- a/plugins/communication-assets/lang/en.json
+++ b/plugins/communication-assets/lang/en.json
@@ -85,6 +85,9 @@
"Voted": "Voted",
"VotedFor": "Voted for",
"RevokedVote": "Revoked vote",
- "AnonymousQuiz": "Anonymous quiz"
+ "AnonymousQuiz": "Anonymous quiz",
+ "Direct": "Direct",
+ "Directs": "Directs",
+ "Members": "Members"
}
}
diff --git a/plugins/communication-assets/lang/es.json b/plugins/communication-assets/lang/es.json
index 030f12b494..18e9443b83 100644
--- a/plugins/communication-assets/lang/es.json
+++ b/plugins/communication-assets/lang/es.json
@@ -85,6 +85,9 @@
"Voted": "Votado",
"VotedFor": "Votado por",
"RevokedVote": "Revocar voto",
- "AnonymousQuiz": "Quiz anónimo"
+ "AnonymousQuiz": "Quiz anónimo",
+ "Direct": "Directo",
+ "Directs": "Directos",
+ "Members": "Miembros"
}
}
diff --git a/plugins/communication-assets/lang/fr.json b/plugins/communication-assets/lang/fr.json
index 1650d3a0ed..c019ea190e 100644
--- a/plugins/communication-assets/lang/fr.json
+++ b/plugins/communication-assets/lang/fr.json
@@ -85,6 +85,9 @@
"Voted": "A voté",
"VotedFor": "A voté pour",
"RevokedVote": "Révoquer le vote",
- "AnonymousQuiz": "Quiz anonyme"
+ "AnonymousQuiz": "Quiz anonyme",
+ "Direct": "Direct",
+ "Directs": "Directs",
+ "Members": "Membres"
}
}
diff --git a/plugins/communication-assets/lang/it.json b/plugins/communication-assets/lang/it.json
index 46c49f30d1..7f38ba9c42 100644
--- a/plugins/communication-assets/lang/it.json
+++ b/plugins/communication-assets/lang/it.json
@@ -85,6 +85,9 @@
"Voted": "Votato",
"VotedFor": "Votato per",
"RevokedVote": "Revoca il voto",
- "AnonymousQuiz": "Quiz anonimo"
+ "AnonymousQuiz": "Quiz anonimo",
+ "Direct": "Diretto",
+ "Directs": "Diretti",
+ "Members": "Membri"
}
}
diff --git a/plugins/communication-assets/lang/ja.json b/plugins/communication-assets/lang/ja.json
index bb38be1b65..adf321fd5c 100644
--- a/plugins/communication-assets/lang/ja.json
+++ b/plugins/communication-assets/lang/ja.json
@@ -85,6 +85,9 @@
"Voted": "投票済み",
"VotedFor": "投票したユーザ",
"RevokedVote": "投票を取り消し",
- "AnonymousQuiz": "匿名クイズ"
+ "AnonymousQuiz": "匿名クイズ",
+ "Direct": "ダイレクト",
+ "Directs": "ダイレクト",
+ "Members": "メンバー"
}
}
diff --git a/plugins/communication-assets/lang/pt.json b/plugins/communication-assets/lang/pt.json
index 76286d1aee..fa82bfcf05 100644
--- a/plugins/communication-assets/lang/pt.json
+++ b/plugins/communication-assets/lang/pt.json
@@ -85,6 +85,9 @@
"Voted": "Votado",
"VotedFor": "Votado para",
"RevokedVote": "Revogar voto",
- "AnonymousQuiz": "Quiz anônimo"
+ "AnonymousQuiz": "Quiz anônimo",
+ "Direct": "Direto",
+ "Directs": "Diretos",
+ "Members": "Membros"
}
}
diff --git a/plugins/communication-assets/lang/ru.json b/plugins/communication-assets/lang/ru.json
index 5b2e6ec007..69bc6e7a91 100644
--- a/plugins/communication-assets/lang/ru.json
+++ b/plugins/communication-assets/lang/ru.json
@@ -85,6 +85,9 @@
"Voted": "Проголосовавшие",
"VotedFor": "Голосовал(а) за",
"RevokedVote": "Отменил(а) голос",
- "AnonymousQuiz": "Анонимная викторина"
+ "AnonymousQuiz": "Анонимная викторина",
+ "Direct": "Личные сообщения",
+ "Directs": "Личные сообщения",
+ "Members": "Участники"
}
}
diff --git a/plugins/communication-assets/lang/zh.json b/plugins/communication-assets/lang/zh.json
index c55df92fe1..1c5c3f407e 100644
--- a/plugins/communication-assets/lang/zh.json
+++ b/plugins/communication-assets/lang/zh.json
@@ -85,6 +85,9 @@
"Voted": "已投票",
"VotedFor": "投票于",
"RevokedVote": "撤销投票",
- "AnonymousQuiz": "匿名测验"
+ "AnonymousQuiz": "匿名测验",
+ "Direct": "私信",
+ "Directs": "私信",
+ "Members": "成员"
}
}
diff --git a/plugins/communication-resources/src/actions.ts b/plugins/communication-resources/src/actions.ts
index 68d18fa2fc..86215b6200 100644
--- a/plugins/communication-resources/src/actions.ts
+++ b/plugins/communication-resources/src/actions.ts
@@ -121,9 +121,7 @@ export async function attachCardToMessage (
},
type
)
- const apply = client.apply('create thread', undefined, true)
- await apply.createDoc(type, parentCard.space, data, threadCardID)
- await apply.commit()
+ await client.createDoc(type, parentCard.space, data, threadCardID)
if (author?.active === true && author?.personUuid !== undefined) {
await communicationClient.addCollaborators(threadCardID, type, [author.personUuid])
diff --git a/plugins/communication-resources/src/components/CreateDirect.svelte b/plugins/communication-resources/src/components/CreateDirect.svelte
new file mode 100644
index 0000000000..5df54bf06c
--- /dev/null
+++ b/plugins/communication-resources/src/components/CreateDirect.svelte
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/communication-resources/src/components/DirectIcon.svelte b/plugins/communication-resources/src/components/DirectIcon.svelte
new file mode 100644
index 0000000000..aa24223e09
--- /dev/null
+++ b/plugins/communication-resources/src/components/DirectIcon.svelte
@@ -0,0 +1,183 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{#if persons.length === 0}
+
+{:else if withoutMe.length === 1 || persons.length === 1}
+ {@const p = withoutMe[0] ?? persons[0]}
+
+{:else if persons.length > 1 && (size === 'medium' || avatarSize === 'medium')}
+
+ {#each persons.slice(0, visiblePersons - 1) as person}
+
+ {/each}
+ {#if persons.length === visiblePersons}
+ {@const person = persons[persons.length - 1]}
+
+ {:else if persons.length > visiblePersons}
+
+ +{persons.length - visiblePersons + 1}
+
+ {:else if persons.length < visiblePersons}
+ {#each Array(visiblePersons - persons.length) as _}
+
+ {/each}
+ {/if}
+
+{:else if persons.length > 1}
+
+
+
+
+
+
+
+
+
+
+ {#if persons.length > 9}
+ 9+
+ {:else}
+ {persons.length}
+ {/if}
+
+
+{/if}
+
+
diff --git a/plugins/communication-resources/src/index.ts b/plugins/communication-resources/src/index.ts
index 7cf211585e..7854b316cd 100644
--- a/plugins/communication-resources/src/index.ts
+++ b/plugins/communication-resources/src/index.ts
@@ -21,8 +21,10 @@ import CreatePoll from './components/poll/CreatePoll.svelte'
import PollPreview from './components/poll/PollPreview.svelte'
import UserVoteActivityPresenter from './components/poll/UserVoteActivityPresenter.svelte'
import UserVotesPresenter from './components/poll/UserVotesPresenter.svelte'
+import DirectIcon from './components/DirectIcon.svelte'
+import CreateDirect from './components/CreateDirect.svelte'
-import { unsubscribe, subscribe, canSubscribe, canUnsubscribe } from './utils'
+import { unsubscribe, subscribe, canSubscribe, canUnsubscribe, canCreateDirect } from './utils'
import {
addReaction,
canCreateCard,
@@ -49,7 +51,9 @@ export { default as ActivityMessageViewer } from './components/message/ActivityM
export default async (): Promise => ({
component: {
- CardMessagesSection
+ CardMessagesSection,
+ DirectIcon,
+ CreateDirect
},
poll: {
PollPresenter,
@@ -81,6 +85,7 @@ export default async (): Promise => ({
CanShowOriginalMessage: canShowOriginalMessage,
CanEditMessage: canEditMessage,
CanRemoveMessage: canRemoveMessage,
- CanCreateCard: canCreateCard
+ CanCreateCard: canCreateCard,
+ CanCreateDirect: canCreateDirect
}
})
diff --git a/plugins/communication-resources/src/plugin.ts b/plugins/communication-resources/src/plugin.ts
index 433317374e..19fa7bba89 100644
--- a/plugins/communication-resources/src/plugin.ts
+++ b/plugins/communication-resources/src/plugin.ts
@@ -22,10 +22,13 @@ import communication, {
} from '@hcengineering/communication'
import { type AnyComponent } from '@hcengineering/ui'
import { type Ref } from '@hcengineering/core'
+import { type CanCreateCardResource } from '@hcengineering/card'
export default mergeIds(communicationId, communication, {
component: {
- CardMessagesSection: '' as AnyComponent
+ CardMessagesSection: '' as AnyComponent,
+ DirectIcon: '' as AnyComponent,
+ CreateDirect: '' as AnyComponent
},
poll: {
PollPresenter: '' as AnyComponent,
@@ -81,7 +84,10 @@ export default mergeIds(communicationId, communication, {
EditMessage: '' as IntlString,
RemoveMessage: '' as IntlString,
CreateCard: '' as IntlString,
- MessageAlreadyHasCardAttached: '' as IntlString
+ MessageAlreadyHasCardAttached: '' as IntlString,
+ Direct: '' as IntlString,
+ Directs: '' as IntlString,
+ Members: '' as IntlString
},
messageActionImpl: {
AddReaction: '' as MessageActionFunctionResource,
@@ -107,6 +113,7 @@ export default mergeIds(communicationId, communication, {
CanShowOriginalMessage: '' as MessageActionVisibilityTesterResource,
CanEditMessage: '' as MessageActionVisibilityTesterResource,
CanRemoveMessage: '' as MessageActionVisibilityTesterResource,
- CanCreateCard: '' as MessageActionVisibilityTesterResource
+ CanCreateCard: '' as MessageActionVisibilityTesterResource,
+ CanCreateDirect: '' as CanCreateCardResource
}
})
diff --git a/plugins/communication-resources/src/utils.ts b/plugins/communication-resources/src/utils.ts
index 2fd4a5893b..5294823628 100644
--- a/plugins/communication-resources/src/utils.ts
+++ b/plugins/communication-resources/src/utils.ts
@@ -11,9 +11,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { canDisplayLinkPreview, fetchLinkPreviewDetails, getCommunicationClient } from '@hcengineering/presentation'
+import {
+ canDisplayLinkPreview,
+ fetchLinkPreviewDetails,
+ getClient,
+ getCommunicationClient
+} from '@hcengineering/presentation'
import { type Card } from '@hcengineering/card'
-import { AccountRole, getCurrentAccount, type Markup } from '@hcengineering/core'
+import { AccountRole, type Data, getCurrentAccount, type Ref, type Space, type Markup } from '@hcengineering/core'
import { getMetadata, translate } from '@hcengineering/platform'
import { addNotification, languageStore, NotificationSeverity, showPopup } from '@hcengineering/ui'
import { type LinkPreviewParams, type Message } from '@hcengineering/communication-types'
@@ -28,6 +33,8 @@ import { type TextInputAction } from './types'
import { guestCommunicationAllowedCards } from './stores'
import { get } from 'svelte/store'
import view from '@hcengineering/view'
+import { type Direct } from '@hcengineering/communication'
+import { type Employee } from '@hcengineering/contact'
export async function unsubscribe (card: Card): Promise {
const client = getCommunicationClient()
@@ -160,3 +167,31 @@ export async function showForbidden (): Promise {
NotificationSeverity.Info
)
}
+
+export async function canCreateDirect (space: Ref, data: Partial>): Promise> {
+ const members = data.members ?? []
+ if (members.length === 0) return false
+
+ const client = getClient()
+
+ if (members.length > 2) {
+ return true
+ }
+
+ const myDirects = await client.findAll(communication.type.Direct, { space })
+ const direct = myDirects.find((it) => {
+ const directMembers = new Set(it.members)
+ const createMembers = new Set(members)
+ if (directMembers.size !== createMembers.size) return false
+ for (const item of directMembers) {
+ if (!createMembers.has(item as Ref)) return false
+ }
+ return true
+ })
+
+ if (direct != null) {
+ return direct._id
+ }
+
+ return true
+}
diff --git a/plugins/communication/src/direct.ts b/plugins/communication/src/direct.ts
new file mode 100644
index 0000000000..a663789c96
--- /dev/null
+++ b/plugins/communication/src/direct.ts
@@ -0,0 +1,21 @@
+// Copyright © 2025 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.
+
+import { Card } from '@hcengineering/card'
+import { Ref } from '@hcengineering/core'
+import { Person } from '@hcengineering/contact'
+
+export interface Direct extends Card {
+ // TODO: Do we neet it? Can we just reuse collaborators?
+ members?: Ref[]
+}
diff --git a/plugins/communication/src/index.ts b/plugins/communication/src/index.ts
index efaba1a071..307a86a60e 100644
--- a/plugins/communication/src/index.ts
+++ b/plugins/communication/src/index.ts
@@ -33,7 +33,8 @@ export default plugin(communicationId, {
GuestCommunicationSettings: '' as Ref>
},
type: {
- Poll: '' as Ref
+ Poll: '' as Ref,
+ Direct: '' as Ref
},
icon: {
Bell: '' as Asset,
diff --git a/plugins/communication/src/poll.ts b/plugins/communication/src/poll.ts
new file mode 100644
index 0000000000..4bc360a18b
--- /dev/null
+++ b/plugins/communication/src/poll.ts
@@ -0,0 +1,34 @@
+// Copyright © 2025 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.
+
+import { AttachedDoc, Ref } from '@hcengineering/core'
+import { PersonSpace } from '@hcengineering/contact'
+import { AccountID, MessageID } from '@hcengineering/communication-types'
+import { Card } from '@hcengineering/card'
+
+export interface PollAnswer extends AttachedDoc {
+ options: string[]
+ space: Ref
+}
+
+export interface UserVote {
+ account: AccountID
+ options: { id: string, label: string, votedAt: Date }[]
+}
+
+export interface Poll extends Card {
+ messageId: MessageID
+ totalVotes: number
+
+ userVotes?: UserVote[]
+}
diff --git a/plugins/communication/src/types.ts b/plugins/communication/src/types.ts
index 6fafb9e40c..e55fcfba21 100644
--- a/plugins/communication/src/types.ts
+++ b/plugins/communication/src/types.ts
@@ -25,6 +25,9 @@ import { Card, MasterTag } from '@hcengineering/card'
import { AnyComponent } from '@hcengineering/ui'
import { PersonSpace } from '@hcengineering/contact'
+export * from './poll'
+export * from './direct'
+
export enum MessagesNavigationAnchors {
ConversationStart = 'conversationStart',
LatestMessages = 'latestMessages'
diff --git a/plugins/contact-resources/src/components/Avatar.svelte b/plugins/contact-resources/src/components/Avatar.svelte
index 1578aff712..44888ece89 100644
--- a/plugins/contact-resources/src/components/Avatar.svelte
+++ b/plugins/contact-resources/src/components/Avatar.svelte
@@ -59,6 +59,7 @@
export let disabled: boolean = false
export let style: 'modern' | undefined = undefined
export let clickable: boolean = false
+ export let clipPath: string | undefined = undefined
export function pulse (): void {
avatarInst.pulse()
@@ -141,6 +142,7 @@
{disabled}
{style}
withStatus
+ {clipPath}
/>
{/if}
diff --git a/plugins/contact-resources/src/components/AvatarInstance.svelte b/plugins/contact-resources/src/components/AvatarInstance.svelte
index 96e16263a0..0cc4122151 100644
--- a/plugins/contact-resources/src/components/AvatarInstance.svelte
+++ b/plugins/contact-resources/src/components/AvatarInstance.svelte
@@ -34,6 +34,7 @@
export let adaptiveName: boolean = false
export let disabled: boolean = false
export let style: 'modern' | undefined = undefined
+ export let clipPath: string | undefined = undefined
function handleClick (): void {
dispatch('click')
@@ -79,6 +80,7 @@
class:withStatus
style:--border-color={bColor ?? 'var(--primary-button-default)'}
style:background-color={background}
+ style:clip-path={clipPath}
use:resizeObserver={(element) => {
fontSize = element.clientWidth * 0.6
}}
@@ -103,6 +105,7 @@
class:withStatus
style:--border-color={bColor ?? 'var(--primary-button-default)'}
style:background-color={background}
+ style:clip-path={clipPath}
on:click={handleClick}
>
{#if url && !imgError}
diff --git a/pods/server/src/server.ts b/pods/server/src/server.ts
index 936338d86d..73c1505037 100644
--- a/pods/server/src/server.ts
+++ b/pods/server/src/server.ts
@@ -119,6 +119,8 @@ export function start (
findCollaborators: async () => [],
findNotifications: async () => [],
findLabels: async () => [],
+ findThreads: async () => [],
+ findPeers: async () => [],
unsubscribeQuery: async () => {},
event: async () => {
return {}
diff --git a/server-plugins/activity-resources/src/newActivity.ts b/server-plugins/activity-resources/src/newActivity.ts
index 5098fce6b9..320f7aa2be 100644
--- a/server-plugins/activity-resources/src/newActivity.ts
+++ b/server-plugins/activity-resources/src/newActivity.ts
@@ -31,7 +31,7 @@ export async function generateActivity (
): Promise
{
const { hierarchy } = control
- if (tx.space === core.space.DerivedTx) return
+ // if (tx.space === core.space.DerivedTx) return
if (
hierarchy.isDerived(tx.objectClass, activity.class.ActivityMessage) ||
@@ -43,6 +43,9 @@ export async function generateActivity (
switch (tx._class) {
case core.class.TxCreateDoc: {
const card = TxProcessor.createDoc2Doc(tx as TxCreateDoc)
+ if (card._class === 'chat:masterTag:Thread') {
+ break
+ }
await createMessages(tx, control, card)
break
}
diff --git a/server-plugins/card-resources/package.json b/server-plugins/card-resources/package.json
index 4c0117c009..78a0f7a01c 100644
--- a/server-plugins/card-resources/package.json
+++ b/server-plugins/card-resources/package.json
@@ -43,7 +43,9 @@
"@hcengineering/core": "^0.6.32",
"@hcengineering/platform": "^0.6.11",
"@hcengineering/server-core": "^0.6.1",
+ "@hcengineering/communication-types": "^0.1.0",
"@hcengineering/communication-sdk-types": "^0.1.0",
+ "@hcengineering/communication": "^0.6.0",
"@hcengineering/server-contact": "^0.6.1",
"@hcengineering/contact": "^0.6.24"
}
diff --git a/server-plugins/card-resources/src/index.ts b/server-plugins/card-resources/src/index.ts
index ae56a891de..357d25048c 100644
--- a/server-plugins/card-resources/src/index.ts
+++ b/server-plugins/card-resources/src/index.ts
@@ -20,10 +20,13 @@ import core, {
Data,
Doc,
fillDefaults,
+ generateId,
getDiffUpdate,
Mixin,
+ notEmpty,
OperationDomain,
Ref,
+ Space,
splitMixinUpdate,
Tx,
TxCreateDoc,
@@ -39,11 +42,17 @@ import {
AddCollaboratorsEvent,
CardEventType,
NotificationEventType,
+ PeerEventType,
RemoveCardEvent,
- UpdateCardTypeEvent
+ UpdateCardTypeEvent,
+ CreatePeerEvent,
+ ThreadPatchEvent,
+ MessageEventType
} from '@hcengineering/communication-sdk-types'
import { getEmployee, getPersonSpaces } from '@hcengineering/server-contact'
-import contact from '@hcengineering/contact'
+import contact, { Employee, Person } from '@hcengineering/contact'
+import communication, { Direct } from '@hcengineering/communication'
+import { CardPeer } from '@hcengineering/communication-types'
async function OnAttribute (ctx: TxCreateDoc[], control: TriggerControl): Promise {
const attr = TxProcessor.createDoc2Doc(ctx[0])
@@ -400,6 +409,192 @@ async function updateParentInfoName (
return res
}
+async function OnThreadCreate (ctx: TxCreateDoc[], control: TriggerControl): Promise {
+ const res: Tx[] = []
+ for (const tx of ctx) {
+ if (tx.space === core.space.DerivedTx) continue
+ const doc = TxProcessor.createDoc2Doc(tx)
+ const parent = doc.parentInfo?.[0]
+ if (parent == null) continue
+ if (!control.hierarchy.isDerived(parent._class, communication.type.Direct)) continue
+ const direct = (await control.findAll(control.ctx, parent._class, { _id: parent._id }, { limit: 1 }))[0] as Direct
+ if (direct == null) continue
+
+ res.push(...(await createThreadCardPeers(direct, doc, control)))
+ }
+
+ return res
+}
+
+async function createThreadCardPeers (direct: Direct, doc: Card, control: TriggerControl): Promise {
+ const res: Tx[] = []
+ const cardIds = new Map[, Ref]>([[doc._id, doc.space]])
+ const members = direct.members ?? []
+ if (members.length === 0) return []
+
+ const thread = (
+ await control.domainRequest(control.ctx, 'communication' as OperationDomain, {
+ findThreads: { params: { threadId: doc._id } }
+ })
+ ).value[0]
+ if (thread === undefined) return []
+
+ const messageId = thread.messageId
+ const directPeer = (
+ (
+ await control.domainRequest(control.ctx, 'communication' as OperationDomain, {
+ findPeers: { params: { kind: 'card', cardId: direct._id } }
+ })
+ ).value as CardPeer[]
+ )[0]
+
+ const personSpaces = (await getPersonSpaces(control)).filter(
+ (it) => it._id !== doc.space && members.includes(it.person)
+ )
+ if (personSpaces.length === 0) return []
+ const accounts = (
+ await control.findAll(control.ctx, contact.mixin.Employee, {
+ _id: { $in: personSpaces.map((it) => it.person) as Ref[] }
+ })
+ )
+ .map((it) => it.personUuid)
+ .filter(notEmpty)
+ if (accounts.length === 0) return []
+
+ // TODO: create directs in person_workspace
+ for (const personSpace of personSpaces) {
+ const _id = generateId()
+ const _class = doc._class
+ cardIds.set(_id, personSpace._id)
+ res.push(
+ control.txFactory.createTxCreateDoc(
+ _class,
+ personSpace._id,
+ {
+ ...doc
+ },
+ _id
+ )
+ )
+
+ const parentDirect = directPeer?.members?.find((m) => m.extra?.space === personSpace._id)
+
+ if (parentDirect !== undefined) {
+ const threadPatchEvent: ThreadPatchEvent = {
+ type: MessageEventType.ThreadPatch,
+ cardId: parentDirect.cardId,
+ messageId,
+ operation: {
+ opcode: 'attach',
+ threadId: _id,
+ threadType: _class
+ },
+ socialId: doc.modifiedBy
+ }
+ await control.domainRequest(control.ctx, 'communication' as OperationDomain, {
+ event: threadPatchEvent
+ })
+ }
+ }
+
+ if (cardIds.size > 1) {
+ const group = generateId()
+ for (const [cardId, spaceId] of cardIds.entries()) {
+ const event: CreatePeerEvent = {
+ type: PeerEventType.CreatePeer,
+ workspaceId: control.workspace.uuid, // TODO: person_workspace
+ cardId,
+ kind: 'card',
+ value: group,
+ extra: { space: spaceId },
+ date: new Date(doc.modifiedOn)
+ }
+ await control.domainRequest(control.ctx, 'communication' as OperationDomain, { event })
+ }
+ }
+
+ return res
+}
+
+async function createDirectCardPeers (doc: Card, members: Ref[], control: TriggerControl): Promise {
+ const res: Tx[] = []
+ const cardIds = new Map[, Ref]>([[doc._id, doc.space]])
+ if (members.length === 0) return []
+
+ const personSpaces = (await getPersonSpaces(control)).filter(
+ (it) => it._id !== doc.space && members.includes(it.person)
+ )
+ if (personSpaces.length === 0) return []
+ const accounts = (
+ await control.findAll(control.ctx, contact.mixin.Employee, {
+ _id: { $in: personSpaces.map((it) => it.person) as Ref[] }
+ })
+ )
+ .map((it) => it.personUuid)
+ .filter(notEmpty)
+ if (accounts.length === 0) return []
+
+ // TODO: create directs in person_workspace
+ for (const personSpace of personSpaces) {
+ const _id = generateId()
+ const _class = doc._class
+ cardIds.set(_id, personSpace._id)
+ res.push(
+ control.txFactory.createTxCreateDoc(
+ _class,
+ personSpace._id,
+ {
+ ...doc
+ },
+ _id
+ )
+ )
+
+ const event: AddCollaboratorsEvent = {
+ type: NotificationEventType.AddCollaborators,
+ cardId: _id,
+ cardType: _class,
+ collaborators: accounts,
+ socialId: doc.modifiedBy,
+ date: new Date(doc.modifiedOn + 1)
+ }
+
+ await control.domainRequest(control.ctx, 'communication' as OperationDomain, { event })
+ }
+
+ if (cardIds.size > 1) {
+ const group = generateId()
+ for (const [cardId, spaceId] of cardIds.entries()) {
+ const event: CreatePeerEvent = {
+ type: PeerEventType.CreatePeer,
+ workspaceId: control.workspace.uuid, // TODO: person_workspace
+ cardId,
+ kind: 'card',
+ value: group,
+ extra: { space: spaceId },
+ date: new Date(doc.modifiedOn)
+ }
+ await control.domainRequest(control.ctx, 'communication' as OperationDomain, { event })
+ }
+ }
+
+ return res
+}
+
+async function OnDirectCreate (ctx: TxCreateDoc[], control: TriggerControl): Promise {
+ const res: Tx[] = []
+
+ for (const tx of ctx) {
+ if (tx.space === core.space.DerivedTx) continue
+ const doc = TxProcessor.createDoc2Doc(tx)
+ const members = doc.members ?? []
+
+ res.push(...(await createDirectCardPeers(doc, members, control)))
+ }
+
+ return res
+}
+
async function OnCardCreate (ctx: TxCreateDoc[], control: TriggerControl): Promise {
const createTx = ctx[0]
const doc = TxProcessor.createDoc2Doc(createTx)
@@ -515,6 +710,8 @@ export default async () => ({
OnCardRemove,
OnCardCreate,
OnCardUpdate,
- OnCardTag
+ OnCardTag,
+ OnDirectCreate,
+ OnThreadCreate
}
})
diff --git a/server-plugins/card/src/index.ts b/server-plugins/card/src/index.ts
index a2cc66353f..78df7c6e6e 100644
--- a/server-plugins/card/src/index.ts
+++ b/server-plugins/card/src/index.ts
@@ -33,6 +33,8 @@ export default plugin(serverCardId, {
OnTagRemove: '' as Resource,
OnMasterTagRemove: '' as Resource,
OnCardCreate: '' as Resource,
+ OnDirectCreate: '' as Resource,
+ OnThreadCreate: '' as Resource,
OnCardUpdate: '' as Resource,
OnCardTag: '' as Resource,
OnCardRemove: '' as Resource
diff --git a/server/server-pipeline/src/communication.ts b/server/server-pipeline/src/communication.ts
index 950e64cb73..97ab564529 100644
--- a/server/server-pipeline/src/communication.ts
+++ b/server/server-pipeline/src/communication.ts
@@ -129,10 +129,18 @@ export class CommunicationMiddleware extends BaseMiddleware implements Middlewar
const { params } = args.findLabels
return await this.communicationApi.findLabels(ctx, params)
}
+ if (args.findThreads !== undefined) {
+ const { params } = args.findThreads
+ return await this.communicationApi.findThreads(ctx, params)
+ }
if (args.findCollaborators !== undefined) {
const { params } = args.findCollaborators
return await this.communicationApi.findCollaborators(ctx, params)
}
+ if (args.findPeers !== undefined) {
+ const { params } = args.findPeers
+ return await this.communicationApi.findPeers(ctx, params)
+ }
if (args.unsubscribeQuery !== undefined) {
const { id } = args.unsubscribeQuery
await this.communicationApi.unsubscribeQuery(ctx, id)