mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 02:51:54 +03:00
Threads special (#1399)
* Draft Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com> * Threads special Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
parent
a8cc01bd0a
commit
7318f3365a
@ -168,6 +168,15 @@ export function createModel (builder: Builder): void {
|
||||
icon: chunter.icon.Chunter,
|
||||
hidden: false,
|
||||
navigatorModel: {
|
||||
specials: [
|
||||
{
|
||||
id: 'threads',
|
||||
label: chunter.string.Threads,
|
||||
icon: chunter.icon.Thread,
|
||||
component: chunter.component.Threads,
|
||||
position: 'top'
|
||||
}
|
||||
],
|
||||
spaces: [
|
||||
{
|
||||
label: chunter.string.Channels,
|
||||
|
@ -26,6 +26,7 @@ export default mergeIds(chunterId, chunter, {
|
||||
component: {
|
||||
CommentPresenter: '' as AnyComponent,
|
||||
ChannelPresenter: '' as AnyComponent,
|
||||
Threads: '' as AnyComponent,
|
||||
ThreadView: '' as AnyComponent
|
||||
},
|
||||
action: {
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
import core, {
|
||||
AnyAttribute, ArrOf, AttachedDoc, Class, Client, Collection, Doc, DocumentQuery,
|
||||
FindOptions, getCurrentAccount, Ref, RefTo, Tx, TxOperations, TxResult
|
||||
FindOptions, FindResult, getCurrentAccount, Ref, RefTo, Tx, TxOperations, TxResult
|
||||
} from '@anticrm/core'
|
||||
import login from '@anticrm/login'
|
||||
import { getMetadata } from '@anticrm/platform'
|
||||
@ -62,7 +62,7 @@ export class LiveQuery {
|
||||
query<T extends Doc>(
|
||||
_class: Ref<Class<T>>,
|
||||
query: DocumentQuery<T>,
|
||||
callback: (result: T[]) => void,
|
||||
callback: (result: FindResult<T>) => void,
|
||||
options?: FindOptions<T>
|
||||
): void {
|
||||
this.unsubscribe()
|
||||
|
@ -386,10 +386,10 @@ describe('query', () => {
|
||||
const comment = result[0]
|
||||
if (comment !== undefined) {
|
||||
if (attempt > 0) {
|
||||
expect((comment as WithLookup<AttachedComment>).$lookup?.space?._id).toEqual(futureSpace._id)
|
||||
expect(comment.$lookup?.space?._id).toEqual(futureSpace._id)
|
||||
resolve(null)
|
||||
} else {
|
||||
expect((comment as WithLookup<AttachedComment>).$lookup?.space).toBeUndefined()
|
||||
expect(comment.$lookup?.space).toBeUndefined()
|
||||
attempt++
|
||||
}
|
||||
}
|
||||
@ -433,10 +433,10 @@ describe('query', () => {
|
||||
const comment = result[0]
|
||||
if (comment !== undefined) {
|
||||
if (attempt > 0) {
|
||||
expect(((comment as WithLookup<AttachedComment>).$lookup?.attachedTo as WithLookup<AttachedComment>)?.$lookup?.space?._id).toEqual(futureSpace._id)
|
||||
expect((comment.$lookup?.attachedTo as WithLookup<AttachedComment>)?.$lookup?.space?._id).toEqual(futureSpace._id)
|
||||
resolve(null)
|
||||
} else {
|
||||
expect(((comment as WithLookup<AttachedComment>).$lookup?.attachedTo as WithLookup<AttachedComment>)?.$lookup?.space).toBeUndefined()
|
||||
expect((comment.$lookup?.attachedTo as WithLookup<AttachedComment>)?.$lookup?.space).toBeUndefined()
|
||||
attempt++
|
||||
}
|
||||
}
|
||||
@ -466,7 +466,7 @@ describe('query', () => {
|
||||
(result) => {
|
||||
const comment = result[0]
|
||||
if (comment !== undefined) {
|
||||
expect(((comment as WithLookup<AttachedComment>).$lookup as any)?.comments).toHaveLength(attempt++)
|
||||
expect((comment.$lookup as any)?.comments).toHaveLength(attempt++)
|
||||
}
|
||||
if (attempt === childLength) {
|
||||
resolve(null)
|
||||
@ -505,10 +505,10 @@ describe('query', () => {
|
||||
const comment = result[0]
|
||||
if (comment !== undefined) {
|
||||
if (attempt > 0) {
|
||||
expect((comment as WithLookup<AttachedComment>).$lookup?.space).toBeUndefined()
|
||||
expect(comment.$lookup?.space).toBeUndefined()
|
||||
resolve(null)
|
||||
} else {
|
||||
expect(((comment as WithLookup<AttachedComment>).$lookup?.space as Doc)?._id).toEqual(futureSpace)
|
||||
expect((comment.$lookup?.space as Doc)?._id).toEqual(futureSpace)
|
||||
attempt++
|
||||
}
|
||||
}
|
||||
@ -546,10 +546,10 @@ describe('query', () => {
|
||||
const comment = result[0]
|
||||
if (comment !== undefined) {
|
||||
if (attempt > 0) {
|
||||
expect(((comment as WithLookup<AttachedComment>).$lookup?.attachedTo as WithLookup<AttachedComment>)?.$lookup?.space).toBeUndefined()
|
||||
expect((comment.$lookup?.attachedTo as WithLookup<AttachedComment>)?.$lookup?.space).toBeUndefined()
|
||||
resolve(null)
|
||||
} else {
|
||||
expect((((comment as WithLookup<AttachedComment>).$lookup?.attachedTo as WithLookup<AttachedComment>)?.$lookup?.space as Doc)?._id).toEqual(futureSpace)
|
||||
expect(((comment.$lookup?.attachedTo as WithLookup<AttachedComment>)?.$lookup?.space as Doc)?._id).toEqual(futureSpace)
|
||||
attempt++
|
||||
}
|
||||
}
|
||||
@ -586,7 +586,7 @@ describe('query', () => {
|
||||
(result) => {
|
||||
const comment = result[0]
|
||||
if (comment !== undefined) {
|
||||
expect(((comment as WithLookup<AttachedComment>).$lookup as any)?.comments).toHaveLength(childLength - attempt)
|
||||
expect((comment.$lookup as any)?.comments).toHaveLength(childLength - attempt)
|
||||
attempt++
|
||||
}
|
||||
if (attempt === childLength) {
|
||||
@ -624,7 +624,7 @@ describe('query', () => {
|
||||
(result) => {
|
||||
const comment = result[0]
|
||||
if (comment !== undefined) {
|
||||
expect(((comment as WithLookup<AttachedComment>).$lookup?.space as Space).name).toEqual(attempt.toString())
|
||||
expect((comment.$lookup?.space as Space).name).toEqual(attempt.toString())
|
||||
}
|
||||
if (attempt > 0) {
|
||||
resolve(null)
|
||||
@ -665,7 +665,7 @@ describe('query', () => {
|
||||
(result) => {
|
||||
const comment = result[0]
|
||||
if (comment !== undefined) {
|
||||
expect((((comment as WithLookup<AttachedComment>).$lookup?.attachedTo as WithLookup<AttachedComment>)?.$lookup?.space as Space).name).toEqual(attempt.toString())
|
||||
expect(((comment.$lookup?.attachedTo as WithLookup<AttachedComment>)?.$lookup?.space as Space).name).toEqual(attempt.toString())
|
||||
}
|
||||
if (attempt > 0) {
|
||||
resolve(null)
|
||||
@ -700,7 +700,7 @@ describe('query', () => {
|
||||
(result) => {
|
||||
const comment = result[0]
|
||||
if (comment !== undefined) {
|
||||
expect((((comment as WithLookup<AttachedComment>).$lookup as any)?.comments[0] as AttachedComment).message).toEqual(attempt.toString())
|
||||
expect(((comment.$lookup as any)?.comments[0] as AttachedComment).message).toEqual(attempt.toString())
|
||||
}
|
||||
if (attempt > 0) {
|
||||
resolve(null)
|
||||
|
@ -118,7 +118,7 @@ export class LiveQuery extends TxProcessor implements Client {
|
||||
query<T extends Doc>(
|
||||
_class: Ref<Class<T>>,
|
||||
query: DocumentQuery<T>,
|
||||
callback: (result: T[]) => void,
|
||||
callback: (result: FindResult<T>) => void,
|
||||
options?: FindOptions<T>
|
||||
): () => void {
|
||||
const result = this.client.findAll(_class, query, options)
|
||||
|
@ -324,6 +324,7 @@ p:last-child { margin-block-end: 0; }
|
||||
|
||||
.pl-2 { padding-left: .5rem; }
|
||||
.pl-4 { padding-left: 1rem; }
|
||||
.pl-8 { padding-left: 2rem; }
|
||||
.pr-1 { padding-right: .25rem; }
|
||||
.pr-2 { padding-right: .5rem; }
|
||||
.pr-4 { padding-right: 1rem; }
|
||||
|
@ -8,4 +8,7 @@
|
||||
<symbol id="lock" viewBox="0 0 16 16">
|
||||
<path d="M12,7.1h-0.7V5.4c0-1.8-1.5-3.3-3.3-3.3c-1.8,0-3.3,1.5-3.3,3.3v1.7H4c-0.8,0-1.5,0.7-1.5,1.5v4.1c0,0.8,0.7,1.5,1.5,1.5h8 c0.8,0,1.5-0.7,1.5-1.5V8.6C13.5,7.8,12.8,7.1,12,7.1z M5.7,5.4c0-1.2,1-2.3,2.3-2.3s2.3,1,2.3,2.3v1.7H5.7V5.4z M12.5,12.7 c0,0.3-0.2,0.5-0.5,0.5H4c-0.3,0-0.5-0.2-0.5-0.5V8.6c0-0.3,0.2-0.5,0.5-0.5h8c0.3,0,0.5,0.2,0.5,0.5V12.7z"/>
|
||||
</symbol>
|
||||
<symbol id="thread" viewBox="0 0 16 16">
|
||||
<path d="M8,14.5c-0.9,0-1.9-0.2-2.7-0.6c-0.2-0.1-0.5-0.2-0.6-0.2c-0.2,0-0.4,0.1-0.7,0.2c-0.5,0.2-1.2,0.4-1.7-0.1 c-0.5-0.5-0.3-1.2-0.1-1.7c0.1-0.3,0.2-0.5,0.2-0.7c0-0.2-0.1-0.4-0.2-0.7C1,8.3,1.5,5.3,3.4,3.4C4.6,2.2,6.3,1.5,8,1.5 s3.4,0.7,4.6,1.9c2.5,2.5,2.5,6.7,0,9.2l0,0C11.4,13.8,9.7,14.5,8,14.5z M4.6,12.7c0.4,0,0.7,0.1,1,0.3c2.1,1,4.6,0.5,6.2-1.1 c2.1-2.1,2.1-5.6,0-7.8c-1-1-2.4-1.6-3.9-1.6c-1.5,0-2.9,0.6-3.9,1.6C2.5,5.7,2,8.2,3,10.3c0.1,0.4,0.3,0.7,0.3,1.1 c0,0.4-0.1,0.7-0.2,1C3,12.6,2.9,13,2.9,13.1C3,13.2,3.4,13,3.6,12.9C3.9,12.8,4.3,12.7,4.6,12.7z M12.2,12.2L12.2,12.2L12.2,12.2z"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.4 KiB |
@ -27,6 +27,7 @@
|
||||
"RepliesCount": "{replies, plural, =1 {# reply} other {# replies}}",
|
||||
"Topic": "Topic",
|
||||
"Thread": "Thread",
|
||||
"Threads": "Threads",
|
||||
"New": "New",
|
||||
"MarkUnread": "Mark unread",
|
||||
"GetNewReplies": "Get notified about new replies",
|
||||
@ -35,6 +36,8 @@
|
||||
"UnpinMessage": "Unpin message",
|
||||
"Pinned": "Pinned:",
|
||||
"EditMessage": "Edit message",
|
||||
"DeleteMessage": "Delete message"
|
||||
"DeleteMessage": "Delete message",
|
||||
"AndYou": "{participants, plural, =0 {Just you} other {and you}}",
|
||||
"ShowMoreReplies": "Show {count} more replies"
|
||||
}
|
||||
}
|
@ -26,6 +26,7 @@
|
||||
"LastReply": "Последний ответ",
|
||||
"RepliesCount": "{replies, plural, =1 {# ответ} =2 {# ответа} =3 {# ответа} =4 {# ответа} other {# ответов}}",
|
||||
"Thread": "Обсуждение",
|
||||
"Threads": "Обсуждения",
|
||||
"New": "Новое",
|
||||
"MarkUnread": "Отметить как непрочитанное",
|
||||
"GetNewReplies": "Получать уведомления о новых ответах",
|
||||
@ -34,6 +35,8 @@
|
||||
"UnpinMessage": "Открепить сообщение",
|
||||
"Pinned": "Закреплено:",
|
||||
"EditMessage": "Редактировать сообщение",
|
||||
"DeleteMessage": "Удалить сообщение"
|
||||
"DeleteMessage": "Удалить сообщение",
|
||||
"AndYou": "{participants, plural, =0 {Только вы} other {и вы}}",
|
||||
"ShowMoreReplies": "{count, plural, =3 {Показать еще # ответа} =4 {Показать еще # ответа} other {Показать еще # ответов}}"
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ const icons = require('../assets/icons.svg') as string // eslint-disable-line
|
||||
loadMetadata(chunter.icon, {
|
||||
Chunter: `${icons}#chunter`,
|
||||
Hashtag: `${icons}#hashtag`,
|
||||
Thread: `${icons}#thread`,
|
||||
Lock: `${icons}#lock`
|
||||
})
|
||||
|
||||
|
@ -107,7 +107,7 @@
|
||||
let newMessagesPos: number = -1
|
||||
</script>
|
||||
|
||||
<div class="flex-col vScroll container" bind:this={div}>
|
||||
<div class="flex-col vScroll" bind:this={div}>
|
||||
{#if messages}
|
||||
{#each messages as message, i (message._id)}
|
||||
{#if newMessagesPos === i}
|
||||
@ -117,10 +117,3 @@
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
margin: 1rem 1rem 0;
|
||||
padding: 1.5rem 1.5rem 0px;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,15 +1,14 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 Hardcore Engineering Inc.
|
||||
//
|
||||
// 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.
|
||||
-->
|
||||
@ -17,7 +16,7 @@
|
||||
import type { Channel } from '@anticrm/chunter'
|
||||
import { Ref, Space } from '@anticrm/core'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { getCurrentLocation, Icon, navigate } from '@anticrm/ui'
|
||||
import { getCurrentLocation, Icon, locationToUrl } from '@anticrm/ui'
|
||||
import chunter from '../plugin'
|
||||
|
||||
export let value: Channel
|
||||
@ -25,24 +24,28 @@
|
||||
|
||||
$: icon = client.getHierarchy().getClass(value._class).icon
|
||||
|
||||
function selectSpace (id: Ref<Space>) {
|
||||
function getLink (id: Ref<Space>): string {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[1] = chunter.app.Chunter
|
||||
loc.path[2] = id
|
||||
loc.path.length = 3
|
||||
loc.fragment = undefined
|
||||
navigate(loc)
|
||||
return locationToUrl(loc)
|
||||
}
|
||||
|
||||
$: link = getLink(value._id)
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="flex-row-center hover-trans"
|
||||
on:click={() => {
|
||||
selectSpace(value._id)
|
||||
}}
|
||||
>
|
||||
{#if icon}
|
||||
<Icon {icon} size={'small'} />
|
||||
{/if}
|
||||
{value.name}
|
||||
</div>
|
||||
{#if value}
|
||||
<a
|
||||
class="flex-presenter"
|
||||
href="{link}"
|
||||
>
|
||||
<div class="icon">
|
||||
{#if icon}
|
||||
<Icon {icon} size={'small'} />
|
||||
{/if}
|
||||
</div>
|
||||
<span class="label">{value.name}</span>
|
||||
</a>
|
||||
{/if}
|
||||
|
@ -23,8 +23,8 @@
|
||||
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}>
|
||||
<Label label={title} {params} />
|
||||
<div class="w-full text-sm flex-center whitespace-nowrap" class:flex-reverse={reverse} class:new={isNew}>
|
||||
<div class:ml-8={!reverse} class:mr-4={reverse}><Label label={title} {params} /></div>
|
||||
<div class:ml-4={!reverse} class:mr-4={reverse} class:line />
|
||||
</div>
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: var(--theme-chat-divider);
|
||||
background-color: var(--theme-dialog-divider);
|
||||
}
|
||||
.new {
|
||||
.line {
|
||||
|
@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import { Attachment } from '@anticrm/attachment'
|
||||
import { AttachmentList, AttachmentRefInput } from '@anticrm/attachment-resources'
|
||||
import type { Message } from '@anticrm/chunter'
|
||||
import type { ChunterMessage, Message } from '@anticrm/chunter'
|
||||
import { Employee, EmployeeAccount, formatName } from '@anticrm/contact'
|
||||
import { Ref, WithLookup, getCurrentAccount } from '@anticrm/core'
|
||||
import { NotificationClientImpl } from '@anticrm/notification-resources'
|
||||
@ -35,7 +35,7 @@
|
||||
import Reactions from './Reactions.svelte'
|
||||
import Replies from './Replies.svelte'
|
||||
|
||||
export let message: WithLookup<Message>
|
||||
export let message: WithLookup<ChunterMessage>
|
||||
export let employees: Map<Ref<Employee>, Employee>
|
||||
export let thread: boolean = false
|
||||
export let isPinned: boolean = false
|
||||
@ -81,19 +81,22 @@
|
||||
const deleteAction = {
|
||||
label: chunter.string.DeleteMessage,
|
||||
action: async () => {
|
||||
(await client.findAll(chunter.class.ThreadMessage, {attachedTo: message._id})).forEach(c => {
|
||||
(await client.findAll(chunter.class.ThreadMessage, { attachedTo: message._id as Ref<Message> })).forEach(c => {
|
||||
UnpinMessage(c)
|
||||
})
|
||||
UnpinMessage(message)
|
||||
await client.remove(message)
|
||||
await client.removeDoc(message._class, message.space, message._id)
|
||||
}
|
||||
}
|
||||
|
||||
let menuShowed = false
|
||||
|
||||
const showMenu = async (ev: Event): Promise<void> => {
|
||||
const actions = await getActions(client, message, chunter.class.Message)
|
||||
const actions = await getActions(client, message, message._class)
|
||||
actions.push(subscribeAction)
|
||||
actions.push(pinActions)
|
||||
|
||||
menuShowed = true
|
||||
showPopup(
|
||||
Menu,
|
||||
{
|
||||
@ -109,7 +112,10 @@
|
||||
...(getCurrentAccount()._id === message.createBy ? [editAction, deleteAction] : [])
|
||||
]
|
||||
},
|
||||
ev.target as HTMLElement
|
||||
ev.target as HTMLElement,
|
||||
() => {
|
||||
menuShowed = false
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ -128,7 +134,7 @@
|
||||
isEditing = false
|
||||
}
|
||||
|
||||
function getEmployee (message: WithLookup<Message>): Employee | undefined {
|
||||
function getEmployee (message: WithLookup<ChunterMessage>): Employee | undefined {
|
||||
const employee = (message.$lookup?.createBy as EmployeeAccount).employee
|
||||
if (employee !== undefined) {
|
||||
return employees.get(employee)
|
||||
@ -138,6 +144,9 @@
|
||||
function openThread () {
|
||||
dispatch('openThread', message._id)
|
||||
}
|
||||
|
||||
$: parentMessage = message as Message
|
||||
$: hasReplies = (parentMessage?.replies?.length ?? 0) > 0
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
@ -159,24 +168,16 @@
|
||||
<div class="text"><MessageViewer message={message.content} /></div>
|
||||
{#if message.attachments}<div class="attachments"><AttachmentList {attachments} /></div>{/if}
|
||||
{/if}
|
||||
{#if reactions || message.replies}
|
||||
{#if reactions || (!thread && hasReplies)}
|
||||
<div class="footer flex-col">
|
||||
<div>
|
||||
{#if reactions}<Reactions />{/if}
|
||||
</div>
|
||||
{#if !thread}
|
||||
<div>
|
||||
{#if message.replies?.length}<Replies
|
||||
replies={message.replies}
|
||||
lastReply={message.lastReply}
|
||||
on:click={openThread}
|
||||
/>{/if}
|
||||
</div>
|
||||
{#if reactions}<Reactions />{/if}
|
||||
{#if !thread && hasReplies}
|
||||
<Replies message={parentMessage} on:click={openThread} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<div class="buttons" class:menuShowed>
|
||||
<div class="tool">
|
||||
<ActionIcon
|
||||
icon={IconMoreH}
|
||||
@ -199,8 +200,7 @@
|
||||
.container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
margin-bottom: 2rem;
|
||||
z-index: 1;
|
||||
padding: 2rem;
|
||||
|
||||
.avatar {
|
||||
min-width: 2.25rem;
|
||||
@ -229,6 +229,7 @@
|
||||
}
|
||||
.text {
|
||||
line-height: 150%;
|
||||
user-select: contain;
|
||||
}
|
||||
.attachments {
|
||||
margin-top: 1rem;
|
||||
@ -247,8 +248,8 @@
|
||||
.buttons {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
top: -0.5rem;
|
||||
right: -0.5rem;
|
||||
top: 0.5rem;
|
||||
right: 1rem;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
user-select: none;
|
||||
@ -256,25 +257,18 @@
|
||||
.tool + .tool {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
&.menuShowed {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
&: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: 0.75rem;
|
||||
z-index: -1;
|
||||
&:hover {
|
||||
background-color: var(--board-card-bg-hover);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
// Copyright © 2020 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
|
||||
@ -13,15 +13,16 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Message } from '@anticrm/chunter'
|
||||
import contact, { Employee } from '@anticrm/contact'
|
||||
import { Ref, Timestamp } from '@anticrm/core'
|
||||
import { Ref } from '@anticrm/core'
|
||||
import { Avatar, createQuery } from '@anticrm/presentation'
|
||||
import { Label, TimeSince } from '@anticrm/ui'
|
||||
import chunter from '../plugin'
|
||||
|
||||
export let replies: Ref<Employee>[] = []
|
||||
export let lastReply: Timestamp = new Date().getTime()
|
||||
$: employees = new Set(replies)
|
||||
export let message: Message
|
||||
$: lastReply = message.lastReply ?? new Date().getTime()
|
||||
$: employees = new Set(message.replies)
|
||||
|
||||
const shown: number = 4
|
||||
let showReplies: Employee[] = []
|
||||
@ -56,9 +57,9 @@
|
||||
{/if}
|
||||
</div>
|
||||
<div class="whitespace-nowrap ml-2 mr-2 over-underline">
|
||||
<Label label={chunter.string.RepliesCount} params={{ replies: replies.length }} />
|
||||
<Label label={chunter.string.RepliesCount} params={{ replies: message.replies?.length ?? 0 }} />
|
||||
</div>
|
||||
{#if replies.length > 1}
|
||||
{#if (message.replies?.length ?? 0) > 1}
|
||||
<div class="mr-1">
|
||||
<Label label={chunter.string.LastReply} />
|
||||
</div>
|
||||
|
199
plugins/chunter-resources/src/components/Thread.svelte
Normal file
199
plugins/chunter-resources/src/components/Thread.svelte
Normal file
@ -0,0 +1,199 @@
|
||||
<!--
|
||||
// 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 attachment from '@anticrm/attachment'
|
||||
import { AttachmentRefInput } from '@anticrm/attachment-resources'
|
||||
import type { Channel, Message, ThreadMessage } from '@anticrm/chunter'
|
||||
import contact, { Employee, EmployeeAccount, formatName } from '@anticrm/contact'
|
||||
import core, { FindOptions, generateId, getCurrentAccount, Ref, SortingOrder, TxFactory } from '@anticrm/core'
|
||||
import { NotificationClientImpl } from '@anticrm/notification-resources'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import { Label } from '@anticrm/ui'
|
||||
import { createBacklinks } from '../backlinks'
|
||||
import chunter from '../plugin'
|
||||
import ChannelPresenter from './ChannelPresenter.svelte'
|
||||
import MsgView from './Message.svelte'
|
||||
|
||||
const client = getClient()
|
||||
const query = createQuery()
|
||||
const messageQuery = createQuery()
|
||||
|
||||
export let _id: Ref<Message>
|
||||
let parent: Message | undefined
|
||||
let commentId = generateId() as Ref<ThreadMessage>
|
||||
|
||||
const notificationClient = NotificationClientImpl.getClient()
|
||||
|
||||
const lookup = {
|
||||
_id: { attachments: attachment.class.Attachment },
|
||||
createBy: core.class.Account
|
||||
}
|
||||
|
||||
let showAll = false
|
||||
let total = 0
|
||||
|
||||
$: updateQuery(_id)
|
||||
$: updateThreadQuery(_id, showAll)
|
||||
|
||||
function updateQuery (id: Ref<Message>) {
|
||||
messageQuery.query(
|
||||
chunter.class.Message,
|
||||
{
|
||||
_id: id
|
||||
},
|
||||
(res) => (parent = res[0]),
|
||||
{
|
||||
lookup: {
|
||||
_id: { attachments: attachment.class.Attachment },
|
||||
createBy: core.class.Account
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function updateThreadQuery (id: Ref<Message>, showAll: boolean) {
|
||||
const options: FindOptions<ThreadMessage> = {
|
||||
lookup,
|
||||
sort: {
|
||||
createOn: SortingOrder.Descending
|
||||
}
|
||||
}
|
||||
if (!showAll) {
|
||||
options.limit = 4
|
||||
}
|
||||
query.query(
|
||||
chunter.class.ThreadMessage,
|
||||
{
|
||||
attachedTo: id
|
||||
},
|
||||
(res) => {
|
||||
total = res.total
|
||||
if (!showAll && res.total > 4) {
|
||||
comments = res.splice(0, 2).reverse()
|
||||
} else {
|
||||
comments = res.reverse()
|
||||
}
|
||||
notificationClient.updateLastView(id, chunter.class.Message)
|
||||
},
|
||||
options
|
||||
)
|
||||
}
|
||||
|
||||
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 getParticipants (comments: ThreadMessage[], parent: Message | undefined, employees: Map<Ref<Employee>, Employee>): Promise<string[]> {
|
||||
const refs = new Set(comments.map((p) => p.createBy))
|
||||
if (parent !== undefined) {
|
||||
refs.add(parent.createBy)
|
||||
}
|
||||
refs.delete(getCurrentAccount()._id)
|
||||
const accounts = await client.findAll(contact.class.EmployeeAccount, { _id: { $in: Array.from(refs) as Ref<EmployeeAccount>[] } })
|
||||
const res: string[] = []
|
||||
for (const account of accounts) {
|
||||
const employee = employees.get(account.employee)
|
||||
if (employee !== undefined) {
|
||||
res.push(formatName(employee.name))
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
async function onMessage (event: CustomEvent) {
|
||||
if (parent === undefined) return
|
||||
const { message, attachments } = event.detail
|
||||
const me = getCurrentAccount()._id
|
||||
const txFactory = new TxFactory(me)
|
||||
const tx = txFactory.createTxCreateDoc<ThreadMessage>(
|
||||
chunter.class.ThreadMessage,
|
||||
parent.space,
|
||||
{
|
||||
attachedTo: _id,
|
||||
attachedToClass: chunter.class.Message,
|
||||
collection: 'replies',
|
||||
content: message,
|
||||
createBy: me,
|
||||
createOn: 0,
|
||||
attachments
|
||||
},
|
||||
commentId
|
||||
)
|
||||
tx.attributes.createOn = tx.modifiedOn
|
||||
await notificationClient.updateLastView(_id, chunter.class.Message, tx.modifiedOn, true)
|
||||
await client.tx(tx)
|
||||
|
||||
// Create an backlink to document
|
||||
await createBacklinks(client, parent.space, chunter.class.Channel, commentId, message)
|
||||
|
||||
commentId = generateId()
|
||||
}
|
||||
let comments: ThreadMessage[] = []
|
||||
|
||||
async function getChannel (_id: Ref<Channel>): Promise<Channel | undefined> {
|
||||
return await client.findOne(chunter.class.Channel, { _id })
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="ml-8 mt-4">
|
||||
{#if parent}
|
||||
{#await getChannel(parent.space) then channel}
|
||||
{#if channel}
|
||||
<ChannelPresenter value={channel} />
|
||||
{/if}
|
||||
{/await}
|
||||
{#await getParticipants(comments, parent, employees) then participants}
|
||||
{participants.join(', ')}
|
||||
<Label label={chunter.string.AndYou} params={{ participants: participants.length }} />
|
||||
{/await}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex-col content">
|
||||
{#if parent}
|
||||
<MsgView message={parent} {employees} thread />
|
||||
{#if total > comments.length}
|
||||
<div class="label pb-2 pt-2 pl-8 over-underline" on:click={() => { showAll = true }}><Label label={chunter.string.ShowMoreReplies} params={{ count: total - comments.length }} /></div>
|
||||
{/if}
|
||||
{#each comments as comment (comment._id)}
|
||||
<MsgView message={comment} {employees} thread />
|
||||
{/each}
|
||||
<div class="mr-4 ml-4 mb-4 mt-2">
|
||||
<AttachmentRefInput space={parent.space} _class={chunter.class.Comment} objectId={commentId} on:message={onMessage} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.content {
|
||||
margin: 1rem 1rem 0px;
|
||||
background-color: var(--theme-border-modal);
|
||||
border-radius: 0.75rem;
|
||||
border: 1px solid var(--theme-zone-border);
|
||||
}
|
||||
|
||||
.label:hover {
|
||||
background-color: var(--board-card-bg-hover);
|
||||
}
|
||||
</style>
|
@ -1,257 +0,0 @@
|
||||
<!--
|
||||
// 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 { Attachment } from '@anticrm/attachment'
|
||||
import { AttachmentList, AttachmentRefInput } from '@anticrm/attachment-resources'
|
||||
import type { ThreadMessage } from '@anticrm/chunter'
|
||||
import { Employee, EmployeeAccount, formatName } from '@anticrm/contact'
|
||||
import { Ref, WithLookup, getCurrentAccount } from '@anticrm/core'
|
||||
import { NotificationClientImpl } from '@anticrm/notification-resources'
|
||||
import { getResource } from '@anticrm/platform'
|
||||
import { Avatar, getClient, MessageViewer } from '@anticrm/presentation'
|
||||
import { ActionIcon, IconMoreH, Menu, showPopup } from '@anticrm/ui'
|
||||
import { Action } from '@anticrm/view'
|
||||
import { getActions } from '@anticrm/view-resources'
|
||||
import { UnpinMessage } from '../index';
|
||||
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 message: WithLookup<ThreadMessage>
|
||||
export let employees: Map<Ref<Employee>, Employee>
|
||||
export let isPinned: boolean = false
|
||||
|
||||
$: attachments = (message.$lookup?.attachments ?? []) as Attachment[]
|
||||
|
||||
const client = getClient()
|
||||
|
||||
const reactions: boolean = false
|
||||
|
||||
const notificationClient = NotificationClientImpl.getClient()
|
||||
const lastViews = notificationClient.getLastViews()
|
||||
$: subscribed = ($lastViews.get(message.attachedTo) ?? -1) > -1
|
||||
$: subscribeAction = subscribed
|
||||
? ({
|
||||
label: chunter.string.TurnOffReplies,
|
||||
action: chunter.actionImpl.UnsubscribeComment
|
||||
} as Action)
|
||||
: ({
|
||||
label: chunter.string.GetNewReplies,
|
||||
action: chunter.actionImpl.SubscribeComment
|
||||
} as Action)
|
||||
|
||||
$: pinActions = isPinned
|
||||
? ({
|
||||
label: chunter.string.UnpinMessage,
|
||||
action: chunter.actionImpl.UnpinMessage
|
||||
} as Action)
|
||||
: ({
|
||||
label: chunter.string.PinMessage,
|
||||
action: chunter.actionImpl.PinMessage
|
||||
} as Action)
|
||||
|
||||
$: isEditing = false;
|
||||
|
||||
const editAction = {
|
||||
label: chunter.string.EditMessage,
|
||||
action: () => isEditing = true
|
||||
}
|
||||
|
||||
const deleteAction = {
|
||||
label: chunter.string.DeleteMessage,
|
||||
action: async () => {
|
||||
await client.removeDoc(message._class, message.space, message._id)
|
||||
UnpinMessage(message)
|
||||
}
|
||||
}
|
||||
|
||||
const showMenu = async (ev: Event): Promise<void> => {
|
||||
const actions = await getActions(client, message, chunter.class.ThreadMessage)
|
||||
actions.push(subscribeAction)
|
||||
actions.push(pinActions)
|
||||
showPopup(
|
||||
Menu,
|
||||
{
|
||||
actions: [
|
||||
...actions.map((a) => ({
|
||||
label: a.label,
|
||||
icon: a.icon,
|
||||
action: async () => {
|
||||
const impl = await getResource(a.action)
|
||||
await impl(message)
|
||||
}
|
||||
})),
|
||||
...(getCurrentAccount()._id === message.createBy ? [editAction, deleteAction] : [])
|
||||
]
|
||||
},
|
||||
ev.target as HTMLElement
|
||||
)
|
||||
}
|
||||
|
||||
async function onMessageEdit (event: CustomEvent) {
|
||||
const { message: newContent, attachments: newAttachments } = event.detail
|
||||
|
||||
if (newContent !== message.content || newAttachments !== attachments) {
|
||||
await client.update(
|
||||
message,
|
||||
{
|
||||
content: newContent,
|
||||
attachments: newAttachments
|
||||
}
|
||||
)
|
||||
}
|
||||
isEditing = false
|
||||
}
|
||||
|
||||
$: employee = getEmployee(message)
|
||||
|
||||
function getEmployee (comment: WithLookup<ThreadMessage>): Employee | undefined {
|
||||
const employee = (comment.$lookup?.createBy as EmployeeAccount)?.employee
|
||||
if (employee !== undefined) {
|
||||
return employees.get(employee)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<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 isEditing}
|
||||
<AttachmentRefInput
|
||||
space={message.space}
|
||||
_class={chunter.class.Comment}
|
||||
objectId={message._id}
|
||||
content={message.content}
|
||||
on:message={onMessageEdit}
|
||||
/>
|
||||
{:else}
|
||||
<div class="text"><MessageViewer message={message.content} /></div>
|
||||
{#if message.attachments}<div class="attachments"><AttachmentList {attachments} /></div>{/if}
|
||||
{/if}
|
||||
{#if reactions}
|
||||
<div class="footer">
|
||||
<div><Reactions /></div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<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: 0.25rem;
|
||||
|
||||
span {
|
||||
margin-left: 0.5rem;
|
||||
font-weight: 400;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.125rem;
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
.text {
|
||||
line-height: 150%;
|
||||
}
|
||||
.attachments {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 2rem;
|
||||
margin-top: 0.5rem;
|
||||
user-select: none;
|
||||
|
||||
div + div {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
top: -0.5rem;
|
||||
right: -0.5rem;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
user-select: none;
|
||||
|
||||
.tool + .tool {
|
||||
margin-right: 0.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: 0.75rem;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -26,7 +26,6 @@
|
||||
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()
|
||||
@ -92,7 +91,7 @@
|
||||
},
|
||||
(res) => {
|
||||
comments = res
|
||||
newMessagesPos = newMessagesStart(comments)
|
||||
newMessagesPos = newMessagesStart(comments, $lastViews)
|
||||
notificationClient.updateLastView(id, chunter.class.Message)
|
||||
},
|
||||
{
|
||||
@ -153,8 +152,8 @@
|
||||
}
|
||||
let comments: ThreadMessage[] = []
|
||||
|
||||
function newMessagesStart (comments: ThreadMessage[]): number {
|
||||
const lastView = $lastViews.get(_id)
|
||||
function newMessagesStart (comments: ThreadMessage[], lastViews: Map<Ref<Doc>, number>): number {
|
||||
const lastView = lastViews.get(_id)
|
||||
if (lastView === undefined || lastView === -1) return -1
|
||||
for (let index = 0; index < comments.length; index++) {
|
||||
const comment = comments[index]
|
||||
@ -165,7 +164,7 @@
|
||||
|
||||
$: markUnread($lastViews)
|
||||
function markUnread (lastViews: Map<Ref<Doc>, number>) {
|
||||
const newPos = newMessagesStart(comments)
|
||||
const newPos = newMessagesStart(comments, lastViews)
|
||||
if (newPos !== -1 || newMessagesPos === -1) {
|
||||
newMessagesPos = newPos
|
||||
}
|
||||
@ -194,11 +193,7 @@
|
||||
{#if newMessagesPos === i}
|
||||
<ChannelSeparator title={chunter.string.New} line reverse isNew />
|
||||
{/if}
|
||||
<ThreadComment
|
||||
message={comment}
|
||||
{employees}
|
||||
isPinned={pinnedIds.includes(comment._id)}
|
||||
/>
|
||||
<MsgView message={comment} {employees} thread isPinned={pinnedIds.includes(comment._id)} />
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
@ -231,10 +226,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.content {
|
||||
margin: 1rem 1rem 0px;
|
||||
padding: 1.5rem 1.5rem 0px;
|
||||
}
|
||||
.ref-input {
|
||||
margin: 1.25rem 2.5rem;
|
||||
}
|
||||
|
55
plugins/chunter-resources/src/components/Threads.svelte
Normal file
55
plugins/chunter-resources/src/components/Threads.svelte
Normal file
@ -0,0 +1,55 @@
|
||||
<!--
|
||||
// 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 { Message } from '@anticrm/chunter'
|
||||
import { getCurrentAccount, Ref, SortingOrder } from '@anticrm/core'
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import { Label, Scroller } from '@anticrm/ui'
|
||||
import chunter from '../plugin'
|
||||
import Thread from './Thread.svelte'
|
||||
|
||||
const query = createQuery()
|
||||
const me = getCurrentAccount()._id
|
||||
|
||||
let threads: Ref<Message>[] = []
|
||||
|
||||
query.query(chunter.class.ThreadMessage, {
|
||||
createBy: me
|
||||
}, (res) => {
|
||||
const ids = new Set(res.map((c) => c.attachedTo))
|
||||
threads = Array.from(ids)
|
||||
}, {
|
||||
sort: {
|
||||
createOn: SortingOrder.Descending
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="ac-header full divide">
|
||||
<div class="ac-header__wrap-title">
|
||||
<span class="ac-header__title"><Label label={chunter.string.Threads} /></span>
|
||||
</div>
|
||||
</div>
|
||||
<Scroller>
|
||||
{#each threads as thread (thread)}
|
||||
<div class="item"><Thread _id={thread}/></div>
|
||||
{/each}
|
||||
</Scroller>
|
||||
|
||||
<style lang="scss">
|
||||
.item + .item {
|
||||
margin-top: 3rem;
|
||||
}
|
||||
</style>
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
const fill: string = 'var(--theme-caption-color)'
|
||||
const fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
|
@ -15,7 +15,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
const fill: string = 'var(--theme-caption-color)'
|
||||
const fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
|
@ -30,6 +30,7 @@ import CommentsPresenter from './components/CommentsPresenter.svelte'
|
||||
import CreateChannel from './components/CreateChannel.svelte'
|
||||
import EditChannel from './components/EditChannel.svelte'
|
||||
import ThreadView from './components/ThreadView.svelte'
|
||||
import Threads from './components/Threads.svelte'
|
||||
|
||||
export { CommentsPresenter }
|
||||
|
||||
@ -40,28 +41,27 @@ async function MarkUnread (object: Message): Promise<void> {
|
||||
|
||||
async function MarkCommentUnread (object: ThreadMessage): Promise<void> {
|
||||
const client = NotificationClientImpl.getClient()
|
||||
const value = object.modifiedOn - 1
|
||||
await client.updateLastView(object.attachedTo, object.attachedToClass, value, true)
|
||||
await client.updateLastView(object.attachedTo, object.attachedToClass, object.createOn - 1, true)
|
||||
}
|
||||
|
||||
async function SubscribeMessage (object: Message): Promise<void> {
|
||||
const client = NotificationClientImpl.getClient()
|
||||
await client.updateLastView(object._id, object._class, undefined, true)
|
||||
}
|
||||
|
||||
async function SubscribeComment (object: ThreadMessage): Promise<void> {
|
||||
const client = NotificationClientImpl.getClient()
|
||||
await client.updateLastView(object.attachedTo, object.attachedToClass, undefined, true)
|
||||
const client = getClient()
|
||||
const notificationClient = NotificationClientImpl.getClient()
|
||||
if (client.getHierarchy().isDerived(object._class, chunter.class.ThreadMessage)) {
|
||||
await notificationClient.updateLastView(object.attachedTo, object.attachedToClass, undefined, true)
|
||||
} else {
|
||||
await notificationClient.updateLastView(object._id, object._class, undefined, true)
|
||||
}
|
||||
}
|
||||
|
||||
async function UnsubscribeMessage (object: Message): Promise<void> {
|
||||
const client = NotificationClientImpl.getClient()
|
||||
await client.unsubscribe(object._id)
|
||||
}
|
||||
|
||||
async function UnsubscribeComment (object: ThreadMessage): Promise<void> {
|
||||
const client = NotificationClientImpl.getClient()
|
||||
await client.unsubscribe(object.attachedTo)
|
||||
const client = getClient()
|
||||
const notificationClient = NotificationClientImpl.getClient()
|
||||
if (client.getHierarchy().isDerived(object._class, chunter.class.ThreadMessage)) {
|
||||
await notificationClient.unsubscribe(object.attachedTo)
|
||||
} else {
|
||||
await notificationClient.unsubscribe(object._id)
|
||||
}
|
||||
}
|
||||
|
||||
async function PinMessage (message: ChunterMessage): Promise<void> {
|
||||
@ -90,6 +90,7 @@ export default async (): Promise<Resources> => ({
|
||||
CommentsPresenter,
|
||||
ChannelPresenter,
|
||||
EditChannel,
|
||||
Threads,
|
||||
ThreadView
|
||||
},
|
||||
activity: {
|
||||
@ -101,9 +102,7 @@ export default async (): Promise<Resources> => ({
|
||||
MarkUnread,
|
||||
MarkCommentUnread,
|
||||
SubscribeMessage,
|
||||
SubscribeComment,
|
||||
UnsubscribeMessage,
|
||||
UnsubscribeComment,
|
||||
PinMessage,
|
||||
UnpinMessage
|
||||
}
|
||||
|
@ -28,9 +28,7 @@ export default mergeIds(chunterId, chunter, {
|
||||
},
|
||||
actionImpl: {
|
||||
SubscribeMessage: '' as Resource<(object: Doc) => Promise<void>>,
|
||||
SubscribeComment: '' as Resource<(object: Doc) => Promise<void>>,
|
||||
UnsubscribeMessage: '' as Resource<(object: Doc) => Promise<void>>,
|
||||
UnsubscribeComment: '' as Resource<(object: Doc) => Promise<void>>,
|
||||
PinMessage: '' as Resource<(object: Doc) => Promise<void>>,
|
||||
UnpinMessage: '' as Resource<(object: Doc) => Promise<void>>
|
||||
},
|
||||
@ -48,6 +46,7 @@ export default mergeIds(chunterId, chunter, {
|
||||
Replies: '' as IntlString,
|
||||
Topic: '' as IntlString,
|
||||
Thread: '' as IntlString,
|
||||
Threads: '' as IntlString,
|
||||
RepliesCount: '' as IntlString,
|
||||
LastReply: '' as IntlString,
|
||||
New: '' as IntlString,
|
||||
@ -57,6 +56,8 @@ export default mergeIds(chunterId, chunter, {
|
||||
UnpinMessage: '' as IntlString,
|
||||
Pinned: '' as IntlString,
|
||||
DeleteMessage: '' as IntlString,
|
||||
EditMessage: '' as IntlString
|
||||
EditMessage: '' as IntlString,
|
||||
AndYou: '' as IntlString,
|
||||
ShowMoreReplies: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
@ -90,6 +90,7 @@ export default plugin(chunterId, {
|
||||
icon: {
|
||||
Chunter: '' as Asset,
|
||||
Hashtag: '' as Asset,
|
||||
Thread: '' as Asset,
|
||||
Lock: '' as Asset
|
||||
},
|
||||
component: {
|
||||
|
Loading…
Reference in New Issue
Block a user