mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-23 03:22:19 +03: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");
|
||||
// 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 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,
|
||||
|
@ -34,6 +34,7 @@ export async function createGeneral (tx: TxOperations): Promise<void> {
|
||||
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<void> {
|
||||
await tx.createDoc(chunter.class.Channel, core.space.Space, {
|
||||
name: 'random',
|
||||
description: 'Random Talks',
|
||||
topic: 'Random Talks',
|
||||
private: false,
|
||||
archived: false,
|
||||
members: []
|
||||
|
@ -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<<T extends Doc>(doc: T, client: Client) => Promise<Status<{}>>>
|
||||
@ -123,6 +129,7 @@ export function createModel (builder: Builder): void {
|
||||
TObjectFactory,
|
||||
TObjectEditorHeader,
|
||||
THTMLPresenter,
|
||||
TSpaceHeader,
|
||||
TTextPresenter,
|
||||
TIgnoreActions
|
||||
)
|
||||
|
@ -11,6 +11,7 @@
|
||||
"AddSocialLinks": "Add social links",
|
||||
"Change": "Change",
|
||||
"Remove": "Remove",
|
||||
"Members": "Members",
|
||||
"Search": "Search...",
|
||||
"Unassigned": "Unassigned"
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
"AddSocialLinks": "Добавить контактную информацию",
|
||||
"Change": "Изменить",
|
||||
"Remove": "Удалить",
|
||||
"Members": "Участники",
|
||||
"Search": "Поиск...",
|
||||
"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 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'
|
||||
|
@ -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
|
||||
},
|
||||
|
@ -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",
|
||||
|
@ -13,6 +13,7 @@
|
||||
"EditUpdate": "Сохранить...",
|
||||
"EditCancel": "Отменить",
|
||||
"Comments" : "Комментарии",
|
||||
"Members": "Участники",
|
||||
"MentionedIn": "упомянул(а) ",
|
||||
"ContactInfo": "Контактная информация",
|
||||
"Content": "Содержимое",
|
||||
|
@ -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<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> })
|
||||
}
|
||||
|
||||
@ -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<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 nodes: Array<NodeListOf<ChildNode>> = [kids]
|
||||
@ -40,7 +53,7 @@ function extractBacklinks (backlinkId: Ref<Doc>, backlinkClass: Ref<Class<Doc>>,
|
||||
const el = kid as HTMLElement
|
||||
const ato = el.getAttribute('data-id') as Ref<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) {
|
||||
result.push({
|
||||
attachedTo: ato,
|
||||
@ -59,20 +72,44 @@ function extractBacklinks (backlinkId: Ref<Doc>, backlinkClass: Ref<Class<Doc>>,
|
||||
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 doc = parser.parseFromString(content, 'application/xhtml+xml')
|
||||
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)
|
||||
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<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 }
|
||||
if (attachedDocId !== undefined) {
|
||||
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.
|
||||
for (const c of current) {
|
||||
// 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) {
|
||||
// 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<Doc
|
||||
// Add missing backlinks
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -15,11 +15,11 @@
|
||||
<script lang="ts">
|
||||
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'
|
||||
|
@ -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
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import type { IntlString } from "@anticrm/platform"
|
||||
import { Label } from "@anticrm/ui"
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import { Label } from '@anticrm/ui'
|
||||
|
||||
export let title: IntlString
|
||||
export let line: boolean = false
|
||||
@ -24,9 +23,9 @@
|
||||
export let isNew: boolean = false
|
||||
</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} />
|
||||
<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>
|
||||
|
||||
<style lang="scss">
|
||||
@ -35,7 +34,6 @@
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: var(--theme-chat-divider);
|
||||
|
||||
}
|
||||
.new {
|
||||
.line {
|
||||
|
@ -12,14 +12,13 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { AttachmentRefInput } from '@anticrm/attachment-resources'
|
||||
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 { getClient } from '@anticrm/presentation'
|
||||
import { getCurrentLocation,navigate } from '@anticrm/ui'
|
||||
import { getCurrentLocation, navigate } from '@anticrm/ui'
|
||||
import { createBacklinks } from '../backlinks'
|
||||
import chunter from '../plugin'
|
||||
import Channel from './Channel.svelte'
|
||||
@ -35,15 +34,20 @@
|
||||
const { message, attachments } = event.detail
|
||||
const me = getCurrentAccount()._id
|
||||
const txFactory = new TxFactory(me)
|
||||
const tx = txFactory.createTxCreateDoc<Message>(_class, space, {
|
||||
attachedTo: space,
|
||||
attachedToClass: chunter.class.Channel,
|
||||
collection: 'messages',
|
||||
content: message,
|
||||
createOn: 0,
|
||||
createBy: me,
|
||||
attachments
|
||||
}, _id)
|
||||
const tx = txFactory.createTxCreateDoc<Message>(
|
||||
_class,
|
||||
space,
|
||||
{
|
||||
attachedTo: space,
|
||||
attachedToClass: chunter.class.Channel,
|
||||
collection: 'messages',
|
||||
content: message,
|
||||
createOn: 0,
|
||||
createBy: me,
|
||||
attachments
|
||||
},
|
||||
_id
|
||||
)
|
||||
tx.attributes.createOn = tx.modifiedOn
|
||||
await notificationClient.updateLastView(space, chunter.class.Channel, tx.modifiedOn, true)
|
||||
await client.tx(tx)
|
||||
@ -59,12 +63,16 @@
|
||||
loc.path[3] = _id
|
||||
navigate(loc)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<Channel {space} on:openThread={(e) => { openThread(e.detail) }} />
|
||||
<Channel
|
||||
{space}
|
||||
on:openThread={(e) => {
|
||||
openThread(e.detail)
|
||||
}}
|
||||
/>
|
||||
<div class="reference">
|
||||
<AttachmentRefInput {space} {_class} objectId={_id} on:message={onMessage}/>
|
||||
<AttachmentRefInput {space} {_class} objectId={_id} on:message={onMessage} />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -12,7 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import type { Person } from '@anticrm/contact'
|
||||
import { formatName } from '@anticrm/contact'
|
||||
@ -25,7 +24,6 @@
|
||||
|
||||
export let user: Person
|
||||
export let message: IMessage
|
||||
|
||||
</script>
|
||||
|
||||
<div class="flex-nowrap">
|
||||
@ -44,16 +42,16 @@
|
||||
margin-right: 1.25rem;
|
||||
|
||||
.header {
|
||||
margin-bottom: .25rem;
|
||||
margin-bottom: 0.25rem;
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
line-height: 150%;
|
||||
color: var(--theme-caption-color);
|
||||
|
||||
span {
|
||||
margin-left: .5rem;
|
||||
margin-left: 0.5rem;
|
||||
font-weight: 400;
|
||||
font-size: .875rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--theme-content-dark-color);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 Hardcore Engineering Inc.
|
||||
@ -26,15 +25,23 @@
|
||||
export let object: Doc
|
||||
const _class = chunter.class.Comment
|
||||
let _id: Ref<Comment> = generateId()
|
||||
|
||||
|
||||
async function onMessage (event: CustomEvent) {
|
||||
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
|
||||
await createBacklinks(client, object._id, object._class, _id, message)
|
||||
_id = generateId()
|
||||
}
|
||||
|
||||
</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
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { Ref, Doc, SortingOrder } from '@anticrm/core'
|
||||
|
||||
@ -25,10 +24,14 @@
|
||||
|
||||
let comments: Comment[] = []
|
||||
const query = createQuery()
|
||||
$: query.query(chunter.class.Comment, { attachedTo: objectId }, (res) => {
|
||||
comments = res
|
||||
}, { limit: 3, sort: { modifiedOn: SortingOrder.Descending } })
|
||||
|
||||
$: query.query(
|
||||
chunter.class.Comment,
|
||||
{ attachedTo: objectId },
|
||||
(res) => {
|
||||
comments = res
|
||||
},
|
||||
{ limit: 3, sort: { modifiedOn: SortingOrder.Descending } }
|
||||
)
|
||||
</script>
|
||||
|
||||
{#each comments as comment}
|
||||
@ -38,6 +41,10 @@
|
||||
{/each}
|
||||
|
||||
<style lang="scss">
|
||||
.item { max-width: 30rem; }
|
||||
.item + .item { margin-top: 1.25rem; }
|
||||
.item {
|
||||
max-width: 30rem;
|
||||
}
|
||||
.item + .item {
|
||||
margin-top: 1.25rem;
|
||||
}
|
||||
</style>
|
||||
|
@ -13,7 +13,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { AttachmentDocList } from '@anticrm/attachment-resources'
|
||||
import type { Comment } from '@anticrm/chunter'
|
||||
@ -41,7 +40,7 @@
|
||||
<div class="content-trans-color ml-4"><TimeSince value={value.modifiedOn} /></div>
|
||||
</div>
|
||||
<ShowMore limit={126} fixed>
|
||||
<MessageViewer message={value.message}/>
|
||||
<MessageViewer message={value.message} />
|
||||
<AttachmentDocList {value} />
|
||||
</ShowMore>
|
||||
</div>
|
||||
@ -56,6 +55,6 @@
|
||||
display: inline-flex;
|
||||
justify-content: space-between;
|
||||
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
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import type { Doc } from '@anticrm/core'
|
||||
import { Tooltip, IconThread } from '@anticrm/ui'
|
||||
@ -28,9 +27,9 @@
|
||||
{#if value && value.comments && value.comments > 0}
|
||||
<Tooltip label={chunter.string.Comments} component={CommentPopup} props={{ objectId: value._id }}>
|
||||
<div class="sm-tool-icon ml-1 mr-1">
|
||||
<span class="icon"><IconThread {size}/></span>
|
||||
<span class="icon"><IconThread {size} /></span>
|
||||
{#if showCounter}
|
||||
{value.comments}
|
||||
{value.comments}
|
||||
{/if}
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
@ -12,7 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { IconFolder, EditBox, ToggleWithLabel, Grid } from '@anticrm/ui'
|
||||
@ -25,16 +24,15 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let name: string = ''
|
||||
let description: string = ''
|
||||
export function canClose(): boolean {
|
||||
export function canClose (): boolean {
|
||||
return name === ''
|
||||
}
|
||||
const client = getClient()
|
||||
|
||||
function createChannel() {
|
||||
function createChannel () {
|
||||
client.createDoc(chunter.class.Channel, core.space.Space, {
|
||||
name,
|
||||
description,
|
||||
description: '',
|
||||
private: false,
|
||||
archived: false,
|
||||
members: [getCurrentAccount()._id]
|
||||
@ -43,10 +41,12 @@
|
||||
</script>
|
||||
|
||||
<SpaceCreateCard
|
||||
label={chunter.string.CreateChannel}
|
||||
label={chunter.string.CreateChannel}
|
||||
okAction={createChannel}
|
||||
canSave={name ? true : false}
|
||||
on:close={() => { dispatch('close') }}
|
||||
canSave={!!name}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
>
|
||||
<Grid column={1} rowGap={1.5}>
|
||||
<EditBox
|
||||
@ -57,6 +57,6 @@
|
||||
maxWidth={'16rem'}
|
||||
focus
|
||||
/>
|
||||
<ToggleWithLabel label={chunter.string.MakePrivate} description={chunter.string.MakePrivateDescription}/>
|
||||
<ToggleWithLabel label={chunter.string.MakePrivate} description={chunter.string.MakePrivateDescription} />
|
||||
</Grid>
|
||||
</SpaceCreateCard>
|
||||
|
@ -12,9 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { Timestamp } from "@anticrm/core"
|
||||
import { Timestamp } from '@anticrm/core'
|
||||
|
||||
export let value: Timestamp
|
||||
export let line: boolean = false
|
||||
@ -32,7 +31,7 @@
|
||||
}
|
||||
</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>
|
||||
|
||||
@ -44,10 +43,10 @@
|
||||
|
||||
.title {
|
||||
position: relative;
|
||||
padding: .375rem .75rem;
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-weight: 600;
|
||||
font-size: .75rem;
|
||||
letter-spacing: .5;
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.5;
|
||||
text-transform: uppercase;
|
||||
color: var(--theme-content-trans-color);
|
||||
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
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import type { AnySvelteComponent } from '@anticrm/ui'
|
||||
import Check from './icons/Check.svelte'
|
||||
@ -23,13 +22,16 @@
|
||||
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>
|
||||
|
||||
<div class="container">
|
||||
{#each reactions as 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>
|
||||
{/each}
|
||||
@ -40,7 +42,11 @@
|
||||
display: flex;
|
||||
user-select: none;
|
||||
|
||||
.counter { margin-left: .25rem; }
|
||||
.reaction + .reaction { margin-left: 1rem; }
|
||||
.counter {
|
||||
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
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import contact,{ Employee } from '@anticrm/contact'
|
||||
import contact, { Employee } from '@anticrm/contact'
|
||||
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 chunter from '../plugin'
|
||||
|
||||
@ -32,16 +31,19 @@
|
||||
$: updateQuery(employees)
|
||||
|
||||
function updateQuery (employees: Set<Ref<Employee>>) {
|
||||
query.query(contact.class.Employee, {
|
||||
_id: { $in: Array.from(employees) }
|
||||
}, (res) => {
|
||||
showReplies = res
|
||||
}, {
|
||||
limit: shown
|
||||
})
|
||||
query.query(
|
||||
contact.class.Employee,
|
||||
{
|
||||
_id: { $in: Array.from(employees) }
|
||||
},
|
||||
(res) => {
|
||||
showReplies = res
|
||||
},
|
||||
{
|
||||
limit: shown
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="flex-row-center container cursor-pointer" on:click>
|
||||
@ -53,7 +55,9 @@
|
||||
<div class="reply"><span>+{employees.size - shown}</span></div>
|
||||
{/if}
|
||||
</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}
|
||||
<div class="mr-1">
|
||||
<Label label={chunter.string.LastReply} />
|
||||
@ -82,9 +86,9 @@
|
||||
align-items: center;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
font-size: .75rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
line-height: .5;
|
||||
line-height: 0.5;
|
||||
color: var(--theme-caption-color);
|
||||
background-color: var(--theme-bg-selection);
|
||||
border-radius: 50%;
|
||||
@ -100,4 +104,4 @@
|
||||
background-color: var(--theme-bg-color);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
@ -12,7 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import type { Backlink } from '@anticrm/chunter'
|
||||
import { MessageViewer } from '@anticrm/presentation'
|
||||
@ -22,4 +21,4 @@
|
||||
// export let edit: boolean = false
|
||||
</script>
|
||||
|
||||
<MessageViewer message={value.message}/>
|
||||
<MessageViewer message={value.message} />
|
||||
|
@ -12,13 +12,11 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import type { Comment } from '@anticrm/chunter'
|
||||
import type { TxCreateDoc } from '@anticrm/core'
|
||||
import { getClient, MessageViewer } from '@anticrm/presentation'
|
||||
import { AttachmentDocList } from '@anticrm/attachment-resources'
|
||||
import { ReferenceInput } from '@anticrm/text-editor'
|
||||
import { Button } from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { updateBacklinks } from '../../backlinks'
|
||||
@ -36,13 +34,21 @@
|
||||
|
||||
async function onMessage (event: CustomEvent) {
|
||||
const { message, attachments } = event.detail
|
||||
await client.updateCollection(tx.objectClass, tx.objectSpace, tx.objectId, value.attachedTo, value.attachedToClass, value.collection, {
|
||||
message,
|
||||
attachments
|
||||
})
|
||||
await client.updateCollection(
|
||||
tx.objectClass,
|
||||
tx.objectSpace,
|
||||
tx.objectId,
|
||||
value.attachedTo,
|
||||
value.attachedToClass,
|
||||
value.collection,
|
||||
{
|
||||
message,
|
||||
attachments
|
||||
}
|
||||
)
|
||||
// We need to update backlinks before and after.
|
||||
await updateBacklinks(client, value.attachedTo, value.attachedToClass, value._id, event.detail)
|
||||
|
||||
|
||||
dispatch('close', false)
|
||||
}
|
||||
let refInput: AttachmentRefInput
|
||||
@ -50,19 +56,32 @@
|
||||
|
||||
<div class:editing>
|
||||
{#if edit}
|
||||
<AttachmentRefInput bind:this={refInput} _class={value._class} 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)
|
||||
}}/>
|
||||
<AttachmentRefInput
|
||||
bind:this={refInput}
|
||||
_class={value._class}
|
||||
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()} />
|
||||
</div>
|
||||
{:else}
|
||||
<MessageViewer message={value.message}/>
|
||||
<MessageViewer message={value.message} />
|
||||
<AttachmentDocList {value} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.editing { border: 1px solid var(--primary-button-focused-border); }
|
||||
.editing {
|
||||
border: 1px solid var(--primary-button-focused-border);
|
||||
}
|
||||
</style>
|
||||
|
@ -4,5 +4,7 @@
|
||||
</script>
|
||||
|
||||
<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>
|
||||
|
@ -3,6 +3,6 @@
|
||||
</script>
|
||||
|
||||
<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"/>
|
||||
<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 "/>
|
||||
<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 " />
|
||||
</svg>
|
||||
|
@ -4,5 +4,7 @@
|
||||
</script>
|
||||
|
||||
<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>
|
||||
|
@ -13,12 +13,13 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
const fill: string = 'var(--theme-caption-color)'
|
||||
</script>
|
||||
|
||||
<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>
|
||||
|
@ -3,5 +3,8 @@
|
||||
</script>
|
||||
|
||||
<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>
|
||||
|
@ -13,7 +13,7 @@
|
||||
// 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 { Resources } from '@anticrm/platform'
|
||||
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 ChannelPresenter from './components/ChannelPresenter.svelte'
|
||||
import ChannelView from './components/ChannelView.svelte'
|
||||
import ChannelHeader from './components/ChannelHeader.svelte'
|
||||
import CommentInput from './components/CommentInput.svelte'
|
||||
import CommentPresenter from './components/CommentPresenter.svelte'
|
||||
import CommentsPresenter from './components/CommentsPresenter.svelte'
|
||||
import CreateChannel from './components/CreateChannel.svelte'
|
||||
import EditChannel from './components/EditChannel.svelte'
|
||||
import ThreadView from './components/ThreadView.svelte'
|
||||
|
||||
export { CommentsPresenter }
|
||||
@ -64,10 +66,12 @@ export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
CommentInput,
|
||||
CreateChannel,
|
||||
ChannelHeader,
|
||||
ChannelView,
|
||||
CommentPresenter,
|
||||
CommentsPresenter,
|
||||
ChannelPresenter,
|
||||
EditChannel,
|
||||
ThreadView
|
||||
},
|
||||
activity: {
|
||||
|
@ -22,7 +22,9 @@ import type { AnyComponent } from '@anticrm/ui'
|
||||
export default mergeIds(chunterId, chunter, {
|
||||
component: {
|
||||
CreateChannel: '' as AnyComponent,
|
||||
ChannelView: '' as AnyComponent
|
||||
ChannelHeader: '' as AnyComponent,
|
||||
ChannelView: '' as AnyComponent,
|
||||
EditChannel: '' as AnyComponent
|
||||
},
|
||||
actionImpl: {
|
||||
SubscribeMessage: '' as Resource<(object: Doc) => Promise<void>>,
|
||||
@ -39,8 +41,10 @@ export default mergeIds(chunterId, chunter, {
|
||||
ChannelDescription: '' as IntlString,
|
||||
MakePrivate: '' as IntlString,
|
||||
MakePrivateDescription: '' as IntlString,
|
||||
Members: '' as IntlString,
|
||||
In: '' as IntlString,
|
||||
Replies: '' as IntlString,
|
||||
Topic: '' as IntlString,
|
||||
Thread: '' as IntlString,
|
||||
RepliesCount: '' as IntlString,
|
||||
LastReply: '' as IntlString,
|
||||
|
@ -1,16 +1,20 @@
|
||||
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> })
|
||||
}
|
||||
|
||||
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)) {
|
||||
options = {
|
||||
month: 'numeric',
|
||||
day: 'numeric',
|
||||
day: 'numeric',
|
||||
...options
|
||||
}
|
||||
}
|
||||
@ -21,5 +25,13 @@ 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()
|
||||
)
|
||||
}
|
||||
|
||||
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 {
|
||||
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");
|
||||
// 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
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface SpaceHeader extends Class<Doc> {
|
||||
header: AnyComponent
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -178,6 +185,7 @@ const view = plugin(viewId, {
|
||||
ObjectEditorHeader: '' as Ref<Mixin<ObjectEditorHeader>>,
|
||||
ObjectValidator: '' as Ref<Mixin<ObjectValidator>>,
|
||||
ObjectFactory: '' as Ref<Mixin<ObjectFactory>>,
|
||||
SpaceHeader: '' as Ref<Mixin<SpaceHeader>>,
|
||||
IgnoreActions: '' as Ref<Mixin<IgnoreActions>>,
|
||||
HTMLPresenter: '' as Ref<Mixin<HTMLPresenter>>,
|
||||
TextPresenter: '' as Ref<Mixin<TextPresenter>>
|
||||
|
@ -16,8 +16,8 @@
|
||||
<script lang="ts">
|
||||
import core, { Class, Doc, Ref, Space, WithLookup } from '@anticrm/core'
|
||||
import { IntlString } from '@anticrm/platform'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import type { AnyComponent } from '@anticrm/ui'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import { AnyComponent, Component } from '@anticrm/ui'
|
||||
import view, { Viewlet } from '@anticrm/view'
|
||||
import type { ViewConfiguration } from '@anticrm/workbench'
|
||||
|
||||
@ -31,14 +31,14 @@
|
||||
|
||||
let search: string = ''
|
||||
let viewlet: WithLookup<Viewlet> | undefined = undefined
|
||||
let space: Ref<Space> | undefined = undefined
|
||||
let space: Space | undefined
|
||||
let _class: Ref<Class<Doc>> | undefined = undefined
|
||||
|
||||
const client = getClient()
|
||||
|
||||
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) {
|
||||
viewlets = await client.findAll(view.class.Viewlet, { attachTo }, {
|
||||
lookup: {
|
||||
@ -47,12 +47,36 @@
|
||||
})
|
||||
_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>
|
||||
<SpaceHeader spaceId={space} {viewlets} {createItemDialog} {createItemLabel} bind:search={search} bind:viewlet={viewlet} />
|
||||
{#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}
|
||||
|
Loading…
Reference in New Issue
Block a user