diff --git a/models/all/src/__bumpversion.ts b/models/all/src/__bumpversion.ts index d5377baaa6..8c90e0f102 100644 --- a/models/all/src/__bumpversion.ts +++ b/models/all/src/__bumpversion.ts @@ -1,5 +1,5 @@ // -// Copyright © 2022 Anticrm Platform Contributors. +// 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 diff --git a/models/chunter/src/index.ts b/models/chunter/src/index.ts index febfc8e14f..4048bacb71 100644 --- a/models/chunter/src/index.ts +++ b/models/chunter/src/index.ts @@ -18,7 +18,7 @@ import type { Backlink, Channel, ChunterMessage, Comment, Message, ThreadMessage import contact, { Employee } from '@anticrm/contact' import type { Account, Class, Doc, Domain, Ref, Space, Timestamp } from '@anticrm/core' import { IndexKind } from '@anticrm/core' -import { ArrOf, Builder, Collection, Index, Model, Prop, TypeMarkup, TypeRef, TypeTimestamp, UX } from '@anticrm/model' +import { ArrOf, Builder, Collection, Index, Model, Prop, TypeMarkup, TypeRef, TypeString, TypeTimestamp, UX } from '@anticrm/model' import attachment from '@anticrm/model-attachment' import core, { TAttachedDoc, TSpace } from '@anticrm/model-core' import view from '@anticrm/model-view' @@ -34,6 +34,10 @@ export const DOMAIN_COMMENT = 'comment' as Domain export class TChannel extends TSpace implements Channel { @Prop(TypeTimestamp(), chunter.string.LastMessage) lastMessage?: Timestamp + + @Prop(TypeString(), chunter.string.Topic) + @Index(IndexKind.FullText) + topic?: string } @Model(chunter.class.ChunterMessage, core.class.AttachedDoc, DOMAIN_CHUNTER) @@ -106,6 +110,14 @@ export function createModel (builder: Builder): void { lastEditField: 'lastMessage' }) + 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, { + header: chunter.component.ChannelHeader + }) + builder.createDoc(view.class.ViewletDescriptor, core.space.Model, { label: chunter.string.Chat, icon: view.icon.Table, diff --git a/models/chunter/src/migration.ts b/models/chunter/src/migration.ts index d4c5acc5ac..b1446b4c54 100644 --- a/models/chunter/src/migration.ts +++ b/models/chunter/src/migration.ts @@ -34,6 +34,7 @@ export async function createGeneral (tx: TxOperations): Promise { await tx.createDoc(chunter.class.Channel, core.space.Space, { name: 'general', description: 'General Channel', + topic: 'General Channel', private: false, archived: false, members: [] @@ -49,6 +50,7 @@ export async function createRandom (tx: TxOperations): Promise { await tx.createDoc(chunter.class.Channel, core.space.Space, { name: 'random', description: 'Random Talks', + topic: 'Random Talks', private: false, archived: false, members: [] diff --git a/models/view/src/index.ts b/models/view/src/index.ts index 4895138cf1..4748bd6eac 100644 --- a/models/view/src/index.ts +++ b/models/view/src/index.ts @@ -29,6 +29,7 @@ import type { ObjectEditorHeader, ObjectFactory, ObjectValidator, + SpaceHeader, Viewlet, HTMLPresenter, TextPresenter, @@ -58,6 +59,11 @@ export class TObjectEditorHeader extends TClass implements ObjectEditorHeader { editor!: AnyComponent } +@Mixin(view.mixin.SpaceHeader, core.class.Class) +export class TSpaceHeader extends TClass implements SpaceHeader { + header!: AnyComponent +} + @Mixin(view.mixin.ObjectValidator, core.class.Class) export class TObjectValidator extends TClass implements ObjectValidator { validator!: Resource<(doc: T, client: Client) => Promise>> @@ -123,6 +129,7 @@ export function createModel (builder: Builder): void { TObjectFactory, TObjectEditorHeader, THTMLPresenter, + TSpaceHeader, TTextPresenter, TIgnoreActions ) diff --git a/packages/presentation/lang/en.json b/packages/presentation/lang/en.json index 95c21b671e..f5759d80f5 100644 --- a/packages/presentation/lang/en.json +++ b/packages/presentation/lang/en.json @@ -11,6 +11,7 @@ "AddSocialLinks": "Add social links", "Change": "Change", "Remove": "Remove", + "Members": "Members", "Search": "Search...", "Unassigned": "Unassigned" } diff --git a/packages/presentation/lang/ru.json b/packages/presentation/lang/ru.json index a182323321..5282157e1f 100644 --- a/packages/presentation/lang/ru.json +++ b/packages/presentation/lang/ru.json @@ -11,6 +11,7 @@ "AddSocialLinks": "Добавить контактную информацию", "Change": "Изменить", "Remove": "Удалить", + "Members": "Участники", "Search": "Поиск...", "Unassigned": "Не назначен" } diff --git a/packages/presentation/src/components/Members.svelte b/packages/presentation/src/components/Members.svelte new file mode 100644 index 0000000000..d66cfcc51a --- /dev/null +++ b/packages/presentation/src/components/Members.svelte @@ -0,0 +1,36 @@ + + + + +
+
+ +
+ +
+ + diff --git a/packages/presentation/src/index.ts b/packages/presentation/src/index.ts index e1573e720c..d0aff88cd8 100644 --- a/packages/presentation/src/index.ts +++ b/packages/presentation/src/index.ts @@ -23,6 +23,7 @@ export { default as AttributesBar } from './components/AttributesBar.svelte' export { default as Avatar } from './components/Avatar.svelte' export { default as Card } from './components/Card.svelte' export { default as EditableAvatar } from './components/EditableAvatar.svelte' +export { default as Members } from './components/Members.svelte' export { default as MessageBox } from './components/MessageBox.svelte' export { default as MessageViewer } from './components/MessageViewer.svelte' export { default as PDFViewer } from './components/PDFViewer.svelte' diff --git a/packages/presentation/src/plugin.ts b/packages/presentation/src/plugin.ts index 895a76f593..9aa2fcc506 100644 --- a/packages/presentation/src/plugin.ts +++ b/packages/presentation/src/plugin.ts @@ -40,6 +40,7 @@ export default plugin(presentationId, { AddSocialLinks: '' as IntlString, Change: '' as IntlString, Remove: '' as IntlString, + Members: '' as IntlString, Search: '' as IntlString, Unassigned: '' as IntlString }, diff --git a/plugins/chunter-assets/lang/en.json b/plugins/chunter-assets/lang/en.json index a74b6862a0..c1f6497a98 100644 --- a/plugins/chunter-assets/lang/en.json +++ b/plugins/chunter-assets/lang/en.json @@ -13,6 +13,7 @@ "EditUpdate": "Save...", "EditCancel": "Cancel", "Comments" : "Comments", + "Members": "Members", "MentionedIn": "mentioned this ", "ContactInfo": "Contact Info", "Content": "Content", @@ -24,6 +25,7 @@ "Replies": "Replies", "LastReply": "Last reply", "RepliesCount": "{replies, plural, =1 {# reply} other {# replies}}", + "Topic": "Topic", "Thread": "Thread", "New": "New", "MarkUnread": "Mark unread", diff --git a/plugins/chunter-assets/lang/ru.json b/plugins/chunter-assets/lang/ru.json index 38ba765612..130b6eb1b8 100644 --- a/plugins/chunter-assets/lang/ru.json +++ b/plugins/chunter-assets/lang/ru.json @@ -13,6 +13,7 @@ "EditUpdate": "Сохранить...", "EditCancel": "Отменить", "Comments" : "Комментарии", + "Members": "Участники", "MentionedIn": "упомянул(а) ", "ContactInfo": "Контактная информация", "Content": "Содержимое", diff --git a/plugins/chunter-resources/src/backlinks.ts b/plugins/chunter-resources/src/backlinks.ts index f80f3c1613..f3df2cf5d0 100644 --- a/plugins/chunter-resources/src/backlinks.ts +++ b/plugins/chunter-resources/src/backlinks.ts @@ -1,9 +1,12 @@ -import { Backlink, Comment } from '@anticrm/chunter' +import { Backlink } from '@anticrm/chunter' import contact, { EmployeeAccount } from '@anticrm/contact' import { Account, Class, Client, Data, Doc, DocumentQuery, Ref, TxOperations } from '@anticrm/core' import chunter from './plugin' -export async function getUser (client: Client, user: Ref | Ref): Promise { +export async function getUser ( + client: Client, + user: Ref | Ref +): Promise { return await client.findOne(contact.class.EmployeeAccount, { _id: user as Ref }) } @@ -23,10 +26,20 @@ export function getTime (time: number): string { export function isToday (time: number): boolean { const current = new Date() const target = new Date(time) - return current.getDate() === target.getDate() && current.getMonth() === target.getMonth() && current.getFullYear() === target.getFullYear() + return ( + current.getDate() === target.getDate() && + current.getMonth() === target.getMonth() && + current.getFullYear() === target.getFullYear() + ) } -function extractBacklinks (backlinkId: Ref, backlinkClass: Ref>, attachedDocId: Ref | undefined, message: string, kids: NodeListOf): Array> { +function extractBacklinks ( + backlinkId: Ref, + backlinkClass: Ref>, + attachedDocId: Ref | undefined, + message: string, + kids: NodeListOf +): Array> { const result: Array> = [] const nodes: Array> = [kids] @@ -40,7 +53,7 @@ function extractBacklinks (backlinkId: Ref, backlinkClass: Ref>, const el = kid as HTMLElement const ato = el.getAttribute('data-id') as Ref const atoClass = el.getAttribute('data-objectclass') as Ref> - const e = result.find(e => e.attachedTo === ato && e.attachedToClass === atoClass) + const e = result.find((e) => e.attachedTo === ato && e.attachedToClass === atoClass) if (e === undefined) { result.push({ attachedTo: ato, @@ -59,20 +72,44 @@ function extractBacklinks (backlinkId: Ref, backlinkClass: Ref>, return result } -export function getBacklinks (backlinkId: Ref, backlinkClass: Ref>, attachedDocId: Ref | undefined, content: string): Array> { +export function getBacklinks ( + backlinkId: Ref, + backlinkClass: Ref>, + attachedDocId: Ref | undefined, + content: string +): Array> { const parser = new DOMParser() const doc = parser.parseFromString(content, 'application/xhtml+xml') return extractBacklinks(backlinkId, backlinkClass, attachedDocId, content, doc.childNodes as NodeListOf) } -export async function createBacklinks (client: TxOperations, backlinkId: Ref, backlinkClass: Ref>, attachedDocId: Ref | undefined, content: string): Promise { +export async function createBacklinks ( + client: TxOperations, + backlinkId: Ref, + backlinkClass: Ref>, + attachedDocId: Ref | undefined, + content: string +): Promise { const backlinks = getBacklinks(backlinkId, backlinkClass, attachedDocId, content) for (const backlink of backlinks) { const { attachedTo, attachedToClass, collection, ...adata } = backlink - await client.addCollection(chunter.class.Backlink, chunter.space.Backlinks, attachedTo, attachedToClass, collection, adata) + await client.addCollection( + chunter.class.Backlink, + chunter.space.Backlinks, + attachedTo, + attachedToClass, + collection, + adata + ) } } -export async function updateBacklinks (client: TxOperations, backlinkId: Ref, backlinkClass: Ref>, attachedDocId: Ref | undefined, content: string): Promise { +export async function updateBacklinks ( + client: TxOperations, + backlinkId: Ref, + backlinkClass: Ref>, + attachedDocId: Ref | undefined, + content: string +): Promise { const q: DocumentQuery = { backlinkId, backlinkClass } if (attachedDocId !== undefined) { q.attachedDocId = attachedDocId @@ -83,12 +120,14 @@ export async function updateBacklinks (client: TxOperations, backlinkId: Ref b.backlinkId === c.backlinkId && b.backlinkClass === c.backlinkClass) + const pos = backlinks.findIndex((b) => b.backlinkId === c.backlinkId && b.backlinkClass === c.backlinkClass) if (pos !== -1) { // We need to check and update if required. const data = backlinks[pos] if (c.message !== data.message) { - await client.updateCollection(c._class, c.space, c._id, c.attachedTo, c.attachedToClass, c.collection, { message: data.message }) + await client.updateCollection(c._class, c.space, c._id, c.attachedTo, c.attachedToClass, c.collection, { + message: data.message + }) } backlinks.splice(pos, 1) } else { @@ -99,6 +138,13 @@ export async function updateBacklinks (client: TxOperations, backlinkId: Ref import attachment from '@anticrm/attachment' import type { Message } from '@anticrm/chunter' - import contact,{ Employee } from '@anticrm/contact' - import core,{ Doc,Ref,Space,WithLookup } from '@anticrm/core' + import contact, { Employee } from '@anticrm/contact' + import core, { Doc, Ref, Space, WithLookup } from '@anticrm/core' import { NotificationClientImpl } from '@anticrm/notification-resources' import { createQuery } from '@anticrm/presentation' - import { afterUpdate,beforeUpdate } from 'svelte' + import { afterUpdate, beforeUpdate } from 'svelte' import chunter from '../plugin' import ChannelSeparator from './ChannelSeparator.svelte' import MessageComponent from './Message.svelte' diff --git a/plugins/chunter-resources/src/components/ChannelHeader.svelte b/plugins/chunter-resources/src/components/ChannelHeader.svelte new file mode 100644 index 0000000000..ee13e3302a --- /dev/null +++ b/plugins/chunter-resources/src/components/ChannelHeader.svelte @@ -0,0 +1,49 @@ + + + +
+ {#if channel} +
+ {/if} +
diff --git a/plugins/chunter-resources/src/components/ChannelSeparator.svelte b/plugins/chunter-resources/src/components/ChannelSeparator.svelte index 6994a8240d..4e8337b138 100644 --- a/plugins/chunter-resources/src/components/ChannelSeparator.svelte +++ b/plugins/chunter-resources/src/components/ChannelSeparator.svelte @@ -12,10 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. --> - -
+