mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 21:50:34 +03:00
Threads (#1246)
Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
parent
b6de3da74a
commit
48b3f3facc
@ -30,6 +30,7 @@
|
||||
"@anticrm/ui": "~0.6.0",
|
||||
"@anticrm/view": "~0.6.0",
|
||||
"@anticrm/model-attachment": "~0.6.0",
|
||||
"@anticrm/contact": "~0.6.5",
|
||||
"@anticrm/chunter": "~0.6.1",
|
||||
"@anticrm/chunter-resources": "~0.6.0",
|
||||
"@anticrm/platform": "~0.6.5",
|
||||
|
@ -15,14 +15,15 @@
|
||||
|
||||
import activity from '@anticrm/activity'
|
||||
import type { Backlink, Channel, Comment, Message } from '@anticrm/chunter'
|
||||
import type { Class, Doc, Domain, Ref } from '@anticrm/core'
|
||||
import type { Account, Class, Doc, Domain, Ref, Timestamp } from '@anticrm/core'
|
||||
import { IndexKind } from '@anticrm/core'
|
||||
import { Builder, Collection, Index, Model, Prop, TypeMarkup, UX } from '@anticrm/model'
|
||||
import { ArrOf, Builder, Collection, Index, Model, Prop, TypeMarkup, TypeRef, TypeTimestamp, UX } from '@anticrm/model'
|
||||
import attachment from '@anticrm/model-attachment'
|
||||
import core, { TAttachedDoc, TDoc, TSpace } from '@anticrm/model-core'
|
||||
import view from '@anticrm/model-view'
|
||||
import workbench from '@anticrm/model-workbench'
|
||||
import chunter from './plugin'
|
||||
import contact, { Employee } from '@anticrm/contact'
|
||||
|
||||
export const DOMAIN_CHUNTER = 'chunter' as Domain
|
||||
export const DOMAIN_COMMENT = 'comment' as Domain
|
||||
@ -39,6 +40,18 @@ export class TMessage extends TDoc implements Message {
|
||||
|
||||
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments)
|
||||
attachments?: number
|
||||
|
||||
@Prop(ArrOf(TypeRef(contact.class.Employee)), chunter.string.Replies)
|
||||
replies?: Ref<Employee>[]
|
||||
|
||||
@Prop(TypeTimestamp(), chunter.string.LastReply)
|
||||
lastReply?: Timestamp
|
||||
|
||||
@Prop(TypeRef(core.class.Account), chunter.string.CreateBy)
|
||||
createBy!: Ref<Account>
|
||||
|
||||
@Prop(TypeTimestamp(), chunter.string.Create)
|
||||
createOn!: Timestamp
|
||||
}
|
||||
|
||||
@Model(chunter.class.Comment, core.class.AttachedDoc, DOMAIN_COMMENT)
|
||||
@ -95,7 +108,8 @@ export function createModel (builder: Builder): void {
|
||||
addSpaceLabel: chunter.string.CreateChannel,
|
||||
createComponent: chunter.component.CreateChannel
|
||||
}
|
||||
]
|
||||
],
|
||||
aside: chunter.component.ThreadView
|
||||
}
|
||||
}, chunter.app.Chunter)
|
||||
|
||||
|
@ -13,7 +13,8 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Client } from '@anticrm/core'
|
||||
import { Message } from '@anticrm/chunter'
|
||||
import type { Client, Ref } from '@anticrm/core'
|
||||
import core, { TxOperations } from '@anticrm/core'
|
||||
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model'
|
||||
import chunter from './plugin'
|
||||
@ -55,6 +56,19 @@ export async function createRandom (tx: TxOperations): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function setCreate (client: TxOperations): Promise<void> {
|
||||
const messages = (await client.findAll(chunter.class.Message, { })).filter((m) => m.createBy === undefined).map((m) => m._id)
|
||||
if (messages.length === 0) return
|
||||
const txes = await client.findAll(core.class.TxCreateDoc, { objectId: { $in: messages } })
|
||||
const promises = txes.map(async (tx) => {
|
||||
await client.updateDoc<Message>(chunter.class.Message, tx.objectSpace, tx.objectId as Ref<Message>, {
|
||||
createBy: tx.modifiedBy,
|
||||
createOn: tx.modifiedOn
|
||||
})
|
||||
})
|
||||
await Promise.all(promises)
|
||||
}
|
||||
|
||||
export const chunterOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient): Promise<void> {
|
||||
},
|
||||
@ -62,5 +76,6 @@ export const chunterOperation: MigrateOperation = {
|
||||
const tx = new TxOperations(client, core.account.System)
|
||||
await createGeneral(tx)
|
||||
await createRandom(tx)
|
||||
await setCreate(tx)
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,8 @@ import type { ViewletDescriptor } from '@anticrm/view'
|
||||
export default mergeIds(chunterId, chunter, {
|
||||
component: {
|
||||
CommentPresenter: '' as AnyComponent,
|
||||
ChannelPresenter: '' as AnyComponent
|
||||
ChannelPresenter: '' as AnyComponent,
|
||||
ThreadView: '' as AnyComponent
|
||||
},
|
||||
string: {
|
||||
ApplicationLabelChunter: '' as IntlString,
|
||||
@ -35,7 +36,9 @@ export default mergeIds(chunterId, chunter, {
|
||||
Comment: '' as IntlString,
|
||||
Message: '' as IntlString,
|
||||
Reference: '' as IntlString,
|
||||
Chat: '' as IntlString
|
||||
Chat: '' as IntlString,
|
||||
CreateBy: '' as IntlString,
|
||||
Create: '' as IntlString
|
||||
},
|
||||
viewlet: {
|
||||
Chat: '' as Ref<ViewletDescriptor>
|
||||
|
@ -20,7 +20,7 @@ import { readable } from 'svelte/store'
|
||||
|
||||
import Root from './components/internal/Root.svelte'
|
||||
|
||||
export type { AnyComponent, AnySvelteComponent, Action, LabelAndProps, TooltipAligment, AnySvelteComponentWithProps } from './types'
|
||||
export type { AnyComponent, AnySvelteComponent, Action, LabelAndProps, TooltipAligment, AnySvelteComponentWithProps, Location } from './types'
|
||||
// export { applicationShortcutKey } from './utils'
|
||||
export { getCurrentLocation, locationToUrl, navigate, location } from './location'
|
||||
|
||||
|
@ -20,6 +20,10 @@
|
||||
"Message": "Message",
|
||||
"Reference": "Reference",
|
||||
"Chat": "Chat",
|
||||
"In": "In"
|
||||
"In": "In",
|
||||
"Replies": "Replies",
|
||||
"LastReply": "Last reply",
|
||||
"RepliesCount": "{replies, plural, =1 {# reply} other {# replies}}",
|
||||
"Thread": "Thread"
|
||||
}
|
||||
}
|
@ -20,6 +20,10 @@
|
||||
"Message": "Сообщение",
|
||||
"Reference": "Ссылка",
|
||||
"Chat": "Чат",
|
||||
"In": "в"
|
||||
"In": "в",
|
||||
"Replies": "Ответы",
|
||||
"LastReply": "Последний ответ",
|
||||
"RepliesCount": "{replies, plural, =1 {# ответ} =2 {# ответа} =3 {# ответа} =4 {# ответа} other {# ответов}}",
|
||||
"Thread": "Обсуждение"
|
||||
}
|
||||
}
|
@ -14,26 +14,43 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import type { Ref, Space } from '@anticrm/core'
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import type { Message } from '@anticrm/chunter'
|
||||
import attachment from '@anticrm/attachment'
|
||||
import contact,{ Employee } from '@anticrm/contact'
|
||||
import { Ref,Space } from '@anticrm/core'
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import chunter from '../plugin'
|
||||
|
||||
import { default as MessageComponent } from './Message.svelte'
|
||||
import MessageComponent from './Message.svelte'
|
||||
|
||||
export let space: Ref<Space> | undefined
|
||||
|
||||
let messages: Message[] | undefined
|
||||
let employees: Map<Ref<Employee>, Employee> = new Map<Ref<Employee>, Employee>()
|
||||
const query = createQuery()
|
||||
const employeeQuery = createQuery()
|
||||
|
||||
employeeQuery.query(contact.class.Employee, { }, (res) => employees = new Map(res.map((r) => { return [r._id, r] })))
|
||||
|
||||
$: updateQuery(space)
|
||||
|
||||
function updateQuery (space: Ref<Space> | undefined) {
|
||||
if (space === undefined) {
|
||||
query.unsubscribe()
|
||||
messages = []
|
||||
return
|
||||
}
|
||||
query.query(chunter.class.Message, {
|
||||
space
|
||||
}, (res) => {
|
||||
messages = res
|
||||
})
|
||||
}
|
||||
|
||||
$: query.query(chunter.class.Message, { space }, result => { messages = result }, { lookup: { _id: { attachments: attachment.class.Attachment } }})
|
||||
</script>
|
||||
|
||||
<div class="flex-col container">
|
||||
{#if messages}
|
||||
{#each messages as message}
|
||||
<MessageComponent {message}/>
|
||||
<MessageComponent {message} {employees} on:openThread />
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -15,56 +15,24 @@
|
||||
|
||||
<script lang="ts">
|
||||
import type { IntlString } from "@anticrm/platform"
|
||||
import { Label } from "@anticrm/ui"
|
||||
|
||||
export let title: IntlString
|
||||
export let line: boolean = false
|
||||
export let params: any = undefined
|
||||
|
||||
</script>
|
||||
|
||||
<div class="flex-center container" class:line={line}>
|
||||
<div class="title">{title}</div>
|
||||
<div class="w-full flex-center whitespace-nowrap mb-4">
|
||||
<Label label={title} {params} />
|
||||
<div class="ml-4" class:line={line} ></div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
.line {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 1.75rem;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
.title {
|
||||
position: relative;
|
||||
padding: .375rem .75rem;
|
||||
font-weight: 600;
|
||||
font-size: .75rem;
|
||||
letter-spacing: .5;
|
||||
text-transform: uppercase;
|
||||
color: var(--theme-content-trans-color);
|
||||
z-index: 1;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 1.25rem;
|
||||
background-color: var(--theme-chat-divider);
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
&.line {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
top: 50%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: var(--theme-chat-divider);
|
||||
}
|
||||
}
|
||||
height: 1px;
|
||||
background-color: var(--theme-chat-divider);
|
||||
}
|
||||
</style>
|
||||
|
@ -14,35 +14,49 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { generateId, Ref, Space } from '@anticrm/core'
|
||||
import chunter from '../plugin'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
|
||||
import Channel from './Channel.svelte'
|
||||
import { AttachmentRefInput } from '@anticrm/attachment-resources'
|
||||
import { Message } from '@anticrm/chunter'
|
||||
import { generateId,getCurrentAccount,Ref,Space, TxFactory } from '@anticrm/core'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { getCurrentLocation,navigate } from '@anticrm/ui'
|
||||
import { createBacklinks } from '../backlinks'
|
||||
import chunter from '../plugin'
|
||||
import Channel from './Channel.svelte'
|
||||
|
||||
export let space: Ref<Space>
|
||||
|
||||
const client = getClient()
|
||||
const _class = chunter.class.Message
|
||||
let _id = generateId()
|
||||
let _id = generateId() as Ref<Message>
|
||||
|
||||
async function onMessage (event: CustomEvent) {
|
||||
const { message, attachments } = event.detail
|
||||
await client.createDoc(_class, space, {
|
||||
const me = getCurrentAccount()._id
|
||||
const txFactory = new TxFactory(me)
|
||||
const tx = txFactory.createTxCreateDoc<Message>(_class, space, {
|
||||
content: message,
|
||||
createOn: 0,
|
||||
createBy: me,
|
||||
attachments
|
||||
}, _id)
|
||||
|
||||
tx.attributes.createOn = tx.modifiedOn
|
||||
await client.tx(tx)
|
||||
|
||||
// Create an backlink to document
|
||||
await createBacklinks(client, space, chunter.class.Channel, _id, message)
|
||||
_id = generateId()
|
||||
}
|
||||
|
||||
function openThread (_id: Ref<Message>) {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[3] = _id
|
||||
navigate(loc)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="msg-board">
|
||||
<Channel {space} />
|
||||
<Channel {space} on:openThread={(e) => { openThread(e.detail) }} />
|
||||
</div>
|
||||
<div class="reference">
|
||||
<AttachmentRefInput {space} {_class} objectId={_id} on:message={onMessage}/>
|
||||
|
@ -1,72 +1,107 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
//
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { Avatar, getClient } from '@anticrm/presentation'
|
||||
import { AttachmentDocList } from '@anticrm/attachment-resources'
|
||||
import type { Message } from '@anticrm/chunter'
|
||||
// import { ActionIcon, IconMoreH } from '@anticrm/ui'
|
||||
// import Emoji from './icons/Emoji.svelte'
|
||||
import contact,{ Employee,EmployeeAccount,formatName } from '@anticrm/contact'
|
||||
import { Account,Ref } from '@anticrm/core'
|
||||
import { getResource } from '@anticrm/platform'
|
||||
import { Avatar,getClient,MessageViewer } from '@anticrm/presentation'
|
||||
import { ActionIcon,IconMoreH,Menu,showPopup } from '@anticrm/ui'
|
||||
import { getActions } from '@anticrm/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import chunter from '../plugin'
|
||||
import { getTime } from '../utils'
|
||||
// import Share from './icons/Share.svelte'
|
||||
// import Bookmark from './icons/Bookmark.svelte'
|
||||
import Bookmark from './icons/Bookmark.svelte'
|
||||
import Emoji from './icons/Emoji.svelte'
|
||||
import Thread from './icons/Thread.svelte'
|
||||
import Reactions from './Reactions.svelte'
|
||||
import Replies from './Replies.svelte'
|
||||
|
||||
import { MessageViewer } from '@anticrm/presentation'
|
||||
import { getTime, getUser } from '../utils'
|
||||
import { formatName } from '@anticrm/contact'
|
||||
import { AttachmentList } from '@anticrm/attachment-resources'
|
||||
import { WithLookup } from '@anticrm/core'
|
||||
import { Attachment } from '@anticrm/attachment'
|
||||
|
||||
export let message: WithLookup<Message>
|
||||
|
||||
let reactions: boolean = false
|
||||
let replies: boolean = false
|
||||
let thread: boolean = false
|
||||
export let message: Message
|
||||
export let employees: Map<Ref<Employee>, Employee>
|
||||
export let thread: boolean = false
|
||||
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: attachments = (message.$lookup?.attachments ?? []) as Attachment[]
|
||||
let reactions: boolean = false
|
||||
|
||||
const showMenu = async (ev: Event): Promise<void> => {
|
||||
const actions = await getActions(client, message, chunter.class.Message)
|
||||
if (actions.length === 0) return
|
||||
showPopup(
|
||||
Menu,
|
||||
{
|
||||
actions: [
|
||||
...actions.map((a) => ({
|
||||
label: a.label,
|
||||
icon: a.icon,
|
||||
action: async () => {
|
||||
const impl = await getResource(a.action)
|
||||
await impl(message)
|
||||
}
|
||||
}))
|
||||
]
|
||||
},
|
||||
ev.target as HTMLElement
|
||||
)
|
||||
}
|
||||
|
||||
async function getEmployee (createdBy: Ref<Account>): Promise<Employee | undefined> {
|
||||
const account = await client.findOne(contact.class.EmployeeAccount, { _id: createdBy as Ref<EmployeeAccount> })
|
||||
if (account === undefined) return
|
||||
return employees.get(account.employee)
|
||||
}
|
||||
|
||||
function openThread () {
|
||||
dispatch('openThread', message._id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<div class="avatar"><Avatar size={'medium'} /></div>
|
||||
<div class="message">
|
||||
<div class="header">
|
||||
{#await getUser(client, message.modifiedBy) then user}
|
||||
{#if user}{formatName(user.name)}{/if}
|
||||
{/await}
|
||||
<span>{getTime(message.modifiedOn)}</span>
|
||||
</div>
|
||||
<div class="text"><MessageViewer message={message.content}/></div>
|
||||
<div class="attachments"><AttachmentList {attachments} /></div>
|
||||
{#if (reactions || replies) && !thread}
|
||||
<div class="footer">
|
||||
<div>{#if reactions}<Reactions/>{/if}</div>
|
||||
<div>{#if replies}<Replies/>{/if}</div>
|
||||
{#await getEmployee(message.createBy) then employee}
|
||||
<div class="avatar"><Avatar size={'medium'} avatar={employee?.avatar} /></div>
|
||||
<div class="message">
|
||||
<div class="header">
|
||||
{#if employee}{formatName(employee.name)}{/if}
|
||||
<span>{getTime(message.createOn)}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<!-- {#if !thread}
|
||||
<div class="buttons">
|
||||
<div class="tool"><ActionIcon icon={IconMoreH} size={'medium'}/></div>
|
||||
<div class="tool"><ActionIcon icon={Bookmark} size={'medium'}/></div>
|
||||
<div class="tool"><ActionIcon icon={Share} size={'medium'}/></div>
|
||||
<div class="tool"><ActionIcon icon={Emoji} size={'medium'}/></div>
|
||||
<div class="text"><MessageViewer message={message.content}/></div>
|
||||
{#if message.attachments}<div class="attachments"><AttachmentDocList value={message} /></div>{/if}
|
||||
{#if (reactions || message.replies)}
|
||||
<div class="footer flex-col">
|
||||
<div>{#if reactions}<Reactions/>{/if}</div>
|
||||
{#if !thread}
|
||||
<div>{#if message.replies}<Replies replies={message.replies} lastReply={message.lastReply} on:click={openThread} />{/if}</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if} -->
|
||||
{/await}
|
||||
<div class="buttons">
|
||||
<div class="tool"><ActionIcon icon={IconMoreH} size={'medium'} action={(e) => { showMenu(e) }}/></div>
|
||||
{#if !thread}
|
||||
<div class="tool"><ActionIcon icon={Thread} size={'medium'} action={openThread}/></div>
|
||||
{/if}
|
||||
<div class="tool"><ActionIcon icon={Bookmark} size={'medium'}/></div>
|
||||
<!-- <div class="tool"><ActionIcon icon={Share} size={'medium'}/></div> -->
|
||||
<div class="tool"><ActionIcon icon={Emoji} size={'medium'}/></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@ -106,36 +141,33 @@
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 2rem;
|
||||
align-items: flex-start;
|
||||
margin-top: .5rem;
|
||||
user-select: none;
|
||||
|
||||
div + div {
|
||||
margin-left: 1rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// .buttons {
|
||||
// position: absolute;
|
||||
// visibility: hidden;
|
||||
// top: -.5rem;
|
||||
// right: -.5rem;
|
||||
// display: flex;
|
||||
// flex-direction: row-reverse;
|
||||
// user-select: none;
|
||||
.buttons {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
top: -.5rem;
|
||||
right: -.5rem;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
user-select: none;
|
||||
|
||||
// .tool + .tool {
|
||||
// margin-right: .5rem;
|
||||
// }
|
||||
// }
|
||||
.tool + .tool {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
// &:hover > .buttons {
|
||||
// visibility: visible;
|
||||
// }
|
||||
&:hover > .buttons {
|
||||
visibility: visible;
|
||||
}
|
||||
&:hover::before {
|
||||
content: '';
|
||||
}
|
||||
|
@ -1,62 +1,80 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
//
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { Avatar } from '@anticrm/presentation'
|
||||
import contact,{ Employee } from '@anticrm/contact'
|
||||
import { Ref, Timestamp } from '@anticrm/core'
|
||||
import { Avatar,createQuery } from '@anticrm/presentation'
|
||||
import { Label, TimeSince } from '@anticrm/ui'
|
||||
import chunter from '../plugin'
|
||||
|
||||
export let replies: string[] = ['Chen', 'Elon', 'Tim', 'Elon', 'Tim', 'Chen']
|
||||
export let replies: Ref<Employee>[] = []
|
||||
export let lastReply: Timestamp = new Date().getTime()
|
||||
$: employees = new Set(replies)
|
||||
|
||||
let shown: number = 4
|
||||
let showReplies: Array<string> = []
|
||||
for (let i = 0; i < shown; i++) {
|
||||
showReplies.push(replies[i])
|
||||
const shown: number = 4
|
||||
let showReplies: Employee[] = []
|
||||
|
||||
const query = createQuery()
|
||||
|
||||
$: updateQuery(employees)
|
||||
|
||||
function updateQuery (employees: Set<Ref<Employee>>) {
|
||||
query.query(contact.class.Employee, {
|
||||
_id: { $in: Array.from(employees) }
|
||||
}, (res) => {
|
||||
showReplies = res
|
||||
}, {
|
||||
limit: shown
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="flex-row-center container">
|
||||
<div class="counter">{replies.length} Replies</div>
|
||||
<div class="flex-row-center container cursor-pointer" on:click>
|
||||
<div class="flex-row-center">
|
||||
{#each showReplies as reply}
|
||||
<div class="reply"><Avatar size={'x-small'} /></div>
|
||||
<div class="reply"><Avatar size={'x-small'} avatar={reply.avatar} /></div>
|
||||
{/each}
|
||||
{#if replies.length > shown}
|
||||
<div class="reply"><span>+{replies.length - shown}</span></div>
|
||||
{#if employees.size > shown}
|
||||
<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>
|
||||
{#if replies.length > 1}
|
||||
<div class="mr-1">
|
||||
<Label label={chunter.string.LastReply} />
|
||||
</div>
|
||||
{/if}
|
||||
<TimeSince value={lastReply} />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
user-select: none;
|
||||
|
||||
.counter {
|
||||
margin-right: .75rem;
|
||||
line-height: 150%;
|
||||
color: var(--theme-content-color);
|
||||
white-space: nowrap;
|
||||
}
|
||||
border: 1px solid transparent;
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.25rem;
|
||||
|
||||
.reply {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
background-color: var(--theme-bg-color);
|
||||
border-radius: 50%;
|
||||
margin-right: -.625rem;
|
||||
|
||||
span {
|
||||
display: flex;
|
||||
@ -71,10 +89,15 @@
|
||||
background-color: var(--theme-bg-selection);
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
.reply + .reply {
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border: 1px solid var(--theme-button-border-hovered);
|
||||
background-color: var(--theme-bg-color);
|
||||
}
|
||||
}
|
||||
</style>
|
174
plugins/chunter-resources/src/components/ThreadComment.svelte
Normal file
174
plugins/chunter-resources/src/components/ThreadComment.svelte
Normal file
@ -0,0 +1,174 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { AttachmentDocList } from '@anticrm/attachment-resources'
|
||||
import type { Comment } from '@anticrm/chunter'
|
||||
import contact,{ Employee,EmployeeAccount,formatName } from '@anticrm/contact'
|
||||
import { Account,Ref } from '@anticrm/core'
|
||||
import { getResource } from '@anticrm/platform'
|
||||
import { Avatar,getClient,MessageViewer } from '@anticrm/presentation'
|
||||
import { ActionIcon,IconMoreH,Menu,showPopup } from '@anticrm/ui'
|
||||
import { getActions } from '@anticrm/view-resources'
|
||||
import chunter from '../plugin'
|
||||
import { getTime } from '../utils'
|
||||
// import Share from './icons/Share.svelte'
|
||||
import Bookmark from './icons/Bookmark.svelte'
|
||||
import Emoji from './icons/Emoji.svelte'
|
||||
import Reactions from './Reactions.svelte'
|
||||
|
||||
export let comment: Comment
|
||||
export let employees: Map<Ref<Employee>, Employee>
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let reactions: boolean = false
|
||||
|
||||
const showMenu = async (ev: Event): Promise<void> => {
|
||||
const actions = await getActions(client, comment, chunter.class.Comment)
|
||||
showPopup(
|
||||
Menu,
|
||||
{
|
||||
actions: [
|
||||
...actions.map((a) => ({
|
||||
label: a.label,
|
||||
icon: a.icon,
|
||||
action: async () => {
|
||||
const impl = await getResource(a.action)
|
||||
await impl(comment)
|
||||
}
|
||||
}))
|
||||
]
|
||||
},
|
||||
ev.target as HTMLElement
|
||||
)
|
||||
}
|
||||
|
||||
async function getEmployee (createdBy: Ref<Account>): Promise<Employee | undefined> {
|
||||
const account = await client.findOne(contact.class.EmployeeAccount, { _id: createdBy as Ref<EmployeeAccount> })
|
||||
if (account === undefined) return
|
||||
return employees.get(account.employee)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
{#await getEmployee(comment.modifiedBy) then employee}
|
||||
<div class="avatar"><Avatar size={'medium'} avatar={employee?.avatar} /></div>
|
||||
<div class="message">
|
||||
<div class="header">
|
||||
{#if employee}{formatName(employee.name)}{/if}
|
||||
<span>{getTime(comment.modifiedOn)}</span>
|
||||
</div>
|
||||
<div class="text"><MessageViewer message={comment.message}/></div>
|
||||
{#if comment.attachments}<div class="attachments"><AttachmentDocList value={comment} /></div>{/if}
|
||||
{#if reactions}
|
||||
<div class="footer">
|
||||
<div><Reactions/></div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/await}
|
||||
<div class="buttons">
|
||||
<div class="tool"><ActionIcon icon={IconMoreH} size={'medium'} action={(e) => { showMenu(e) }}/></div>
|
||||
<div class="tool"><ActionIcon icon={Bookmark} size={'medium'}/></div>
|
||||
<!-- <div class="tool"><ActionIcon icon={Share} size={'medium'}/></div> -->
|
||||
<div class="tool"><ActionIcon icon={Emoji} size={'medium'}/></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
margin-bottom: 2rem;
|
||||
z-index: 1;
|
||||
|
||||
.avatar { min-width: 2.25rem; }
|
||||
|
||||
.message {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
margin-left: 1rem;
|
||||
|
||||
.header {
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
line-height: 150%;
|
||||
color: var(--theme-caption-color);
|
||||
margin-bottom: .25rem;
|
||||
|
||||
span {
|
||||
margin-left: .5rem;
|
||||
font-weight: 400;
|
||||
font-size: .875rem;
|
||||
line-height: 1.125rem;
|
||||
opacity: .4;
|
||||
}
|
||||
}
|
||||
.text {
|
||||
line-height: 150%;
|
||||
}
|
||||
.attachments {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 2rem;
|
||||
margin-top: .5rem;
|
||||
user-select: none;
|
||||
|
||||
div + div {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
top: -.5rem;
|
||||
right: -.5rem;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
user-select: none;
|
||||
|
||||
.tool + .tool {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover > .buttons {
|
||||
visibility: visible;
|
||||
}
|
||||
&:hover::before {
|
||||
content: '';
|
||||
}
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: -1.25rem;
|
||||
left: -1.25rem;
|
||||
width: calc(100% + 2.5rem);
|
||||
height: calc(100% + 2.5rem);
|
||||
background-color: var(--theme-button-bg-enabled);
|
||||
border: 1px solid var(--theme-bg-accent-color);
|
||||
border-radius: .75rem;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
</style>
|
146
plugins/chunter-resources/src/components/ThreadView.svelte
Normal file
146
plugins/chunter-resources/src/components/ThreadView.svelte
Normal file
@ -0,0 +1,146 @@
|
||||
<!--
|
||||
// Copyright © 2021 Anticrm Platform Contributors.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import attachment from '@anticrm/attachment'
|
||||
import { AttachmentRefInput } from '@anticrm/attachment-resources'
|
||||
import type { Comment,Message } from '@anticrm/chunter'
|
||||
import contact,{ Employee, EmployeeAccount } from '@anticrm/contact'
|
||||
import { Class,generateId,getCurrentAccount,Lookup,Ref, Space } from '@anticrm/core'
|
||||
import { createQuery,getClient } from '@anticrm/presentation'
|
||||
import { IconClose,Label } from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { createBacklinks } from '../backlinks'
|
||||
import chunter from '../plugin'
|
||||
import ChannelSeparator from './ChannelSeparator.svelte'
|
||||
import MsgView from './Message.svelte'
|
||||
import ThreadComment from './ThreadComment.svelte'
|
||||
|
||||
const client = getClient()
|
||||
const query = createQuery()
|
||||
const messageQuery = createQuery()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let _id: Ref<Message>
|
||||
export let space: Ref<Space>
|
||||
let message: Message | undefined
|
||||
let commentId = generateId()
|
||||
|
||||
const lookup = {
|
||||
_id: { attachments: attachment.class.Attachment }
|
||||
}
|
||||
|
||||
$: updateQueries(_id)
|
||||
|
||||
function updateQueries (id: Ref<Message>) {
|
||||
messageQuery.query(chunter.class.Message, {
|
||||
_id: id
|
||||
}, (res) => message = res[0], {
|
||||
lookup
|
||||
})
|
||||
|
||||
query.query(chunter.class.Comment, {
|
||||
attachedTo: id
|
||||
}, (res) => comments = res, {
|
||||
lookup
|
||||
})
|
||||
}
|
||||
|
||||
let employees: Map<Ref<Employee>, Employee> = new Map<Ref<Employee>, Employee>()
|
||||
const employeeQuery = createQuery()
|
||||
|
||||
employeeQuery.query(contact.class.Employee, { }, (res) => employees = new Map(res.map((r) => { return [r._id, r] })))
|
||||
|
||||
async function onMessage (event: CustomEvent) {
|
||||
const { message, attachments } = event.detail
|
||||
const employee = (getCurrentAccount() as EmployeeAccount).employee
|
||||
await client.createDoc(chunter.class.Comment, space, {
|
||||
attachedTo: _id,
|
||||
attachedToClass: chunter.class.Message,
|
||||
collection: 'replies',
|
||||
message,
|
||||
attachments
|
||||
}, commentId)
|
||||
|
||||
await client.updateDoc(chunter.class.Message, space, _id, {
|
||||
$push: { replies: employee },
|
||||
lastReply: new Date().getTime()
|
||||
})
|
||||
|
||||
// Create an backlink to document
|
||||
await createBacklinks(client, space, chunter.class.Channel, commentId, message)
|
||||
commentId = generateId()
|
||||
}
|
||||
let comments: Comment[] = []
|
||||
</script>
|
||||
|
||||
<div class="header">
|
||||
<div class="title"><Label label={chunter.string.Thread} /></div>
|
||||
<div class="tool" on:click={() => { dispatch('close') }}><IconClose size='medium' /></div>
|
||||
</div>
|
||||
<div class="h-full flex-col">
|
||||
<div class="content">
|
||||
{#if message}
|
||||
<div class="flex-col">
|
||||
<MsgView {message} {employees} thread />
|
||||
{#if comments.length}
|
||||
<ChannelSeparator title={chunter.string.RepliesCount} line params={{ replies: message.replies?.length }} />
|
||||
{/if}
|
||||
{#each comments as comment}
|
||||
<ThreadComment {comment} {employees} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="ref-input">
|
||||
<AttachmentRefInput {space} _class={chunter.class.Comment} objectId={commentId} on:message={onMessage}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 1.75rem 0 2.5rem;
|
||||
height: 4rem;
|
||||
min-height: 4rem;
|
||||
|
||||
.title {
|
||||
flex-grow: 1;
|
||||
font-weight: 500;
|
||||
font-size: 1.25rem;
|
||||
color: var(--theme-caption-color);
|
||||
user-select: none;
|
||||
}
|
||||
.tool {
|
||||
margin-left: 0.75rem;
|
||||
opacity: 0.4;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
margin: 1rem 1rem 0px;
|
||||
padding: 1.5rem 1.5rem 0px;
|
||||
}
|
||||
.ref-input {
|
||||
margin: 1.25rem 2.5rem;
|
||||
}
|
||||
</style>
|
13
plugins/chunter-resources/src/components/icons/Thread.svelte
Normal file
13
plugins/chunter-resources/src/components/icons/Thread.svelte
Normal file
@ -0,0 +1,13 @@
|
||||
<script lang="ts">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
const fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M13.1,2.9C11.8,1.5,9.9,0.7,8,0.7c-1.9,0-3.8,0.8-5.1,2.1C0.7,5,0.1,8.3,1.4,11c0.1,0.3,0.2,0.5,0.2,0.7 c0,0.2-0.1,0.5-0.2,0.8c-0.2,0.6-0.5,1.4,0.1,1.9c0.6,0.6,1.4,0.3,1.9,0.1c0.3-0.1,0.6-0.2,0.8-0.2c0.2,0,0.4,0.1,0.7,0.2 c1,0.4,2,0.7,3,0.7c1.9,0,3.8-0.7,5.2-2.1h0C16,10.3,16,5.7,13.1,2.9z M12.3,12.3c-1.8,1.8-4.5,2.3-6.9,1.2 c-0.4-0.2-0.8-0.3-1.2-0.3c-0.4,0-0.8,0.1-1.2,0.3c-0.2,0.1-0.6,0.2-0.7,0.2c0-0.1,0.1-0.5,0.2-0.7c0.1-0.4,0.3-0.8,0.3-1.2 c0-0.4-0.2-0.8-0.3-1.2C1.4,8.3,1.9,5.5,3.7,3.7C4.9,2.6,6.4,1.9,8,1.9s3.1,0.6,4.3,1.8C14.7,6.1,14.7,9.9,12.3,12.3L12.3,12.3z"
|
||||
/>
|
||||
<path d="M10.6,7.5L10.6,7.5c-0.4,0-0.8,0.3-0.8,0.8S10.2,9,10.6,9s0.8-0.3,0.8-0.8S11,7.5,10.6,7.5z" />
|
||||
<path d="M8,7.5L8,7.5c-0.4,0-0.8,0.3-0.8,0.8S7.5,9,8,9s0.8-0.3,0.8-0.8S8.4,7.5,8,7.5z" />
|
||||
<path d="M5.3,7.5L5.3,7.5c-0.4,0-0.8,0.3-0.8,0.8S4.9,9,5.3,9S6,8.7,6,8.3S5.7,7.5,5.3,7.5z" />
|
||||
</svg>
|
@ -23,6 +23,7 @@ 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 ThreadView from './components/ThreadView.svelte'
|
||||
|
||||
export { CommentsPresenter }
|
||||
|
||||
@ -33,7 +34,8 @@ export default async (): Promise<Resources> => ({
|
||||
ChannelView,
|
||||
CommentPresenter,
|
||||
CommentsPresenter,
|
||||
ChannelPresenter
|
||||
ChannelPresenter,
|
||||
ThreadView
|
||||
},
|
||||
activity: {
|
||||
TxCommentCreate,
|
||||
|
@ -32,6 +32,10 @@ export default mergeIds(chunterId, chunter, {
|
||||
ChannelDescription: '' as IntlString,
|
||||
MakePrivate: '' as IntlString,
|
||||
MakePrivateDescription: '' as IntlString,
|
||||
In: '' as IntlString
|
||||
In: '' as IntlString,
|
||||
Replies: '' as IntlString,
|
||||
Thread: '' as IntlString,
|
||||
RepliesCount: '' as IntlString,
|
||||
LastReply: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
@ -28,6 +28,7 @@
|
||||
"dependencies": {
|
||||
"@anticrm/platform": "~0.6.5",
|
||||
"@anticrm/ui": "~0.6.0",
|
||||
"@anticrm/contact": "~0.6.5",
|
||||
"@anticrm/core": "~0.6.16"
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,8 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { AttachedDoc, Class, Doc, Ref, Space } from '@anticrm/core'
|
||||
import type { Account, AttachedDoc, Class, Doc, Ref, Space, Timestamp } from '@anticrm/core'
|
||||
import type { Employee } from '@anticrm/contact'
|
||||
import type { Asset, Plugin } from '@anticrm/platform'
|
||||
import { IntlString, plugin } from '@anticrm/platform'
|
||||
import { AnyComponent } from '@anticrm/ui'
|
||||
@ -29,6 +30,10 @@ export interface Channel extends Space {}
|
||||
export interface Message extends Doc {
|
||||
content: string
|
||||
attachments?: number
|
||||
replies?: Ref<Employee>[]
|
||||
lastReply?: Timestamp
|
||||
createBy: Ref<Account>
|
||||
createOn: Timestamp
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -84,7 +84,7 @@
|
||||
</Tooltip>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
<SearchEdit bind:value={search} on:change={() => {
|
||||
dispatch('search', search)
|
||||
}}/>
|
||||
|
@ -25,6 +25,7 @@
|
||||
closeTooltip,
|
||||
Component, getCurrentLocation,
|
||||
location,
|
||||
Location,
|
||||
navigate,
|
||||
PanelInstance,
|
||||
Popup,
|
||||
@ -60,6 +61,8 @@
|
||||
let createItemLabel: IntlString | undefined
|
||||
let navigatorModel: NavigatorModel | undefined
|
||||
|
||||
let asideId: string | undefined
|
||||
|
||||
onDestroy(
|
||||
location.subscribe(async (loc) => {
|
||||
closeTooltip()
|
||||
@ -89,22 +92,14 @@
|
||||
if (spaceId === currentSpace) {
|
||||
// Check if we need update location.
|
||||
const loc = getCurrentLocation()
|
||||
if (loc.path[3] !== spaceSpecial) {
|
||||
if (spaceSpecial !== currentSpecial && spaceSpecial !== asideId) {
|
||||
if (spaceSpecial !== undefined) {
|
||||
loc.path[3] = spaceSpecial
|
||||
loc.path.length = 4
|
||||
} else {
|
||||
loc.path.length = 3
|
||||
}
|
||||
if (spaceSpecial !== undefined) {
|
||||
loc.path[3] = spaceSpecial
|
||||
loc.path.length = 4
|
||||
specialComponent = getSpecialComponent(spaceSpecial)
|
||||
currentSpecial = spaceSpecial
|
||||
setSpaceSpecial(loc, spaceSpecial)
|
||||
} else {
|
||||
loc.path.length = 3
|
||||
spaceSpecial = undefined
|
||||
currentSpecial = undefined
|
||||
asideId = undefined
|
||||
}
|
||||
navigate(loc)
|
||||
}
|
||||
@ -129,19 +124,33 @@
|
||||
loc.path[2] = spaceId
|
||||
loc.path.length = 3
|
||||
if (spaceSpecial !== undefined) {
|
||||
loc.path[3] = spaceSpecial
|
||||
currentSpecial = spaceSpecial
|
||||
loc.path.length = 4
|
||||
specialComponent = getSpecialComponent(spaceSpecial)
|
||||
setSpaceSpecial(loc, spaceSpecial)
|
||||
}
|
||||
navigate(loc)
|
||||
} else {
|
||||
asideId = undefined
|
||||
currentView = undefined
|
||||
createItemDialog = undefined
|
||||
createItemLabel = undefined
|
||||
}
|
||||
}
|
||||
|
||||
function setSpaceSpecial (loc: Location, spaceSpecial: string): void {
|
||||
loc.path[3] = spaceSpecial
|
||||
loc.path.length = 4
|
||||
specialComponent = getSpecialComponent(spaceSpecial)
|
||||
if (specialComponent !== undefined) {
|
||||
currentSpecial = spaceSpecial
|
||||
asideId = undefined
|
||||
} else if (navigatorModel?.aside !== undefined) {
|
||||
asideId = spaceSpecial
|
||||
} else {
|
||||
loc.path.length = 3
|
||||
currentSpecial = undefined
|
||||
asideId = undefined
|
||||
}
|
||||
}
|
||||
|
||||
function selectSpecial (id: string): void {
|
||||
specialComponent = getSpecialComponent(id)
|
||||
if (specialComponent !== undefined) {
|
||||
@ -247,6 +256,13 @@
|
||||
limit: 1
|
||||
}
|
||||
)
|
||||
|
||||
function closeAside () {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path.length = 3
|
||||
navigate(loc)
|
||||
asideId = undefined
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if client}
|
||||
@ -346,7 +362,9 @@
|
||||
<SpaceView {currentSpace} {currentView} {createItemDialog} {createItemLabel} />
|
||||
{/if}
|
||||
</div>
|
||||
<!-- <div class="aside"><Chat thread/></div> -->
|
||||
{#if asideId && navigatorModel?.aside !== undefined}
|
||||
<div class="antiPanel-component indent antiComponent filled"><Component is={navigatorModel.aside} props={{ currentSpace, _id: asideId }} on:close={closeAside} /></div>
|
||||
{/if}
|
||||
</div>
|
||||
<PanelInstance />
|
||||
<Popup />
|
||||
|
@ -54,6 +54,7 @@ export interface SpacesNavModel {
|
||||
export interface NavigatorModel {
|
||||
spaces: SpacesNavModel[]
|
||||
specials?: SpecialNavModel[]
|
||||
aside?: AnyComponent
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user