mirror of
https://github.com/hcengineering/platform.git
synced 2025-06-09 09:20:54 +00:00
Chunter: Channel attributes (#1334)
Signed-off-by: Ruslan Izhitsky <ruslan.izhitskiy@xored.com>
This commit is contained in:
parent
a7a049c78f
commit
80a1d0c7ca
@ -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");
|
// 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
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
@ -18,7 +18,7 @@ import type { Backlink, Channel, ChunterMessage, Comment, Message, ThreadMessage
|
|||||||
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'
|
||||||
import { IndexKind } 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 attachment from '@anticrm/model-attachment'
|
||||||
import core, { TAttachedDoc, TSpace } from '@anticrm/model-core'
|
import core, { TAttachedDoc, TSpace } from '@anticrm/model-core'
|
||||||
import view from '@anticrm/model-view'
|
import view from '@anticrm/model-view'
|
||||||
@ -34,6 +34,10 @@ export const DOMAIN_COMMENT = 'comment' as Domain
|
|||||||
export class TChannel extends TSpace implements Channel {
|
export class TChannel extends TSpace implements Channel {
|
||||||
@Prop(TypeTimestamp(), chunter.string.LastMessage)
|
@Prop(TypeTimestamp(), chunter.string.LastMessage)
|
||||||
lastMessage?: Timestamp
|
lastMessage?: Timestamp
|
||||||
|
|
||||||
|
@Prop(TypeString(), chunter.string.Topic)
|
||||||
|
@Index(IndexKind.FullText)
|
||||||
|
topic?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@Model(chunter.class.ChunterMessage, core.class.AttachedDoc, DOMAIN_CHUNTER)
|
@Model(chunter.class.ChunterMessage, core.class.AttachedDoc, DOMAIN_CHUNTER)
|
||||||
@ -106,6 +110,14 @@ export function createModel (builder: Builder): void {
|
|||||||
lastEditField: 'lastMessage'
|
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, {
|
builder.createDoc(view.class.ViewletDescriptor, core.space.Model, {
|
||||||
label: chunter.string.Chat,
|
label: chunter.string.Chat,
|
||||||
icon: view.icon.Table,
|
icon: view.icon.Table,
|
||||||
|
@ -34,6 +34,7 @@ export async function createGeneral (tx: TxOperations): Promise<void> {
|
|||||||
await tx.createDoc(chunter.class.Channel, core.space.Space, {
|
await tx.createDoc(chunter.class.Channel, core.space.Space, {
|
||||||
name: 'general',
|
name: 'general',
|
||||||
description: 'General Channel',
|
description: 'General Channel',
|
||||||
|
topic: 'General Channel',
|
||||||
private: false,
|
private: false,
|
||||||
archived: false,
|
archived: false,
|
||||||
members: []
|
members: []
|
||||||
@ -49,6 +50,7 @@ export async function createRandom (tx: TxOperations): Promise<void> {
|
|||||||
await tx.createDoc(chunter.class.Channel, core.space.Space, {
|
await tx.createDoc(chunter.class.Channel, core.space.Space, {
|
||||||
name: 'random',
|
name: 'random',
|
||||||
description: 'Random Talks',
|
description: 'Random Talks',
|
||||||
|
topic: 'Random Talks',
|
||||||
private: false,
|
private: false,
|
||||||
archived: false,
|
archived: false,
|
||||||
members: []
|
members: []
|
||||||
|
@ -29,6 +29,7 @@ import type {
|
|||||||
ObjectEditorHeader,
|
ObjectEditorHeader,
|
||||||
ObjectFactory,
|
ObjectFactory,
|
||||||
ObjectValidator,
|
ObjectValidator,
|
||||||
|
SpaceHeader,
|
||||||
Viewlet,
|
Viewlet,
|
||||||
HTMLPresenter,
|
HTMLPresenter,
|
||||||
TextPresenter,
|
TextPresenter,
|
||||||
@ -58,6 +59,11 @@ export class TObjectEditorHeader extends TClass implements ObjectEditorHeader {
|
|||||||
editor!: AnyComponent
|
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)
|
@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<{}>>>
|
||||||
@ -123,6 +129,7 @@ export function createModel (builder: Builder): void {
|
|||||||
TObjectFactory,
|
TObjectFactory,
|
||||||
TObjectEditorHeader,
|
TObjectEditorHeader,
|
||||||
THTMLPresenter,
|
THTMLPresenter,
|
||||||
|
TSpaceHeader,
|
||||||
TTextPresenter,
|
TTextPresenter,
|
||||||
TIgnoreActions
|
TIgnoreActions
|
||||||
)
|
)
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
"AddSocialLinks": "Add social links",
|
"AddSocialLinks": "Add social links",
|
||||||
"Change": "Change",
|
"Change": "Change",
|
||||||
"Remove": "Remove",
|
"Remove": "Remove",
|
||||||
|
"Members": "Members",
|
||||||
"Search": "Search...",
|
"Search": "Search...",
|
||||||
"Unassigned": "Unassigned"
|
"Unassigned": "Unassigned"
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
"AddSocialLinks": "Добавить контактную информацию",
|
"AddSocialLinks": "Добавить контактную информацию",
|
||||||
"Change": "Изменить",
|
"Change": "Изменить",
|
||||||
"Remove": "Удалить",
|
"Remove": "Удалить",
|
||||||
|
"Members": "Участники",
|
||||||
"Search": "Поиск...",
|
"Search": "Поиск...",
|
||||||
"Unassigned": "Не назначен"
|
"Unassigned": "Не назначен"
|
||||||
}
|
}
|
||||||
|
36
packages/presentation/src/components/Members.svelte
Normal file
36
packages/presentation/src/components/Members.svelte
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<!--
|
||||||
|
// 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 { Class, Doc, Ref, Space } from '@anticrm/core'
|
||||||
|
// import { getClient } from '@anticrm/presentation'
|
||||||
|
import { Label } from '@anticrm/ui'
|
||||||
|
// import { Table } from '@anticrm/view-resources'
|
||||||
|
import presentation from '../plugin'
|
||||||
|
|
||||||
|
// const client = getClient()
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex-col">
|
||||||
|
<div class="flex-row-center">
|
||||||
|
<span class="title"><Label label={presentation.string.Members} /></span>
|
||||||
|
</div>
|
||||||
|
<!-- TODO: implement Members -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
|
||||||
|
</style>
|
@ -23,6 +23,7 @@ export { default as AttributesBar } from './components/AttributesBar.svelte'
|
|||||||
export { default as Avatar } from './components/Avatar.svelte'
|
export { default as Avatar } from './components/Avatar.svelte'
|
||||||
export { default as Card } from './components/Card.svelte'
|
export { default as Card } from './components/Card.svelte'
|
||||||
export { default as EditableAvatar } from './components/EditableAvatar.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 MessageBox } from './components/MessageBox.svelte'
|
||||||
export { default as MessageViewer } from './components/MessageViewer.svelte'
|
export { default as MessageViewer } from './components/MessageViewer.svelte'
|
||||||
export { default as PDFViewer } from './components/PDFViewer.svelte'
|
export { default as PDFViewer } from './components/PDFViewer.svelte'
|
||||||
|
@ -40,6 +40,7 @@ export default plugin(presentationId, {
|
|||||||
AddSocialLinks: '' as IntlString,
|
AddSocialLinks: '' as IntlString,
|
||||||
Change: '' as IntlString,
|
Change: '' as IntlString,
|
||||||
Remove: '' as IntlString,
|
Remove: '' as IntlString,
|
||||||
|
Members: '' as IntlString,
|
||||||
Search: '' as IntlString,
|
Search: '' as IntlString,
|
||||||
Unassigned: '' as IntlString
|
Unassigned: '' as IntlString
|
||||||
},
|
},
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"EditUpdate": "Save...",
|
"EditUpdate": "Save...",
|
||||||
"EditCancel": "Cancel",
|
"EditCancel": "Cancel",
|
||||||
"Comments" : "Comments",
|
"Comments" : "Comments",
|
||||||
|
"Members": "Members",
|
||||||
"MentionedIn": "mentioned this ",
|
"MentionedIn": "mentioned this ",
|
||||||
"ContactInfo": "Contact Info",
|
"ContactInfo": "Contact Info",
|
||||||
"Content": "Content",
|
"Content": "Content",
|
||||||
@ -24,6 +25,7 @@
|
|||||||
"Replies": "Replies",
|
"Replies": "Replies",
|
||||||
"LastReply": "Last reply",
|
"LastReply": "Last reply",
|
||||||
"RepliesCount": "{replies, plural, =1 {# reply} other {# replies}}",
|
"RepliesCount": "{replies, plural, =1 {# reply} other {# replies}}",
|
||||||
|
"Topic": "Topic",
|
||||||
"Thread": "Thread",
|
"Thread": "Thread",
|
||||||
"New": "New",
|
"New": "New",
|
||||||
"MarkUnread": "Mark unread",
|
"MarkUnread": "Mark unread",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"EditUpdate": "Сохранить...",
|
"EditUpdate": "Сохранить...",
|
||||||
"EditCancel": "Отменить",
|
"EditCancel": "Отменить",
|
||||||
"Comments" : "Комментарии",
|
"Comments" : "Комментарии",
|
||||||
|
"Members": "Участники",
|
||||||
"MentionedIn": "упомянул(а) ",
|
"MentionedIn": "упомянул(а) ",
|
||||||
"ContactInfo": "Контактная информация",
|
"ContactInfo": "Контактная информация",
|
||||||
"Content": "Содержимое",
|
"Content": "Содержимое",
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
import { Backlink, Comment } from '@anticrm/chunter'
|
import { Backlink } from '@anticrm/chunter'
|
||||||
import contact, { EmployeeAccount } from '@anticrm/contact'
|
import contact, { EmployeeAccount } from '@anticrm/contact'
|
||||||
import { Account, Class, Client, Data, Doc, DocumentQuery, Ref, TxOperations } from '@anticrm/core'
|
import { Account, Class, Client, Data, Doc, DocumentQuery, Ref, TxOperations } from '@anticrm/core'
|
||||||
import chunter from './plugin'
|
import chunter from './plugin'
|
||||||
|
|
||||||
export async function getUser (client: Client, user: Ref<EmployeeAccount> | Ref<Account>): Promise<EmployeeAccount | undefined> {
|
export async function getUser (
|
||||||
|
client: Client,
|
||||||
|
user: Ref<EmployeeAccount> | Ref<Account>
|
||||||
|
): Promise<EmployeeAccount | undefined> {
|
||||||
return await client.findOne(contact.class.EmployeeAccount, { _id: user as Ref<EmployeeAccount> })
|
return await client.findOne(contact.class.EmployeeAccount, { _id: user as Ref<EmployeeAccount> })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,10 +26,20 @@ export function getTime (time: number): string {
|
|||||||
export function isToday (time: number): boolean {
|
export function isToday (time: number): boolean {
|
||||||
const current = new Date()
|
const current = new Date()
|
||||||
const target = new Date(time)
|
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<Doc>, backlinkClass: Ref<Class<Doc>>, attachedDocId: Ref<Doc> | undefined, message: string, kids: NodeListOf<ChildNode>): Array<Data<Backlink>> {
|
function extractBacklinks (
|
||||||
|
backlinkId: Ref<Doc>,
|
||||||
|
backlinkClass: Ref<Class<Doc>>,
|
||||||
|
attachedDocId: Ref<Doc> | undefined,
|
||||||
|
message: string,
|
||||||
|
kids: NodeListOf<ChildNode>
|
||||||
|
): Array<Data<Backlink>> {
|
||||||
const result: Array<Data<Backlink>> = []
|
const result: Array<Data<Backlink>> = []
|
||||||
|
|
||||||
const nodes: Array<NodeListOf<ChildNode>> = [kids]
|
const nodes: Array<NodeListOf<ChildNode>> = [kids]
|
||||||
@ -40,7 +53,7 @@ function extractBacklinks (backlinkId: Ref<Doc>, backlinkClass: Ref<Class<Doc>>,
|
|||||||
const el = kid as HTMLElement
|
const el = kid as HTMLElement
|
||||||
const ato = el.getAttribute('data-id') as Ref<Doc>
|
const ato = el.getAttribute('data-id') as Ref<Doc>
|
||||||
const atoClass = el.getAttribute('data-objectclass') as Ref<Class<Doc>>
|
const atoClass = el.getAttribute('data-objectclass') as Ref<Class<Doc>>
|
||||||
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) {
|
if (e === undefined) {
|
||||||
result.push({
|
result.push({
|
||||||
attachedTo: ato,
|
attachedTo: ato,
|
||||||
@ -59,20 +72,44 @@ function extractBacklinks (backlinkId: Ref<Doc>, backlinkClass: Ref<Class<Doc>>,
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getBacklinks (backlinkId: Ref<Doc>, backlinkClass: Ref<Class<Doc>>, attachedDocId: Ref<Doc> | undefined, content: string): Array<Data<Backlink>> {
|
export function getBacklinks (
|
||||||
|
backlinkId: Ref<Doc>,
|
||||||
|
backlinkClass: Ref<Class<Doc>>,
|
||||||
|
attachedDocId: Ref<Doc> | undefined,
|
||||||
|
content: string
|
||||||
|
): Array<Data<Backlink>> {
|
||||||
const parser = new DOMParser()
|
const parser = new DOMParser()
|
||||||
const doc = parser.parseFromString(content, 'application/xhtml+xml')
|
const doc = parser.parseFromString(content, 'application/xhtml+xml')
|
||||||
return extractBacklinks(backlinkId, backlinkClass, attachedDocId, content, doc.childNodes as NodeListOf<HTMLElement>)
|
return extractBacklinks(backlinkId, backlinkClass, attachedDocId, content, doc.childNodes as NodeListOf<HTMLElement>)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createBacklinks (client: TxOperations, backlinkId: Ref<Doc>, backlinkClass: Ref<Class<Doc>>, attachedDocId: Ref<Doc> | undefined, content: string): Promise<void> {
|
export async function createBacklinks (
|
||||||
|
client: TxOperations,
|
||||||
|
backlinkId: Ref<Doc>,
|
||||||
|
backlinkClass: Ref<Class<Doc>>,
|
||||||
|
attachedDocId: Ref<Doc> | undefined,
|
||||||
|
content: string
|
||||||
|
): Promise<void> {
|
||||||
const backlinks = getBacklinks(backlinkId, backlinkClass, attachedDocId, content)
|
const backlinks = getBacklinks(backlinkId, backlinkClass, attachedDocId, content)
|
||||||
for (const backlink of backlinks) {
|
for (const backlink of backlinks) {
|
||||||
const { attachedTo, attachedToClass, collection, ...adata } = backlink
|
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<Doc>, backlinkClass: Ref<Class<Doc>>, attachedDocId: Ref<Doc> | undefined, content: string): Promise<void> {
|
export async function updateBacklinks (
|
||||||
|
client: TxOperations,
|
||||||
|
backlinkId: Ref<Doc>,
|
||||||
|
backlinkClass: Ref<Class<Doc>>,
|
||||||
|
attachedDocId: Ref<Doc> | undefined,
|
||||||
|
content: string
|
||||||
|
): Promise<void> {
|
||||||
const q: DocumentQuery<Backlink> = { backlinkId, backlinkClass }
|
const q: DocumentQuery<Backlink> = { backlinkId, backlinkClass }
|
||||||
if (attachedDocId !== undefined) {
|
if (attachedDocId !== undefined) {
|
||||||
q.attachedDocId = attachedDocId
|
q.attachedDocId = attachedDocId
|
||||||
@ -83,12 +120,14 @@ export async function updateBacklinks (client: TxOperations, backlinkId: Ref<Doc
|
|||||||
// We need to find ones we need to remove, and ones we need to update.
|
// We need to find ones we need to remove, and ones we need to update.
|
||||||
for (const c of current) {
|
for (const c of current) {
|
||||||
// Find existing and check if we need to update message.
|
// Find existing and check if we need to update message.
|
||||||
const pos = backlinks.findIndex(b => b.backlinkId === c.backlinkId && b.backlinkClass === c.backlinkClass)
|
const pos = backlinks.findIndex((b) => b.backlinkId === c.backlinkId && b.backlinkClass === c.backlinkClass)
|
||||||
if (pos !== -1) {
|
if (pos !== -1) {
|
||||||
// We need to check and update if required.
|
// We need to check and update if required.
|
||||||
const data = backlinks[pos]
|
const data = backlinks[pos]
|
||||||
if (c.message !== data.message) {
|
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)
|
backlinks.splice(pos, 1)
|
||||||
} else {
|
} else {
|
||||||
@ -99,6 +138,13 @@ export async function updateBacklinks (client: TxOperations, backlinkId: Ref<Doc
|
|||||||
// Add missing backlinks
|
// Add missing backlinks
|
||||||
for (const backlink of backlinks) {
|
for (const backlink of backlinks) {
|
||||||
const { attachedTo, attachedToClass, collection, ...adata } = backlink
|
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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,11 +15,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import attachment from '@anticrm/attachment'
|
import attachment from '@anticrm/attachment'
|
||||||
import type { Message } from '@anticrm/chunter'
|
import type { Message } from '@anticrm/chunter'
|
||||||
import contact,{ Employee } from '@anticrm/contact'
|
import contact, { Employee } from '@anticrm/contact'
|
||||||
import core,{ Doc,Ref,Space,WithLookup } from '@anticrm/core'
|
import core, { Doc, Ref, Space, WithLookup } from '@anticrm/core'
|
||||||
import { NotificationClientImpl } from '@anticrm/notification-resources'
|
import { NotificationClientImpl } from '@anticrm/notification-resources'
|
||||||
import { createQuery } from '@anticrm/presentation'
|
import { createQuery } from '@anticrm/presentation'
|
||||||
import { afterUpdate,beforeUpdate } from 'svelte'
|
import { afterUpdate, beforeUpdate } from 'svelte'
|
||||||
import chunter from '../plugin'
|
import chunter from '../plugin'
|
||||||
import ChannelSeparator from './ChannelSeparator.svelte'
|
import ChannelSeparator from './ChannelSeparator.svelte'
|
||||||
import MessageComponent from './Message.svelte'
|
import MessageComponent from './Message.svelte'
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
<!--
|
||||||
|
// 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 { Channel } 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 } from '../utils'
|
||||||
|
import Header from './Header.svelte'
|
||||||
|
|
||||||
|
export let spaceId: Ref<Channel> | undefined
|
||||||
|
|
||||||
|
const client = getClient()
|
||||||
|
const query = createQuery()
|
||||||
|
let channel: Channel | undefined
|
||||||
|
|
||||||
|
$: query.query(chunter.class.Channel, { _id: spaceId }, (result) => {
|
||||||
|
channel = result[0]
|
||||||
|
})
|
||||||
|
|
||||||
|
async function onSpaceEdit (): Promise<void> {
|
||||||
|
if (channel === undefined) return
|
||||||
|
showPanel(chunter.component.EditChannel, channel._id, channel._class, 'right')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="ac-header divide full">
|
||||||
|
{#if channel}
|
||||||
|
<Header
|
||||||
|
icon={classIcon(client, channel._class)}
|
||||||
|
label={channel.name}
|
||||||
|
description={channel.topic}
|
||||||
|
on:click={onSpaceEdit}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
@ -12,10 +12,9 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { IntlString } from "@anticrm/platform"
|
import type { IntlString } from '@anticrm/platform'
|
||||||
import { Label } from "@anticrm/ui"
|
import { Label } from '@anticrm/ui'
|
||||||
|
|
||||||
export let title: IntlString
|
export let title: IntlString
|
||||||
export let line: boolean = false
|
export let line: boolean = false
|
||||||
@ -24,9 +23,9 @@
|
|||||||
export let isNew: boolean = false
|
export let isNew: boolean = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="w-full text-sm flex-center whitespace-nowrap mb-6" class:flex-reverse={reverse} class:new={isNew} >
|
<div class="w-full text-sm flex-center whitespace-nowrap mb-6" class:flex-reverse={reverse} class:new={isNew}>
|
||||||
<Label label={title} {params} />
|
<Label label={title} {params} />
|
||||||
<div class:ml-4={!reverse} class:mr-4={reverse} class:line={line} ></div>
|
<div class:ml-4={!reverse} class:mr-4={reverse} class:line />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@ -35,7 +34,6 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background-color: var(--theme-chat-divider);
|
background-color: var(--theme-chat-divider);
|
||||||
|
|
||||||
}
|
}
|
||||||
.new {
|
.new {
|
||||||
.line {
|
.line {
|
||||||
|
@ -12,14 +12,13 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AttachmentRefInput } from '@anticrm/attachment-resources'
|
import { AttachmentRefInput } from '@anticrm/attachment-resources'
|
||||||
import { Message } from '@anticrm/chunter'
|
import { Message } from '@anticrm/chunter'
|
||||||
import { generateId,getCurrentAccount,Ref,Space, TxFactory } from '@anticrm/core'
|
import { generateId, getCurrentAccount, Ref, Space, TxFactory } from '@anticrm/core'
|
||||||
import { NotificationClientImpl } from '@anticrm/notification-resources'
|
import { NotificationClientImpl } from '@anticrm/notification-resources'
|
||||||
import { getClient } from '@anticrm/presentation'
|
import { getClient } from '@anticrm/presentation'
|
||||||
import { getCurrentLocation,navigate } from '@anticrm/ui'
|
import { getCurrentLocation, navigate } from '@anticrm/ui'
|
||||||
import { createBacklinks } from '../backlinks'
|
import { createBacklinks } from '../backlinks'
|
||||||
import chunter from '../plugin'
|
import chunter from '../plugin'
|
||||||
import Channel from './Channel.svelte'
|
import Channel from './Channel.svelte'
|
||||||
@ -35,15 +34,20 @@
|
|||||||
const { message, attachments } = event.detail
|
const { message, attachments } = event.detail
|
||||||
const me = getCurrentAccount()._id
|
const me = getCurrentAccount()._id
|
||||||
const txFactory = new TxFactory(me)
|
const txFactory = new TxFactory(me)
|
||||||
const tx = txFactory.createTxCreateDoc<Message>(_class, space, {
|
const tx = txFactory.createTxCreateDoc<Message>(
|
||||||
attachedTo: space,
|
_class,
|
||||||
attachedToClass: chunter.class.Channel,
|
space,
|
||||||
collection: 'messages',
|
{
|
||||||
content: message,
|
attachedTo: space,
|
||||||
createOn: 0,
|
attachedToClass: chunter.class.Channel,
|
||||||
createBy: me,
|
collection: 'messages',
|
||||||
attachments
|
content: message,
|
||||||
}, _id)
|
createOn: 0,
|
||||||
|
createBy: me,
|
||||||
|
attachments
|
||||||
|
},
|
||||||
|
_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.Channel, tx.modifiedOn, true)
|
||||||
await client.tx(tx)
|
await client.tx(tx)
|
||||||
@ -59,12 +63,16 @@
|
|||||||
loc.path[3] = _id
|
loc.path[3] = _id
|
||||||
navigate(loc)
|
navigate(loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Channel {space} on:openThread={(e) => { openThread(e.detail) }} />
|
<Channel
|
||||||
|
{space}
|
||||||
|
on:openThread={(e) => {
|
||||||
|
openThread(e.detail)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<div class="reference">
|
<div class="reference">
|
||||||
<AttachmentRefInput {space} {_class} objectId={_id} on:message={onMessage}/>
|
<AttachmentRefInput {space} {_class} objectId={_id} on:message={onMessage} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Person } from '@anticrm/contact'
|
import type { Person } from '@anticrm/contact'
|
||||||
import { formatName } from '@anticrm/contact'
|
import { formatName } from '@anticrm/contact'
|
||||||
@ -25,7 +24,6 @@
|
|||||||
|
|
||||||
export let user: Person
|
export let user: Person
|
||||||
export let message: IMessage
|
export let message: IMessage
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex-nowrap">
|
<div class="flex-nowrap">
|
||||||
@ -44,16 +42,16 @@
|
|||||||
margin-right: 1.25rem;
|
margin-right: 1.25rem;
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
margin-bottom: .25rem;
|
margin-bottom: 0.25rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
line-height: 150%;
|
line-height: 150%;
|
||||||
color: var(--theme-caption-color);
|
color: var(--theme-caption-color);
|
||||||
|
|
||||||
span {
|
span {
|
||||||
margin-left: .5rem;
|
margin-left: 0.5rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: .875rem;
|
font-size: 0.875rem;
|
||||||
color: var(--theme-content-dark-color);
|
color: var(--theme-content-dark-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
<!--
|
<!--
|
||||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||||
// Copyright © 2021 Hardcore Engineering Inc.
|
// Copyright © 2021 Hardcore Engineering Inc.
|
||||||
@ -26,15 +25,23 @@
|
|||||||
export let object: Doc
|
export let object: Doc
|
||||||
const _class = chunter.class.Comment
|
const _class = chunter.class.Comment
|
||||||
let _id: Ref<Comment> = generateId()
|
let _id: Ref<Comment> = generateId()
|
||||||
|
|
||||||
async function onMessage (event: CustomEvent) {
|
async function onMessage (event: CustomEvent) {
|
||||||
const { message, attachments } = event.detail
|
const { message, attachments } = event.detail
|
||||||
await client.addCollection<Doc, Comment>(_class, object.space, object._id, object._class, 'comments', { message, attachments }, _id)
|
await client.addCollection<Doc, Comment>(
|
||||||
|
_class,
|
||||||
|
object.space,
|
||||||
|
object._id,
|
||||||
|
object._class,
|
||||||
|
'comments',
|
||||||
|
{ message, attachments },
|
||||||
|
_id
|
||||||
|
)
|
||||||
|
|
||||||
// Create an backlink to document
|
// Create an backlink to document
|
||||||
await createBacklinks(client, object._id, object._class, _id, message)
|
await createBacklinks(client, object._id, object._class, _id, message)
|
||||||
_id = generateId()
|
_id = generateId()
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<AttachmentRefInput {_class} space={object.space} objectId={_id} on:message={onMessage}/>
|
|
||||||
|
<AttachmentRefInput {_class} space={object.space} objectId={_id} on:message={onMessage} />
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Ref, Doc, SortingOrder } from '@anticrm/core'
|
import { Ref, Doc, SortingOrder } from '@anticrm/core'
|
||||||
|
|
||||||
@ -25,10 +24,14 @@
|
|||||||
|
|
||||||
let comments: Comment[] = []
|
let comments: Comment[] = []
|
||||||
const query = createQuery()
|
const query = createQuery()
|
||||||
$: query.query(chunter.class.Comment, { attachedTo: objectId }, (res) => {
|
$: query.query(
|
||||||
comments = res
|
chunter.class.Comment,
|
||||||
}, { limit: 3, sort: { modifiedOn: SortingOrder.Descending } })
|
{ attachedTo: objectId },
|
||||||
|
(res) => {
|
||||||
|
comments = res
|
||||||
|
},
|
||||||
|
{ limit: 3, sort: { modifiedOn: SortingOrder.Descending } }
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#each comments as comment}
|
{#each comments as comment}
|
||||||
@ -38,6 +41,10 @@
|
|||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.item { max-width: 30rem; }
|
.item {
|
||||||
.item + .item { margin-top: 1.25rem; }
|
max-width: 30rem;
|
||||||
|
}
|
||||||
|
.item + .item {
|
||||||
|
margin-top: 1.25rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AttachmentDocList } from '@anticrm/attachment-resources'
|
import { AttachmentDocList } from '@anticrm/attachment-resources'
|
||||||
import type { Comment } from '@anticrm/chunter'
|
import type { Comment } from '@anticrm/chunter'
|
||||||
@ -41,7 +40,7 @@
|
|||||||
<div class="content-trans-color ml-4"><TimeSince value={value.modifiedOn} /></div>
|
<div class="content-trans-color ml-4"><TimeSince value={value.modifiedOn} /></div>
|
||||||
</div>
|
</div>
|
||||||
<ShowMore limit={126} fixed>
|
<ShowMore limit={126} fixed>
|
||||||
<MessageViewer message={value.message}/>
|
<MessageViewer message={value.message} />
|
||||||
<AttachmentDocList {value} />
|
<AttachmentDocList {value} />
|
||||||
</ShowMore>
|
</ShowMore>
|
||||||
</div>
|
</div>
|
||||||
@ -56,6 +55,6 @@
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
margin-bottom: .25rem;
|
margin-bottom: 0.25rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Doc } from '@anticrm/core'
|
import type { Doc } from '@anticrm/core'
|
||||||
import { Tooltip, IconThread } from '@anticrm/ui'
|
import { Tooltip, IconThread } from '@anticrm/ui'
|
||||||
@ -28,9 +27,9 @@
|
|||||||
{#if value && value.comments && value.comments > 0}
|
{#if value && value.comments && value.comments > 0}
|
||||||
<Tooltip label={chunter.string.Comments} component={CommentPopup} props={{ objectId: value._id }}>
|
<Tooltip label={chunter.string.Comments} component={CommentPopup} props={{ objectId: value._id }}>
|
||||||
<div class="sm-tool-icon ml-1 mr-1">
|
<div class="sm-tool-icon ml-1 mr-1">
|
||||||
<span class="icon"><IconThread {size}/></span>
|
<span class="icon"><IconThread {size} /></span>
|
||||||
{#if showCounter}
|
{#if showCounter}
|
||||||
{value.comments}
|
{value.comments}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import { IconFolder, EditBox, ToggleWithLabel, Grid } from '@anticrm/ui'
|
import { IconFolder, EditBox, ToggleWithLabel, Grid } from '@anticrm/ui'
|
||||||
@ -25,16 +24,15 @@
|
|||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let name: string = ''
|
let name: string = ''
|
||||||
let description: string = ''
|
export function canClose (): boolean {
|
||||||
export function canClose(): boolean {
|
|
||||||
return name === ''
|
return name === ''
|
||||||
}
|
}
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
||||||
function createChannel() {
|
function createChannel () {
|
||||||
client.createDoc(chunter.class.Channel, core.space.Space, {
|
client.createDoc(chunter.class.Channel, core.space.Space, {
|
||||||
name,
|
name,
|
||||||
description,
|
description: '',
|
||||||
private: false,
|
private: false,
|
||||||
archived: false,
|
archived: false,
|
||||||
members: [getCurrentAccount()._id]
|
members: [getCurrentAccount()._id]
|
||||||
@ -43,10 +41,12 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SpaceCreateCard
|
<SpaceCreateCard
|
||||||
label={chunter.string.CreateChannel}
|
label={chunter.string.CreateChannel}
|
||||||
okAction={createChannel}
|
okAction={createChannel}
|
||||||
canSave={name ? true : false}
|
canSave={!!name}
|
||||||
on:close={() => { dispatch('close') }}
|
on:close={() => {
|
||||||
|
dispatch('close')
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Grid column={1} rowGap={1.5}>
|
<Grid column={1} rowGap={1.5}>
|
||||||
<EditBox
|
<EditBox
|
||||||
@ -57,6 +57,6 @@
|
|||||||
maxWidth={'16rem'}
|
maxWidth={'16rem'}
|
||||||
focus
|
focus
|
||||||
/>
|
/>
|
||||||
<ToggleWithLabel label={chunter.string.MakePrivate} description={chunter.string.MakePrivateDescription}/>
|
<ToggleWithLabel label={chunter.string.MakePrivate} description={chunter.string.MakePrivateDescription} />
|
||||||
</Grid>
|
</Grid>
|
||||||
</SpaceCreateCard>
|
</SpaceCreateCard>
|
||||||
|
@ -12,9 +12,8 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Timestamp } from "@anticrm/core"
|
import { Timestamp } from '@anticrm/core'
|
||||||
|
|
||||||
export let value: Timestamp
|
export let value: Timestamp
|
||||||
export let line: boolean = false
|
export let line: boolean = false
|
||||||
@ -32,7 +31,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex-center container" class:line={line}>
|
<div class="flex-center container" class:line>
|
||||||
<div class="title">{new Intl.DateTimeFormat('default', options).format(value)}</div>
|
<div class="title">{new Intl.DateTimeFormat('default', options).format(value)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -44,10 +43,10 @@
|
|||||||
|
|
||||||
.title {
|
.title {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: .375rem .75rem;
|
padding: 0.375rem 0.75rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: .75rem;
|
font-size: 0.75rem;
|
||||||
letter-spacing: .5;
|
letter-spacing: 0.5;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: var(--theme-content-trans-color);
|
color: var(--theme-content-trans-color);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
91
plugins/chunter-resources/src/components/EditChannel.svelte
Normal file
91
plugins/chunter-resources/src/components/EditChannel.svelte
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
<!--
|
||||||
|
//
|
||||||
|
// 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 { Channel } from '@anticrm/chunter'
|
||||||
|
import type { Class, Ref } from '@anticrm/core'
|
||||||
|
import type { IntlString } from '@anticrm/platform'
|
||||||
|
import { createQuery, getClient, Members } from '@anticrm/presentation'
|
||||||
|
import { Icon, IconClose, Label, ActionIcon, Scroller } from '@anticrm/ui'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
|
||||||
|
import chunter from '../plugin'
|
||||||
|
import EditChannelDescriptionTab from './EditChannelDescriptionTab.svelte'
|
||||||
|
|
||||||
|
export let _id: Ref<Channel>
|
||||||
|
export let _class: Ref<Class<Channel>>
|
||||||
|
|
||||||
|
let channel: Channel
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
const client = getClient()
|
||||||
|
const clazz = client.getHierarchy().getClass(_class)
|
||||||
|
|
||||||
|
const query = createQuery()
|
||||||
|
$: query.query(chunter.class.Channel, { _id }, (result) => {
|
||||||
|
channel = result[0]
|
||||||
|
})
|
||||||
|
|
||||||
|
const tabLabels: IntlString[] = [chunter.string.Channel, chunter.string.Members]
|
||||||
|
let selectedTabIndex = 0
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
on:click={() => {
|
||||||
|
dispatch('close')
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div class="antiDialogs antiComponent">
|
||||||
|
<div class="ac-header short mirror divide">
|
||||||
|
<div class="ac-header__wrap-title">
|
||||||
|
<div class="ac-header__icon">
|
||||||
|
{#if clazz.icon}<Icon icon={clazz.icon} size={'medium'} />{/if}
|
||||||
|
</div>
|
||||||
|
<div class="ac-header__title"><Label label={clazz.label} /></div>
|
||||||
|
</div>
|
||||||
|
<div class="tool">
|
||||||
|
<ActionIcon
|
||||||
|
icon={IconClose}
|
||||||
|
size={'small'}
|
||||||
|
action={() => {
|
||||||
|
dispatch('close')
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ac-tabs">
|
||||||
|
{#each tabLabels as tabLabel, i}
|
||||||
|
<div
|
||||||
|
class="ac-tabs__tab"
|
||||||
|
class:selected={i === selectedTabIndex}
|
||||||
|
on:click={() => {
|
||||||
|
selectedTabIndex = i
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label label={tabLabel} />
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
<div class="ac-tabs__empty" />
|
||||||
|
</div>
|
||||||
|
<Scroller padding>
|
||||||
|
{#if selectedTabIndex === 0}
|
||||||
|
<EditChannelDescriptionTab {channel} {_id} {_class} />
|
||||||
|
{:else}
|
||||||
|
<!-- Channel members -->
|
||||||
|
<Members />
|
||||||
|
{/if}
|
||||||
|
</Scroller>
|
||||||
|
</div>
|
@ -0,0 +1,86 @@
|
|||||||
|
<!--
|
||||||
|
//
|
||||||
|
// 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 { Channel } from '@anticrm/chunter'
|
||||||
|
import type { Class, Ref } from '@anticrm/core'
|
||||||
|
import { createQuery, getClient } from '@anticrm/presentation'
|
||||||
|
import { EditBox } from '@anticrm/ui'
|
||||||
|
|
||||||
|
import chunter from '../plugin'
|
||||||
|
|
||||||
|
export let _id: Ref<Channel>
|
||||||
|
export let _class: Ref<Class<Channel>>
|
||||||
|
|
||||||
|
export let channel: Channel
|
||||||
|
|
||||||
|
const client = getClient()
|
||||||
|
const clazz = client.getHierarchy().getClass(_class)
|
||||||
|
|
||||||
|
const query = createQuery()
|
||||||
|
|
||||||
|
function onNameChange (ev: Event) {
|
||||||
|
const value = (ev.target as HTMLInputElement).value
|
||||||
|
if (value.trim().length > 0) {
|
||||||
|
client.updateDoc(_class, channel.space, channel._id, { name: value })
|
||||||
|
} else {
|
||||||
|
// Just refresh value
|
||||||
|
query.query(chunter.class.Channel, { _id }, (result) => {
|
||||||
|
channel = result[0]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTopicChange (ev: Event) {
|
||||||
|
const newTopic = (ev.target as HTMLInputElement).value
|
||||||
|
client.update(channel, { topic: newTopic })
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDescriptionChange (ev: Event) {
|
||||||
|
const newDescription = (ev.target as HTMLInputElement).value
|
||||||
|
client.update(channel, { description: newDescription })
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if channel}
|
||||||
|
<div class="flex-col flex-gap-3">
|
||||||
|
<EditBox
|
||||||
|
label={clazz.label}
|
||||||
|
icon={clazz.icon}
|
||||||
|
bind:value={channel.name}
|
||||||
|
placeholder={clazz.label}
|
||||||
|
maxWidth="39rem"
|
||||||
|
focus
|
||||||
|
on:change={onNameChange}
|
||||||
|
/>
|
||||||
|
<EditBox
|
||||||
|
label={chunter.string.Topic}
|
||||||
|
bind:value={channel.topic}
|
||||||
|
placeholder={chunter.string.Topic}
|
||||||
|
maxWidth="39rem"
|
||||||
|
focus
|
||||||
|
on:change={onTopicChange}
|
||||||
|
/>
|
||||||
|
<EditBox
|
||||||
|
label={chunter.string.ChannelDescription}
|
||||||
|
bind:value={channel.description}
|
||||||
|
placeholder={chunter.string.ChannelDescription}
|
||||||
|
maxWidth="39rem"
|
||||||
|
focus
|
||||||
|
on:change={onDescriptionChange}
|
||||||
|
/>
|
||||||
|
<!-- TODO: implement Attachments here -->
|
||||||
|
</div>
|
||||||
|
{/if}
|
39
plugins/chunter-resources/src/components/Header.svelte
Normal file
39
plugins/chunter-resources/src/components/Header.svelte
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<!--
|
||||||
|
// 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 { Asset } from '@anticrm/platform'
|
||||||
|
import { Icon } from '@anticrm/ui'
|
||||||
|
|
||||||
|
export let icon: Asset | undefined
|
||||||
|
export let label: string
|
||||||
|
export let description: string | undefined
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="ac-header__wrap-description">
|
||||||
|
<div class="ac-header__wrap-title" on:click>
|
||||||
|
{#if icon}<div class="ac-header__icon"><Icon {icon} size={'small'} /></div>{/if}
|
||||||
|
<span class="ac-header__title">{label}</span>
|
||||||
|
</div>
|
||||||
|
{#if description}<span class="ac-header__description">{description}</span>{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.ac-header__wrap-title:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
span {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -12,7 +12,6 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { AnySvelteComponent } from '@anticrm/ui'
|
import type { AnySvelteComponent } from '@anticrm/ui'
|
||||||
import Check from './icons/Check.svelte'
|
import Check from './icons/Check.svelte'
|
||||||
@ -23,13 +22,16 @@
|
|||||||
count: number
|
count: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export let reactions: Reaction[] = [{ icon: Check, count: 3}, { icon: Heart, count: 10}]
|
export let reactions: Reaction[] = [
|
||||||
|
{ icon: Check, count: 3 },
|
||||||
|
{ icon: Heart, count: 10 }
|
||||||
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{#each reactions as reaction}
|
{#each reactions as reaction}
|
||||||
<div class="flex-row-center reaction">
|
<div class="flex-row-center reaction">
|
||||||
<svelte:component this={reaction.icon} size={'medium'}/>
|
<svelte:component this={reaction.icon} size={'medium'} />
|
||||||
<div class="caption-color counter">{reaction.count}</div>
|
<div class="caption-color counter">{reaction.count}</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
@ -40,7 +42,11 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
.counter { margin-left: .25rem; }
|
.counter {
|
||||||
.reaction + .reaction { margin-left: 1rem; }
|
margin-left: 0.25rem;
|
||||||
|
}
|
||||||
|
.reaction + .reaction {
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -12,11 +12,10 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import contact,{ Employee } from '@anticrm/contact'
|
import contact, { Employee } from '@anticrm/contact'
|
||||||
import { Ref, Timestamp } from '@anticrm/core'
|
import { Ref, Timestamp } from '@anticrm/core'
|
||||||
import { Avatar,createQuery } from '@anticrm/presentation'
|
import { Avatar, createQuery } from '@anticrm/presentation'
|
||||||
import { Label, TimeSince } from '@anticrm/ui'
|
import { Label, TimeSince } from '@anticrm/ui'
|
||||||
import chunter from '../plugin'
|
import chunter from '../plugin'
|
||||||
|
|
||||||
@ -32,16 +31,19 @@
|
|||||||
$: updateQuery(employees)
|
$: updateQuery(employees)
|
||||||
|
|
||||||
function updateQuery (employees: Set<Ref<Employee>>) {
|
function updateQuery (employees: Set<Ref<Employee>>) {
|
||||||
query.query(contact.class.Employee, {
|
query.query(
|
||||||
_id: { $in: Array.from(employees) }
|
contact.class.Employee,
|
||||||
}, (res) => {
|
{
|
||||||
showReplies = res
|
_id: { $in: Array.from(employees) }
|
||||||
}, {
|
},
|
||||||
limit: shown
|
(res) => {
|
||||||
})
|
showReplies = res
|
||||||
|
},
|
||||||
|
{
|
||||||
|
limit: shown
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex-row-center container cursor-pointer" on:click>
|
<div class="flex-row-center container cursor-pointer" on:click>
|
||||||
@ -53,7 +55,9 @@
|
|||||||
<div class="reply"><span>+{employees.size - shown}</span></div>
|
<div class="reply"><span>+{employees.size - shown}</span></div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="whitespace-nowrap ml-2 mr-2 over-underline"><Label label={chunter.string.RepliesCount} params={{ replies: replies.length }} /></div>
|
<div class="whitespace-nowrap ml-2 mr-2 over-underline">
|
||||||
|
<Label label={chunter.string.RepliesCount} params={{ replies: replies.length }} />
|
||||||
|
</div>
|
||||||
{#if replies.length > 1}
|
{#if replies.length > 1}
|
||||||
<div class="mr-1">
|
<div class="mr-1">
|
||||||
<Label label={chunter.string.LastReply} />
|
<Label label={chunter.string.LastReply} />
|
||||||
@ -82,9 +86,9 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
width: 1.5rem;
|
width: 1.5rem;
|
||||||
height: 1.5rem;
|
height: 1.5rem;
|
||||||
font-size: .75rem;
|
font-size: 0.75rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: .5;
|
line-height: 0.5;
|
||||||
color: var(--theme-caption-color);
|
color: var(--theme-caption-color);
|
||||||
background-color: var(--theme-bg-selection);
|
background-color: var(--theme-bg-selection);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
@ -100,4 +104,4 @@
|
|||||||
background-color: var(--theme-bg-color);
|
background-color: var(--theme-bg-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Backlink } from '@anticrm/chunter'
|
import type { Backlink } from '@anticrm/chunter'
|
||||||
import { MessageViewer } from '@anticrm/presentation'
|
import { MessageViewer } from '@anticrm/presentation'
|
||||||
@ -22,4 +21,4 @@
|
|||||||
// export let edit: boolean = false
|
// export let edit: boolean = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<MessageViewer message={value.message}/>
|
<MessageViewer message={value.message} />
|
||||||
|
@ -12,13 +12,11 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Comment } from '@anticrm/chunter'
|
import type { Comment } from '@anticrm/chunter'
|
||||||
import type { TxCreateDoc } from '@anticrm/core'
|
import type { TxCreateDoc } from '@anticrm/core'
|
||||||
import { getClient, MessageViewer } from '@anticrm/presentation'
|
import { getClient, MessageViewer } from '@anticrm/presentation'
|
||||||
import { AttachmentDocList } from '@anticrm/attachment-resources'
|
import { AttachmentDocList } from '@anticrm/attachment-resources'
|
||||||
import { ReferenceInput } from '@anticrm/text-editor'
|
|
||||||
import { Button } from '@anticrm/ui'
|
import { Button } from '@anticrm/ui'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import { updateBacklinks } from '../../backlinks'
|
import { updateBacklinks } from '../../backlinks'
|
||||||
@ -36,13 +34,21 @@
|
|||||||
|
|
||||||
async function onMessage (event: CustomEvent) {
|
async function onMessage (event: CustomEvent) {
|
||||||
const { message, attachments } = event.detail
|
const { message, attachments } = event.detail
|
||||||
await client.updateCollection(tx.objectClass, tx.objectSpace, tx.objectId, value.attachedTo, value.attachedToClass, value.collection, {
|
await client.updateCollection(
|
||||||
message,
|
tx.objectClass,
|
||||||
attachments
|
tx.objectSpace,
|
||||||
})
|
tx.objectId,
|
||||||
|
value.attachedTo,
|
||||||
|
value.attachedToClass,
|
||||||
|
value.collection,
|
||||||
|
{
|
||||||
|
message,
|
||||||
|
attachments
|
||||||
|
}
|
||||||
|
)
|
||||||
// We need to update backlinks before and after.
|
// We need to update backlinks before and after.
|
||||||
await updateBacklinks(client, value.attachedTo, value.attachedToClass, value._id, event.detail)
|
await updateBacklinks(client, value.attachedTo, value.attachedToClass, value._id, event.detail)
|
||||||
|
|
||||||
dispatch('close', false)
|
dispatch('close', false)
|
||||||
}
|
}
|
||||||
let refInput: AttachmentRefInput
|
let refInput: AttachmentRefInput
|
||||||
@ -50,19 +56,32 @@
|
|||||||
|
|
||||||
<div class:editing>
|
<div class:editing>
|
||||||
{#if edit}
|
{#if edit}
|
||||||
<AttachmentRefInput bind:this={refInput} _class={value._class} objectId={value._id} space={value.space} content={value.message} on:message={onMessage} showSend={false} />
|
<AttachmentRefInput
|
||||||
<div class='flex-row-reverse gap-2 reverse'>
|
bind:this={refInput}
|
||||||
<Button label={chunter.string.EditCancel} on:click={() => {
|
_class={value._class}
|
||||||
dispatch('close', false)
|
objectId={value._id}
|
||||||
}}/>
|
space={value.space}
|
||||||
|
content={value.message}
|
||||||
|
on:message={onMessage}
|
||||||
|
showSend={false}
|
||||||
|
/>
|
||||||
|
<div class="flex-row-reverse gap-2 reverse">
|
||||||
|
<Button
|
||||||
|
label={chunter.string.EditCancel}
|
||||||
|
on:click={() => {
|
||||||
|
dispatch('close', false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Button label={chunter.string.EditUpdate} on:click={() => refInput.submit()} />
|
<Button label={chunter.string.EditUpdate} on:click={() => refInput.submit()} />
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<MessageViewer message={value.message}/>
|
<MessageViewer message={value.message} />
|
||||||
<AttachmentDocList {value} />
|
<AttachmentDocList {value} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.editing { border: 1px solid var(--primary-button-focused-border); }
|
.editing {
|
||||||
|
border: 1px solid var(--primary-button-focused-border);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -4,5 +4,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svg class="svg-{size}" {fill} viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
<svg class="svg-{size}" {fill} viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M16.2,18c-0.1,0-0.2,0-0.3-0.1l-6-4l-6,4c-0.2,0.1-0.4,0.1-0.5,0c-0.2-0.1-0.3-0.3-0.3-0.4V4.2 C3.2,3,4.3,2,5.5,2h8.9c1.3,0,2.3,1,2.3,2.2v13.3c0,0.2-0.1,0.4-0.3,0.4C16.4,18,16.3,18,16.2,18z M10,12.8c0.1,0,0.2,0,0.3,0.1 l5.5,3.6V4.2c0-0.6-0.6-1.2-1.3-1.2H5.5C4.8,3,4.2,3.5,4.2,4.2v12.4l5.5-3.6C9.8,12.9,9.9,12.8,10,12.8z"/>
|
<path
|
||||||
|
d="M16.2,18c-0.1,0-0.2,0-0.3-0.1l-6-4l-6,4c-0.2,0.1-0.4,0.1-0.5,0c-0.2-0.1-0.3-0.3-0.3-0.4V4.2 C3.2,3,4.3,2,5.5,2h8.9c1.3,0,2.3,1,2.3,2.2v13.3c0,0.2-0.1,0.4-0.3,0.4C16.4,18,16.3,18,16.2,18z M10,12.8c0.1,0,0.2,0,0.3,0.1 l5.5,3.6V4.2c0-0.6-0.6-1.2-1.3-1.2H5.5C4.8,3,4.2,3.5,4.2,4.2v12.4l5.5-3.6C9.8,12.9,9.9,12.8,10,12.8z"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svg class="svg-{size}" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
<svg class="svg-{size}" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill={'#27B166'} d="M10.1,1C5.2,0.9,1.1,4.9,1,9.8V10c0,5,4,9,9,9c4.9,0,9-4,9-8.9C19,5.1,15.1,1.1,10.1,1z"/>
|
<path fill={'#27B166'} d="M10.1,1C5.2,0.9,1.1,4.9,1,9.8V10c0,5,4,9,9,9c4.9,0,9-4,9-8.9C19,5.1,15.1,1.1,10.1,1z" />
|
||||||
<polygon fill={'#FCFCFC'} points="8.5,14.3 5.4,11.1 6.6,9.9 8.5,11.7 13.9,6.4 15.1,7.6 "/>
|
<polygon fill={'#FCFCFC'} points="8.5,14.3 5.4,11.1 6.6,9.9 8.5,11.7 13.9,6.4 15.1,7.6 " />
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -4,5 +4,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svg class="svg-{size}" viewBox="0 0 24 24" {fill} xmlns="http://www.w3.org/2000/svg">
|
<svg class="svg-{size}" viewBox="0 0 24 24" {fill} xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M12.8,12l7.6-7.6c0.2-0.2,0.2-0.6,0-0.8s-0.6-0.2-0.8,0L12,11.2L4.4,3.6c-0.2-0.2-0.6-0.2-0.8,0s-0.2,0.6,0,0.8 l7.6,7.6l-7.6,7.6c-0.2,0.2-0.2,0.6,0,0.8c0.1,0.1,0.3,0.2,0.4,0.2s0.3-0.1,0.4-0.2l7.6-7.6l7.6,7.6c0.1,0.1,0.3,0.2,0.4,0.2 s0.3-0.1,0.4-0.2c0.2-0.2,0.2-0.6,0-0.8L12.8,12z"/>
|
<path
|
||||||
|
d="M12.8,12l7.6-7.6c0.2-0.2,0.2-0.6,0-0.8s-0.6-0.2-0.8,0L12,11.2L4.4,3.6c-0.2-0.2-0.6-0.2-0.8,0s-0.2,0.6,0,0.8 l7.6,7.6l-7.6,7.6c-0.2,0.2-0.2,0.6,0,0.8c0.1,0.1,0.3,0.2,0.4,0.2s0.3-0.1,0.4-0.2l7.6-7.6l7.6,7.6c0.1,0.1,0.3,0.2,0.4,0.2 s0.3-0.1,0.4-0.2c0.2-0.2,0.2-0.6,0-0.8L12.8,12z"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -13,12 +13,13 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let size: 'small' | 'medium' | 'large'
|
export let size: 'small' | 'medium' | 'large'
|
||||||
const fill: string = 'var(--theme-caption-color)'
|
const fill: string = 'var(--theme-caption-color)'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svg class="svg-{size}" {fill} viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
<svg class="svg-{size}" {fill} viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M10 2C14.4183 2 18 5.58172 18 10C18 14.4183 14.4183 18 10 18C5.58172 18 2 14.4183 2 10C2 5.58172 5.58172 2 10 2ZM10 3C6.13401 3 3 6.13401 3 10C3 13.866 6.13401 17 10 17C13.866 17 17 13.866 17 10C17 6.13401 13.866 3 10 3ZM7.15467 12.4273C8.66416 13.9463 11.0877 14.0045 12.6671 12.5961L12.8453 12.4273C13.04 12.2314 13.3566 12.2304 13.5524 12.4251C13.7265 12.5981 13.7467 12.8674 13.6123 13.0627L13.5547 13.1322L13.5323 13.1545C11.5691 15.1054 8.39616 15.0953 6.44533 13.1322C6.25069 12.9363 6.25169 12.6197 6.44757 12.4251C6.64344 12.2304 6.96002 12.2314 7.15467 12.4273ZM12.5 7.5C13.0523 7.5 13.5 7.94772 13.5 8.5C13.5 9.05228 13.0523 9.5 12.5 9.5C11.9477 9.5 11.5 9.05228 11.5 8.5C11.5 7.94772 11.9477 7.5 12.5 7.5ZM7.5 7.5C8.05228 7.5 8.5 7.94772 8.5 8.5C8.5 9.05228 8.05228 9.5 7.5 9.5C6.94772 9.5 6.5 9.05228 6.5 8.5C6.5 7.94772 6.94772 7.5 7.5 7.5Z"/>
|
<path
|
||||||
|
d="M10 2C14.4183 2 18 5.58172 18 10C18 14.4183 14.4183 18 10 18C5.58172 18 2 14.4183 2 10C2 5.58172 5.58172 2 10 2ZM10 3C6.13401 3 3 6.13401 3 10C3 13.866 6.13401 17 10 17C13.866 17 17 13.866 17 10C17 6.13401 13.866 3 10 3ZM7.15467 12.4273C8.66416 13.9463 11.0877 14.0045 12.6671 12.5961L12.8453 12.4273C13.04 12.2314 13.3566 12.2304 13.5524 12.4251C13.7265 12.5981 13.7467 12.8674 13.6123 13.0627L13.5547 13.1322L13.5323 13.1545C11.5691 15.1054 8.39616 15.0953 6.44533 13.1322C6.25069 12.9363 6.25169 12.6197 6.44757 12.4251C6.64344 12.2304 6.96002 12.2314 7.15467 12.4273ZM12.5 7.5C13.0523 7.5 13.5 7.94772 13.5 8.5C13.5 9.05228 13.0523 9.5 12.5 9.5C11.9477 9.5 11.5 9.05228 11.5 8.5C11.5 7.94772 11.9477 7.5 12.5 7.5ZM7.5 7.5C8.05228 7.5 8.5 7.94772 8.5 8.5C8.5 9.05228 8.05228 9.5 7.5 9.5C6.94772 9.5 6.5 9.05228 6.5 8.5C6.5 7.94772 6.94772 7.5 7.5 7.5Z"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -3,5 +3,8 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svg class="svg-{size}" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
<svg class="svg-{size}" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill={'#F96E50'} d="M1,6.8c0-1.3,0.5-2.6,1.5-3.5c0.9-0.9,2.2-1.4,3.5-1.4c1.6,0,3,0.6,4.1,1.8c1-1.2,2.5-1.8,4.1-1.8 c1.3,0,2.6,0.5,3.5,1.4C18.5,4.2,19,5.5,19,6.8c0,4.8-5.8,8.5-9,11.3C6.7,15.2,1,11.6,1,6.8z"/>
|
<path
|
||||||
|
fill={'#F96E50'}
|
||||||
|
d="M1,6.8c0-1.3,0.5-2.6,1.5-3.5c0.9-0.9,2.2-1.4,3.5-1.4c1.6,0,3,0.6,4.1,1.8c1-1.2,2.5-1.8,4.1-1.8 c1.3,0,2.6,0.5,3.5,1.4C18.5,4.2,19,5.5,19,6.8c0,4.8-5.8,8.5-9,11.3C6.7,15.2,1,11.6,1,6.8z"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import chunter, { Comment, Message, ThreadMessage } from '@anticrm/chunter'
|
import chunter, { 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 TxBacklinkCreate from './components/activity/TxBacklinkCreate.svelte'
|
import TxBacklinkCreate from './components/activity/TxBacklinkCreate.svelte'
|
||||||
@ -21,10 +21,12 @@ import TxBacklinkReference from './components/activity/TxBacklinkReference.svelt
|
|||||||
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 ChannelView from './components/ChannelView.svelte'
|
import ChannelView from './components/ChannelView.svelte'
|
||||||
|
import ChannelHeader from './components/ChannelHeader.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 EditChannel from './components/EditChannel.svelte'
|
||||||
import ThreadView from './components/ThreadView.svelte'
|
import ThreadView from './components/ThreadView.svelte'
|
||||||
|
|
||||||
export { CommentsPresenter }
|
export { CommentsPresenter }
|
||||||
@ -64,10 +66,12 @@ export default async (): Promise<Resources> => ({
|
|||||||
component: {
|
component: {
|
||||||
CommentInput,
|
CommentInput,
|
||||||
CreateChannel,
|
CreateChannel,
|
||||||
|
ChannelHeader,
|
||||||
ChannelView,
|
ChannelView,
|
||||||
CommentPresenter,
|
CommentPresenter,
|
||||||
CommentsPresenter,
|
CommentsPresenter,
|
||||||
ChannelPresenter,
|
ChannelPresenter,
|
||||||
|
EditChannel,
|
||||||
ThreadView
|
ThreadView
|
||||||
},
|
},
|
||||||
activity: {
|
activity: {
|
||||||
|
@ -22,7 +22,9 @@ import type { AnyComponent } from '@anticrm/ui'
|
|||||||
export default mergeIds(chunterId, chunter, {
|
export default mergeIds(chunterId, chunter, {
|
||||||
component: {
|
component: {
|
||||||
CreateChannel: '' as AnyComponent,
|
CreateChannel: '' as AnyComponent,
|
||||||
ChannelView: '' as AnyComponent
|
ChannelHeader: '' as AnyComponent,
|
||||||
|
ChannelView: '' as AnyComponent,
|
||||||
|
EditChannel: '' as AnyComponent
|
||||||
},
|
},
|
||||||
actionImpl: {
|
actionImpl: {
|
||||||
SubscribeMessage: '' as Resource<(object: Doc) => Promise<void>>,
|
SubscribeMessage: '' as Resource<(object: Doc) => Promise<void>>,
|
||||||
@ -39,8 +41,10 @@ export default mergeIds(chunterId, chunter, {
|
|||||||
ChannelDescription: '' as IntlString,
|
ChannelDescription: '' as IntlString,
|
||||||
MakePrivate: '' as IntlString,
|
MakePrivate: '' as IntlString,
|
||||||
MakePrivateDescription: '' as IntlString,
|
MakePrivateDescription: '' as IntlString,
|
||||||
|
Members: '' as IntlString,
|
||||||
In: '' as IntlString,
|
In: '' as IntlString,
|
||||||
Replies: '' as IntlString,
|
Replies: '' as IntlString,
|
||||||
|
Topic: '' as IntlString,
|
||||||
Thread: '' as IntlString,
|
Thread: '' as IntlString,
|
||||||
RepliesCount: '' as IntlString,
|
RepliesCount: '' as IntlString,
|
||||||
LastReply: '' as IntlString,
|
LastReply: '' as IntlString,
|
||||||
|
@ -1,16 +1,20 @@
|
|||||||
import contact, { EmployeeAccount } from '@anticrm/contact'
|
import contact, { EmployeeAccount } from '@anticrm/contact'
|
||||||
import { Account, Client, Ref, Timestamp } from '@anticrm/core'
|
import { Account, Class, Client, Obj, Ref } from '@anticrm/core'
|
||||||
|
import { Asset } from '@anticrm/platform'
|
||||||
|
|
||||||
export async function getUser (client: Client, user: Ref<EmployeeAccount> | Ref<Account>): Promise<EmployeeAccount | undefined> {
|
export async function getUser (
|
||||||
|
client: Client,
|
||||||
|
user: Ref<EmployeeAccount> | Ref<Account>
|
||||||
|
): Promise<EmployeeAccount | undefined> {
|
||||||
return await client.findOne(contact.class.EmployeeAccount, { _id: user as Ref<EmployeeAccount> })
|
return await client.findOne(contact.class.EmployeeAccount, { _id: user as Ref<EmployeeAccount> })
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTime (time: number): string {
|
export function getTime (time: number): string {
|
||||||
let options: Intl.DateTimeFormatOptions = { hour: 'numeric', minute: 'numeric'}
|
let options: Intl.DateTimeFormatOptions = { hour: 'numeric', minute: 'numeric' }
|
||||||
if (!isToday(time)) {
|
if (!isToday(time)) {
|
||||||
options = {
|
options = {
|
||||||
month: 'numeric',
|
month: 'numeric',
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
...options
|
...options
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -21,5 +25,13 @@ export function getTime (time: number): string {
|
|||||||
export function isToday (time: number): boolean {
|
export function isToday (time: number): boolean {
|
||||||
const current = new Date()
|
const current = new Date()
|
||||||
const target = new Date(time)
|
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()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function classIcon (client: Client, _class: Ref<Class<Obj>>): Asset | undefined {
|
||||||
|
return client.getHierarchy().getClass(_class).icon
|
||||||
|
}
|
||||||
|
@ -24,6 +24,7 @@ import { AnyComponent } from '@anticrm/ui'
|
|||||||
*/
|
*/
|
||||||
export interface Channel extends Space {
|
export interface Channel extends Space {
|
||||||
lastMessage?: Timestamp
|
lastMessage?: Timestamp
|
||||||
|
topic?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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");
|
// 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
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
@ -40,6 +40,13 @@ export interface ObjectEditor extends Class<Doc> {
|
|||||||
editor: AnyComponent
|
editor: AnyComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface SpaceHeader extends Class<Doc> {
|
||||||
|
header: AnyComponent
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -178,6 +185,7 @@ const view = plugin(viewId, {
|
|||||||
ObjectEditorHeader: '' as Ref<Mixin<ObjectEditorHeader>>,
|
ObjectEditorHeader: '' as Ref<Mixin<ObjectEditorHeader>>,
|
||||||
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>>,
|
||||||
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>>
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import core, { Class, Doc, Ref, Space, WithLookup } from '@anticrm/core'
|
import core, { Class, Doc, Ref, Space, WithLookup } from '@anticrm/core'
|
||||||
import { IntlString } from '@anticrm/platform'
|
import { IntlString } from '@anticrm/platform'
|
||||||
import { getClient } from '@anticrm/presentation'
|
import { createQuery, getClient } from '@anticrm/presentation'
|
||||||
import type { AnyComponent } from '@anticrm/ui'
|
import { AnyComponent, Component } from '@anticrm/ui'
|
||||||
import view, { Viewlet } from '@anticrm/view'
|
import view, { Viewlet } from '@anticrm/view'
|
||||||
import type { ViewConfiguration } from '@anticrm/workbench'
|
import type { ViewConfiguration } from '@anticrm/workbench'
|
||||||
|
|
||||||
@ -31,14 +31,14 @@
|
|||||||
|
|
||||||
let search: string = ''
|
let search: string = ''
|
||||||
let viewlet: WithLookup<Viewlet> | undefined = undefined
|
let viewlet: WithLookup<Viewlet> | undefined = undefined
|
||||||
let space: Ref<Space> | undefined = undefined
|
let space: Space | undefined
|
||||||
let _class: Ref<Class<Doc>> | undefined = undefined
|
let _class: Ref<Class<Doc>> | undefined = undefined
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
||||||
let viewlets: WithLookup<Viewlet>[] = []
|
let viewlets: WithLookup<Viewlet>[] = []
|
||||||
|
|
||||||
async function update (attachTo?: Ref<Class<Doc>>, currentSpace?: Ref<Space>): Promise<void> {
|
async function update (attachTo?: Ref<Class<Doc>>): Promise<void> {
|
||||||
if (attachTo) {
|
if (attachTo) {
|
||||||
viewlets = await client.findAll(view.class.Viewlet, { attachTo }, {
|
viewlets = await client.findAll(view.class.Viewlet, { attachTo }, {
|
||||||
lookup: {
|
lookup: {
|
||||||
@ -47,12 +47,36 @@
|
|||||||
})
|
})
|
||||||
_class = attachTo
|
_class = attachTo
|
||||||
}
|
}
|
||||||
space = currentSpace
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$: update(currentView?.class, currentSpace)
|
$: update(currentView?.class)
|
||||||
|
|
||||||
|
const query = createQuery()
|
||||||
|
|
||||||
|
$: currentSpace && query.query(core.class.Space, {
|
||||||
|
_id: currentSpace
|
||||||
|
}, (res) => {
|
||||||
|
space = res[0]
|
||||||
|
}, {
|
||||||
|
limit: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
const hierarchy = client.getHierarchy()
|
||||||
|
async function getHeader (_class: Ref<Class<Space>>): Promise<AnyComponent | undefined> {
|
||||||
|
const clazz = hierarchy.getClass(_class)
|
||||||
|
const headerMixin = hierarchy.as(clazz, view.mixin.SpaceHeader)
|
||||||
|
if (headerMixin?.header == null && clazz.extends != null) return getHeader(clazz.extends)
|
||||||
|
return headerMixin.header
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<SpaceHeader spaceId={space} {viewlets} {createItemDialog} {createItemLabel} bind:search={search} bind:viewlet={viewlet} />
|
|
||||||
{#if _class && space}
|
{#if _class && space}
|
||||||
<SpaceContent {space} {_class} {search} {viewlet} />
|
{#await getHeader(space._class) then header}
|
||||||
|
{#if header}
|
||||||
|
<Component is={header} props={{ spaceId: space._id, viewlets, createItemDialog, createItemLabel }} />
|
||||||
|
{:else}
|
||||||
|
<SpaceHeader spaceId={space._id} {viewlets} {createItemDialog} {createItemLabel} bind:search={search} bind:viewlet={viewlet} />
|
||||||
|
{/if}
|
||||||
|
{/await}
|
||||||
|
<SpaceContent space={space._id} {_class} {search} {viewlet} />
|
||||||
{/if}
|
{/if}
|
||||||
|
Loading…
Reference in New Issue
Block a user