mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-11 12:17:44 +02:00
Combine into one application chat, inbox and home (#9865)
Signed-off-by: Artem Savchenko <armisav@gmail.com>
This commit is contained in:
@@ -1,147 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 cardPlugin, { Card } from '@hcengineering/card'
|
||||
import {
|
||||
defineSeparators,
|
||||
Separator,
|
||||
deviceOptionsStore as deviceInfo,
|
||||
resolvedLocationStore,
|
||||
Location,
|
||||
restoreLocation,
|
||||
Component,
|
||||
closePanel,
|
||||
getCurrentLocation
|
||||
} from '@hcengineering/ui'
|
||||
import { onDestroy } from 'svelte'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { inboxId } from '@hcengineering/inbox'
|
||||
import view from '@hcengineering/view'
|
||||
import { NotificationContext } from '@hcengineering/communication-types'
|
||||
|
||||
import InboxNavigation from './InboxNavigation.svelte'
|
||||
import { getCardIdFromLocation, navigateToCard } from '../location'
|
||||
import InboxHeader from './InboxHeader.svelte'
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let replacedPanelElement: HTMLElement
|
||||
let card: Card | undefined = undefined
|
||||
let needRestoreLoc = true
|
||||
|
||||
async function syncLocation (loc: Location): Promise<void> {
|
||||
if (loc.path[2] !== inboxId) {
|
||||
return
|
||||
}
|
||||
|
||||
const cardId = getCardIdFromLocation(loc)
|
||||
|
||||
if (cardId == null || cardId === '') {
|
||||
card = undefined
|
||||
if (needRestoreLoc) {
|
||||
needRestoreLoc = false
|
||||
restoreLocation(loc, inboxId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
needRestoreLoc = false
|
||||
|
||||
if (cardId !== card?._id) {
|
||||
card = await client.findOne(cardPlugin.class.Card, { _id: cardId })
|
||||
}
|
||||
}
|
||||
|
||||
function selectCard (event: CustomEvent<{ context: NotificationContext, card: Card }>): void {
|
||||
closePanel()
|
||||
const loc = getCurrentLocation()
|
||||
if (card?._id === event.detail.card._id && loc.path[2] === inboxId) return
|
||||
card = event.detail.card
|
||||
navigateToCard(card._id)
|
||||
}
|
||||
|
||||
function handleClose (): void {
|
||||
navigateToCard(undefined)
|
||||
}
|
||||
|
||||
onDestroy(
|
||||
resolvedLocationStore.subscribe((loc) => {
|
||||
void syncLocation(loc)
|
||||
})
|
||||
)
|
||||
|
||||
defineSeparators('new-inbox', [
|
||||
{ minSize: 15, maxSize: 60, size: 30, float: 'navigator' },
|
||||
{ size: 'auto', minSize: 20, maxSize: 'auto' }
|
||||
])
|
||||
|
||||
$: $deviceInfo.replacedPanel = replacedPanelElement
|
||||
onDestroy(() => ($deviceInfo.replacedPanel = undefined))
|
||||
</script>
|
||||
|
||||
<div class="hulyPanels-container inbox">
|
||||
{#if $deviceInfo.navigator.visible}
|
||||
<div
|
||||
class="antiPanel-navigator {$deviceInfo.navigator.direction === 'horizontal'
|
||||
? 'portrait'
|
||||
: 'landscape'} border-left inbox__navigator"
|
||||
class:fly={$deviceInfo.navigator.float}
|
||||
>
|
||||
<div class="antiPanel-wrap__content hulyNavPanel-container">
|
||||
<InboxHeader />
|
||||
<div class="antiPanel-wrap__content hulyNavPanel-container">
|
||||
<InboxNavigation {card} on:select={selectCard} />
|
||||
</div>
|
||||
</div>
|
||||
{#if !($deviceInfo.isMobile && $deviceInfo.isPortrait && $deviceInfo.minWidth)}
|
||||
<Separator name="new-inbox" float={$deviceInfo.navigator.float ? 'navigator' : true} index={0} />
|
||||
{/if}
|
||||
</div>
|
||||
<Separator
|
||||
name="new-inbox"
|
||||
float={$deviceInfo.navigator.float}
|
||||
index={0}
|
||||
color={'transparent'}
|
||||
separatorSize={0}
|
||||
short
|
||||
/>
|
||||
{/if}
|
||||
<div bind:this={replacedPanelElement} class="hulyComponent inbox__panel">
|
||||
{#if card}
|
||||
{@const panel = client.getHierarchy().classHierarchyMixin(card._class, view.mixin.ObjectPanel)}
|
||||
<Component
|
||||
is={panel?.component ?? view.component.EditDoc}
|
||||
props={{
|
||||
_id: card._id,
|
||||
embedded: true
|
||||
}}
|
||||
on:close={handleClose}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.inbox {
|
||||
&__navigator {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__panel {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,181 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 { NotificationContext } from '@hcengineering/communication-types'
|
||||
import { Card } from '@hcengineering/card'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { createNotificationsQuery, getCommunicationClient } from '@hcengineering/presentation'
|
||||
import { CheckBox, Spinner } from '@hcengineering/ui'
|
||||
import { AccountRole, getCurrentAccount } from '@hcengineering/core'
|
||||
|
||||
import InboxNotification from './InboxNotification.svelte'
|
||||
import InboxCardIcon from './InboxCardIcon.svelte'
|
||||
|
||||
export let context: NotificationContext
|
||||
export let card: Card
|
||||
export let selected: boolean = false
|
||||
|
||||
const account = getCurrentAccount()
|
||||
const dispatch = createEventDispatcher()
|
||||
const communicationClient = getCommunicationClient()
|
||||
|
||||
const notificationsQuery = createNotificationsQuery()
|
||||
|
||||
let total = 0
|
||||
|
||||
notificationsQuery.query({ limit: 1, total: true, read: false, strict: true, context: context.id }, (res) => {
|
||||
total = res.getTotal()
|
||||
})
|
||||
|
||||
let isRemoving = false
|
||||
async function handleToggle (): Promise<void> {
|
||||
isRemoving = true
|
||||
try {
|
||||
await communicationClient.removeNotificationContext(context.id)
|
||||
} finally {
|
||||
isRemoving = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="inbox-card" class:selected on:click={() => dispatch('select', { context, card })}>
|
||||
<div class="inbox-card__header">
|
||||
<InboxCardIcon {card} count={total ?? 0} />
|
||||
<div class="inbox-card__labels">
|
||||
<span class="inbox-card__title overflow-label clear-mins" title={card.title}>
|
||||
{card.title}
|
||||
</span>
|
||||
</div>
|
||||
{#if account.role !== AccountRole.ReadOnlyGuest}
|
||||
<div class="inbox-card__remove">
|
||||
{#if isRemoving}
|
||||
<Spinner size="small" />
|
||||
{:else}
|
||||
<CheckBox kind="todo" size="medium" on:value={handleToggle} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="inbox-card__content">
|
||||
<div class="inbox-card__notifications">
|
||||
{#each context.notifications ?? [] as notification}
|
||||
<div class="inbox-card__notification">
|
||||
<div class="inbox-card__marker" />
|
||||
<InboxNotification {notification} {card} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.inbox-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
border-bottom: 1px solid var(--global-ui-BorderColor);
|
||||
min-height: 5.625rem;
|
||||
|
||||
&.selected {
|
||||
background-color: var(--global-ui-highlight-BackgroundColor);
|
||||
}
|
||||
|
||||
&:hover:not(.selected) {
|
||||
background-color: var(--global-ui-highlight-BackgroundColor);
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-left: var(--spacing-0_5);
|
||||
}
|
||||
|
||||
&__labels {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
min-width: 0;
|
||||
color: var(--global-primary-TextColor);
|
||||
font-size: 0.875rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-weight: 400;
|
||||
color: var(--global-primary-TextColor);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&__remove {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
min-width: 1.5rem;
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
&__notifications {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
margin-top: var(--spacing-1);
|
||||
}
|
||||
|
||||
&__notification {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
&:first-child .inbox-card__marker {
|
||||
border-top-left-radius: 0.5rem;
|
||||
border-top-right-radius: 0.5rem;
|
||||
}
|
||||
|
||||
&:last-child .inbox-card__marker {
|
||||
border-bottom-left-radius: 0.5rem;
|
||||
border-bottom-right-radius: 0.5rem;
|
||||
}
|
||||
|
||||
&:hover .inbox-card__marker {
|
||||
border-radius: 0.5rem;
|
||||
background: var(--global-primary-LinkColor);
|
||||
}
|
||||
}
|
||||
|
||||
&__marker {
|
||||
position: absolute;
|
||||
width: 0.25rem;
|
||||
height: 100%;
|
||||
background: var(--global-ui-highlight-BackgroundColor);
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,68 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 cardPlugin, { Card } from '@hcengineering/card'
|
||||
import { Component } from '@hcengineering/ui'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import communication from '@hcengineering/communication'
|
||||
|
||||
import NotifyMarker from './NotifyMarker.svelte'
|
||||
|
||||
export let card: Card
|
||||
export let count: number = 0
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
$: size = hierarchy.isDerived(card._class, communication.type.Direct) ? 'large' : 'medium'
|
||||
</script>
|
||||
|
||||
<div class="card-icon">
|
||||
<Component is={cardPlugin.component.CardIcon} props={{ value: card, size }} />
|
||||
|
||||
{#if count > 0}
|
||||
<div class="card-icon__marker">
|
||||
<NotifyMarker {count} size="small" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.card-icon {
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
min-width: 2.5rem;
|
||||
min-height: 2.5rem;
|
||||
color: var(--global-secondary-TextColor);
|
||||
background-color: var(--global-ui-BackgroundColor);
|
||||
border: 1px solid var(--global-subtle-ui-BorderColor);
|
||||
border-radius: var(--medium-BorderRadius);
|
||||
fill: var(--global-secondary-TextColor);
|
||||
|
||||
&__marker {
|
||||
position: absolute;
|
||||
top: -0.375rem;
|
||||
right: 0;
|
||||
transform: translateX(calc(100% - 0.875rem));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,61 +0,0 @@
|
||||
<!-- Copyright © 2025 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 { SortingOrder } from '@hcengineering/core'
|
||||
import { IconDelete, IconSettings, Label, ModernButton, showPopup, eventToHTMLElement } from '@hcengineering/ui'
|
||||
import { getCommunicationClient } from '@hcengineering/presentation'
|
||||
|
||||
import inbox from '../plugin'
|
||||
import InboxViewSettings from './InboxViewSettings.svelte'
|
||||
|
||||
const communicationClient = getCommunicationClient()
|
||||
|
||||
let clearing = false
|
||||
async function clearInbox (): Promise<void> {
|
||||
if (clearing) return
|
||||
try {
|
||||
clearing = true
|
||||
const contexts = await communicationClient.findNotificationContexts({ order: SortingOrder.Ascending })
|
||||
await Promise.all(contexts.map((context) => communicationClient.removeNotificationContext(context.id)))
|
||||
clearing = false
|
||||
} catch (e) {
|
||||
clearing = false
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
function click (e: MouseEvent): void {
|
||||
showPopup(InboxViewSettings, {}, eventToHTMLElement(e))
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="hulyNavPanel-header withButton small inbox-header">
|
||||
<span class="overflow-label"><Label label={inbox.string.Inbox} /></span>
|
||||
<div class="flex-row-center flex-gap-2">
|
||||
<ModernButton
|
||||
label={inbox.string.ClearAll}
|
||||
icon={IconDelete}
|
||||
size="small"
|
||||
iconSize="small"
|
||||
on:click={clearInbox}
|
||||
loading={clearing}
|
||||
/>
|
||||
<ModernButton icon={IconSettings} on:click={click} size="small" iconSize="small" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.inbox-header {
|
||||
border-bottom: 1px solid var(--theme-navpanel-border);
|
||||
}
|
||||
</style>
|
||||
@@ -1,201 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 cardPlugin, { Card } from '@hcengineering/card'
|
||||
import { Label, ListView, Loading, Scroller } from '@hcengineering/ui'
|
||||
import { SortingOrder } from '@hcengineering/core'
|
||||
import { createNotificationContextsQuery, createQuery, getCommunicationClient } from '@hcengineering/presentation'
|
||||
import { NotificationContext, Window } from '@hcengineering/communication-types'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import InboxCard from './InboxCard.svelte'
|
||||
import inbox from '../plugin'
|
||||
|
||||
export let card: Card | undefined = undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const limit = 20
|
||||
|
||||
let cards: Card[] = []
|
||||
let contexts: NotificationContext[] = []
|
||||
|
||||
const communicationClient = getCommunicationClient()
|
||||
const cardsQuery = createQuery()
|
||||
const query = createNotificationContextsQuery()
|
||||
|
||||
let divScroll: HTMLElement | undefined | null = undefined
|
||||
let contentDiv: HTMLElement | undefined | null = undefined
|
||||
let window: Window<NotificationContext> | undefined = undefined
|
||||
|
||||
let list: ListView
|
||||
let listSelection = 0
|
||||
|
||||
let isLoading = true
|
||||
|
||||
query.query(
|
||||
{
|
||||
notifications: {
|
||||
message: true,
|
||||
order: SortingOrder.Descending,
|
||||
limit: 3
|
||||
},
|
||||
order: SortingOrder.Descending,
|
||||
limit
|
||||
},
|
||||
(res) => {
|
||||
window = res
|
||||
contexts = res.getResult().filter((it) => (it.notifications?.length ?? 0) > 0)
|
||||
isLoading = false
|
||||
|
||||
if (contexts.length < limit && window.hasPrevPage()) {
|
||||
void window.loadPrevPage()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
$: cardsQuery.query(cardPlugin.class.Card, { _id: { $in: contexts.map((c) => c.cardId) } }, (res) => {
|
||||
cards = res
|
||||
})
|
||||
|
||||
function handleScroll (): void {
|
||||
if (divScroll != null && window != null && window.hasPrevPage()) {
|
||||
const isAtBottom = divScroll.scrollTop + divScroll.clientHeight >= divScroll.scrollHeight - 10
|
||||
if (isAtBottom) {
|
||||
void window.loadPrevPage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function onKeydown (key: KeyboardEvent): Promise<void> {
|
||||
if (key.code === 'ArrowUp') {
|
||||
key.stopPropagation()
|
||||
key.preventDefault()
|
||||
list.select(listSelection - 1)
|
||||
}
|
||||
if (key.code === 'ArrowDown') {
|
||||
key.stopPropagation()
|
||||
key.preventDefault()
|
||||
list.select(listSelection + 1)
|
||||
}
|
||||
if (key.code === 'Backspace') {
|
||||
key.preventDefault()
|
||||
key.stopPropagation()
|
||||
const context = contexts[listSelection]
|
||||
if (context != null) {
|
||||
await communicationClient.removeNotificationContext(context.id)
|
||||
}
|
||||
}
|
||||
if (key.code === 'Enter') {
|
||||
key.preventDefault()
|
||||
key.stopPropagation()
|
||||
const context = contexts[listSelection]
|
||||
const card = cards.find((c) => c._id === context.cardId)
|
||||
if (card && context) {
|
||||
dispatch('select', { context, card })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$: if (contentDiv != null) {
|
||||
contentDiv.focus()
|
||||
}
|
||||
|
||||
function getContextKey (index: number): string {
|
||||
const contextId = contexts[index].id
|
||||
return contextId ?? index.toString()
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if contexts.length > 0}
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<Scroller bind:divScroll padding="0" shrink onScroll={handleScroll}>
|
||||
<div class="inbox-list" bind:this={contentDiv} tabindex="0" on:keydown={onKeydown}>
|
||||
<ListView
|
||||
bind:this={list}
|
||||
bind:selection={listSelection}
|
||||
count={contexts.length}
|
||||
items={contexts}
|
||||
highlightIndex={contexts.findIndex((it) => it.cardId === card?._id)}
|
||||
noScroll
|
||||
kind="full-size"
|
||||
colorsSchema="lumia"
|
||||
lazy={true}
|
||||
getKey={getContextKey}
|
||||
>
|
||||
<svelte:fragment slot="item" let:item={itemIndex}>
|
||||
{@const context = contexts[itemIndex]}
|
||||
{@const contextCard = cards.find((c) => c._id === context.cardId)}
|
||||
{#if context && contextCard}
|
||||
<InboxCard
|
||||
{context}
|
||||
card={contextCard}
|
||||
on:select={(e) => {
|
||||
dispatch('select', e.detail)
|
||||
listSelection = itemIndex
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</ListView>
|
||||
</div>
|
||||
</Scroller>
|
||||
{:else if isLoading}
|
||||
<div class="placeholder">
|
||||
<Loading />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="placeholder">
|
||||
<div class="placeholder__header">
|
||||
<Label label={inbox.string.InboxIsClear} />
|
||||
</div>
|
||||
<span class="mb-4">
|
||||
<Label label={inbox.string.YouDontHaveAnyNewMessages} />
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.inbox-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
:global(.list-item) {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
display: flex;
|
||||
align-self: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
height: inherit;
|
||||
width: 100%;
|
||||
gap: 0.5rem;
|
||||
|
||||
&__header {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,31 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 { Notification, NotificationType } from '@hcengineering/communication-types'
|
||||
import { Card } from '@hcengineering/card'
|
||||
|
||||
import MessageNotification from './MessageNotification.svelte'
|
||||
import ReactionNotification from './ReactionNotification.svelte'
|
||||
|
||||
export let notification: Notification
|
||||
export let card: Card
|
||||
</script>
|
||||
|
||||
{#if notification.type === NotificationType.Message}
|
||||
<MessageNotification {notification} {card} />
|
||||
{:else if notification.type === NotificationType.Reaction}
|
||||
<ReactionNotification {notification} {card} />
|
||||
{/if}
|
||||
@@ -1,120 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2023 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 { createEventDispatcher } from 'svelte'
|
||||
import { createFocusManager, FocusHandler, Label, ListView, ModernToggle, resizeObserver } from '@hcengineering/ui'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
|
||||
import { updateViewSetting, isViewSettingEnabled, viewSettingsStore, hideUserNamesSettingId } from '../settings'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let popupElement: HTMLDivElement | undefined = undefined
|
||||
|
||||
const items = [
|
||||
{
|
||||
id: hideUserNamesSettingId,
|
||||
label: getEmbeddedLabel('Hide user names')
|
||||
}
|
||||
]
|
||||
|
||||
let selection = 0
|
||||
let list: ListView
|
||||
|
||||
export function onKeydown (key: KeyboardEvent): boolean {
|
||||
if (key.code === 'Tab') {
|
||||
dispatch('close')
|
||||
key.preventDefault()
|
||||
key.stopPropagation()
|
||||
return true
|
||||
}
|
||||
if (key.code === 'ArrowUp') {
|
||||
key.stopPropagation()
|
||||
key.preventDefault()
|
||||
list.select(selection - 1)
|
||||
return true
|
||||
}
|
||||
if (key.code === 'ArrowDown') {
|
||||
key.stopPropagation()
|
||||
key.preventDefault()
|
||||
list.select(selection + 1)
|
||||
return true
|
||||
}
|
||||
if (key.code === 'Enter') {
|
||||
key.preventDefault()
|
||||
key.stopPropagation()
|
||||
const item = items[selection]
|
||||
if (item != null) {
|
||||
onToggle(item.id)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const manager = createFocusManager()
|
||||
|
||||
$: if (popupElement) {
|
||||
popupElement.focus()
|
||||
}
|
||||
|
||||
function onToggle (id: string): void {
|
||||
updateViewSetting(id, !isViewSettingEnabled($viewSettingsStore, id))
|
||||
}
|
||||
</script>
|
||||
|
||||
<FocusHandler {manager} />
|
||||
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="selectPopup"
|
||||
bind:this={popupElement}
|
||||
tabindex="0"
|
||||
use:resizeObserver={() => {
|
||||
dispatch('changeContent')
|
||||
}}
|
||||
on:keydown={onKeydown}
|
||||
>
|
||||
<div class="menu-space" />
|
||||
<div class="scroll">
|
||||
<div class="box">
|
||||
<ListView bind:this={list} count={items.length} bind:selection on:changeContent={() => dispatch('changeContent')}>
|
||||
<svelte:fragment slot="item" let:item={itemId}>
|
||||
{@const item = items[itemId]}
|
||||
<div class="menu-item withList w-full container">
|
||||
<Label label={item.label} />
|
||||
<ModernToggle
|
||||
checked={isViewSettingEnabled($viewSettingsStore, item.id)}
|
||||
size="small"
|
||||
on:change={() => {
|
||||
onToggle(item.id)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</ListView>
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-space" />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
@@ -1,27 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 { Notification } from '@hcengineering/communication-types'
|
||||
import { Card } from '@hcengineering/card'
|
||||
import NotificationPreview from './preview/NotificationPreview.svelte'
|
||||
|
||||
export let notification: Notification
|
||||
export let card: Card
|
||||
</script>
|
||||
|
||||
{#if notification.message}
|
||||
<NotificationPreview {card} message={notification.message} date={notification.created} />
|
||||
{/if}
|
||||
@@ -1,69 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2024 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">
|
||||
export let count: number = 0
|
||||
export let kind: 'primary' | 'simple' = 'primary'
|
||||
export let size: 'xx-small' | 'x-small' | 'small' | 'medium' = 'small'
|
||||
</script>
|
||||
|
||||
{#if kind === 'primary' && count > 0}
|
||||
<div class="notifyMarker {size} {kind}">
|
||||
{count}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if kind === 'simple'}
|
||||
<div class="notifyMarker {size} {kind}" />
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.notifyMarker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
border-radius: 0.5rem;
|
||||
font-weight: 500;
|
||||
padding: 0 0.25rem;
|
||||
|
||||
&.simple,
|
||||
&.primary {
|
||||
background-color: var(--global-higlight-Color);
|
||||
color: var(--global-on-accent-TextColor);
|
||||
}
|
||||
|
||||
&.xx-small {
|
||||
min-width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
}
|
||||
|
||||
&.x-small {
|
||||
min-width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
}
|
||||
|
||||
&.small {
|
||||
min-width: 1rem;
|
||||
height: 1rem;
|
||||
font-size: 0.5rem;
|
||||
}
|
||||
|
||||
&.medium {
|
||||
min-width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
font-size: 0.625rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,95 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 { Notification, ReactionNotificationContent, SocialID } from '@hcengineering/communication-types'
|
||||
import { EmojiPresenter } from '@hcengineering/emoji-resources'
|
||||
import { Card } from '@hcengineering/card'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import { Person } from '@hcengineering/contact'
|
||||
import { employeeByPersonIdStore, getPersonByPersonId } from '@hcengineering/contact-resources'
|
||||
|
||||
import NotificationPreview from './preview/NotificationPreview.svelte'
|
||||
import PreviewTemplate from './preview/PreviewTemplate.svelte'
|
||||
import inbox from '../plugin'
|
||||
|
||||
export let notification: Notification
|
||||
export let card: Card
|
||||
|
||||
let content = notification.content as ReactionNotificationContent
|
||||
$: content = notification.content as ReactionNotificationContent
|
||||
|
||||
let author: Person | undefined
|
||||
$: void updateAuthor(content.creator)
|
||||
|
||||
async function updateAuthor (socialId: SocialID): Promise<void> {
|
||||
author = $employeeByPersonIdStore.get(socialId)
|
||||
|
||||
if (author === undefined) {
|
||||
author = (await getPersonByPersonId(socialId)) ?? undefined
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if notification.message}
|
||||
<div class="reaction-notification">
|
||||
<PreviewTemplate socialId={content.creator} date={notification.created} color="secondary" showSeparator={false}>
|
||||
<svelte:fragment slot="content">
|
||||
<span class="ml-1-5" />
|
||||
<Label label={inbox.string.ReactedToYourMessage} />
|
||||
</svelte:fragment>
|
||||
</PreviewTemplate>
|
||||
|
||||
<div class="reaction-notification__body">
|
||||
<div class="reaction-notification__emoji">
|
||||
<EmojiPresenter emoji={content.emoji} fitSize center />
|
||||
</div>
|
||||
<NotificationPreview
|
||||
{card}
|
||||
message={notification.message}
|
||||
date={notification.created}
|
||||
kind="column"
|
||||
padding="0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.reaction-notification {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: var(--global-secondary-TextColor);
|
||||
white-space: nowrap;
|
||||
|
||||
&__body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding-right: var(--spacing-0_75);
|
||||
padding-left: var(--spacing-1_25);
|
||||
}
|
||||
|
||||
&__emoji {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 2rem;
|
||||
width: 2.5rem;
|
||||
min-width: 2.5rem;
|
||||
min-height: 2.5rem;
|
||||
height: 2.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,75 +0,0 @@
|
||||
<!-- Copyright © 2025 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 { LiteMessageViewer } from '@hcengineering/presentation'
|
||||
import { Card } from '@hcengineering/card'
|
||||
import { type WithLookup } from '@hcengineering/core'
|
||||
import { Message, MessageType, SocialID } from '@hcengineering/communication-types'
|
||||
import { Person } from '@hcengineering/contact'
|
||||
import { getEmbeddedLabel, IntlString } from '@hcengineering/platform'
|
||||
import { employeeByPersonIdStore, getPersonByPersonId } from '@hcengineering/contact-resources'
|
||||
import { markdownToMarkup } from '@hcengineering/text-markdown'
|
||||
import { jsonToMarkup, markupToText } from '@hcengineering/text'
|
||||
import { ActivityMessageViewer, isActivityMessage, AttachmentsPreview } from '@hcengineering/communication-resources'
|
||||
|
||||
import PreviewTemplate from './PreviewTemplate.svelte'
|
||||
|
||||
export let card: Card
|
||||
export let message: Message
|
||||
export let date: Date
|
||||
export let color: 'primary' | 'secondary' = 'primary'
|
||||
export let kind: 'default' | 'column' = 'default'
|
||||
export let padding: string | undefined = undefined
|
||||
|
||||
const tooltipLimit = 512
|
||||
|
||||
let person: WithLookup<Person> | undefined = undefined
|
||||
|
||||
$: void updatePerson(message.creator)
|
||||
|
||||
function getTooltipLabel (message: Message): IntlString {
|
||||
const text = markupToText(jsonToMarkup(markdownToMarkup(message.content)))
|
||||
if (text.length > tooltipLimit) {
|
||||
return getEmbeddedLabel(text.substring(0, tooltipLimit) + '...')
|
||||
}
|
||||
return getEmbeddedLabel(text)
|
||||
}
|
||||
|
||||
async function updatePerson (socialId: SocialID): Promise<void> {
|
||||
person = $employeeByPersonIdStore.get(socialId) ?? (await getPersonByPersonId(socialId)) ?? undefined
|
||||
}
|
||||
</script>
|
||||
|
||||
<PreviewTemplate
|
||||
{kind}
|
||||
{color}
|
||||
{person}
|
||||
{padding}
|
||||
socialId={message.creator}
|
||||
{date}
|
||||
fixHeight={message.type !== MessageType.Activity}
|
||||
tooltipLabel={getTooltipLabel(message)}
|
||||
>
|
||||
<svelte:fragment slot="content">
|
||||
{#if isActivityMessage(message)}
|
||||
<ActivityMessageViewer {message} {card} author={person} />
|
||||
{:else}
|
||||
<LiteMessageViewer message={markdownToMarkup(message.content)} />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="after">
|
||||
<AttachmentsPreview {message} />
|
||||
</svelte:fragment>
|
||||
</PreviewTemplate>
|
||||
@@ -1,228 +0,0 @@
|
||||
<!-- Copyright © 2025 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 { Person, formatName } from '@hcengineering/contact'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { resizeObserver, TimeSince, tooltip } from '@hcengineering/ui'
|
||||
import {
|
||||
Avatar,
|
||||
PersonPreviewProvider,
|
||||
SystemAvatar,
|
||||
employeeByPersonIdStore,
|
||||
getPersonByPersonId
|
||||
} from '@hcengineering/contact-resources'
|
||||
import { SocialID } from '@hcengineering/communication-types'
|
||||
import { isViewSettingEnabled, hideUserNamesSettingId, viewSettingsStore } from '../../settings'
|
||||
|
||||
export let tooltipLabel: IntlString | undefined = undefined
|
||||
export let person: Person | undefined = undefined
|
||||
export let socialId: SocialID
|
||||
export let date: Date
|
||||
export let color: 'primary' | 'secondary' = 'primary'
|
||||
export let kind: 'default' | 'column' = 'default'
|
||||
export let padding: string | undefined = undefined
|
||||
export let fixHeight: boolean = false
|
||||
export let showSeparator: boolean = true
|
||||
|
||||
let clientWidth: number
|
||||
|
||||
$: showPersonName = clientWidth > 300 && !isViewSettingEnabled($viewSettingsStore, hideUserNamesSettingId)
|
||||
|
||||
let _person: Person | undefined
|
||||
$: void updatePerson(socialId, person)
|
||||
|
||||
async function updatePerson (socialId: SocialID, person: Person | undefined): Promise<void> {
|
||||
_person = person ?? $employeeByPersonIdStore.get(socialId)
|
||||
if (!_person) {
|
||||
_person = (await getPersonByPersonId(socialId)) ?? undefined
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if kind === 'column'}
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="message-preview-template clear-mins {color} {kind}"
|
||||
style:padding
|
||||
use:resizeObserver={(element) => (clientWidth = element.clientWidth)}
|
||||
>
|
||||
<span class="message-preview-template__column-header">
|
||||
{#if _person}
|
||||
<span class="overflow-label clear-mins">
|
||||
<PersonPreviewProvider value={_person} inline>
|
||||
{formatName(_person?.name ?? '')}
|
||||
</PersonPreviewProvider>
|
||||
</span>
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<span class="message-preview-template__left">
|
||||
<span
|
||||
class="message-preview-template__content overflow-label {color}"
|
||||
class:fixHeight
|
||||
use:tooltip={tooltipLabel ? { label: tooltipLabel } : undefined}
|
||||
>
|
||||
<slot />
|
||||
</span>
|
||||
|
||||
<slot name="after" />
|
||||
</span>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="message-preview-template clear-mins {color}"
|
||||
use:resizeObserver={(element) => (clientWidth = element.clientWidth)}
|
||||
>
|
||||
<span class="message-preview-template__left">
|
||||
<PersonPreviewProvider value={_person} inline>
|
||||
<span class="message-preview-template__person">
|
||||
<span class="message-preview-template__avatar">
|
||||
{#if _person}
|
||||
<Avatar size="card" person={_person} name={_person.name} />
|
||||
{:else}
|
||||
<SystemAvatar size="card" />
|
||||
{/if}
|
||||
</span>
|
||||
{#if showPersonName && _person}
|
||||
<span class="message-preview-template__name overflow-label {color}">{formatName(_person?.name ?? '')}</span>
|
||||
{/if}
|
||||
{#if showSeparator}
|
||||
{#if showPersonName}
|
||||
<span class="message-preview-template__separator"> • </span>
|
||||
{:else}
|
||||
<span class="mr-0-5" />
|
||||
{/if}
|
||||
{/if}
|
||||
</span>
|
||||
</PersonPreviewProvider>
|
||||
|
||||
{#if $$slots.content}
|
||||
<span
|
||||
class="message-preview-template__content overflow-label {color}"
|
||||
class:fixHeight
|
||||
use:tooltip={tooltipLabel ? { label: tooltipLabel, textAlign: 'left' } : undefined}
|
||||
>
|
||||
<slot name="content" />
|
||||
</span>
|
||||
{/if}
|
||||
<slot name="after" />
|
||||
</span>
|
||||
|
||||
<span class="message-preview-template__time clear-mins">
|
||||
<TimeSince value={date.getTime()} />
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.message-preview-template {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-height: 2rem;
|
||||
color: var(--global-secondary-TextColor);
|
||||
padding: 0.25rem var(--spacing-1_25) 0.25rem var(--spacing-1_25);
|
||||
overflow: hidden;
|
||||
font-size: 0.875rem;
|
||||
gap: 0.25rem;
|
||||
|
||||
&.column {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
&__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
min-width: 2.75rem;
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__person {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
color: var(--global-primary-TextColor);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
&__avatar {
|
||||
width: 1.325rem;
|
||||
min-width: 1.325rem;
|
||||
height: 1.325rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: inline;
|
||||
color: var(--global-primary-TextColor);
|
||||
max-width: 100%;
|
||||
|
||||
&.fixHeight {
|
||||
max-height: 1.375rem;
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
color: var(--global-secondary-TextColor);
|
||||
}
|
||||
}
|
||||
|
||||
&__separator {
|
||||
font-size: 0.875rem;
|
||||
min-width: 0.375rem;
|
||||
flex: 0 0 auto;
|
||||
color: var(--global-primary-TextColor);
|
||||
margin: 0 0.25rem;
|
||||
}
|
||||
|
||||
&__name {
|
||||
font-weight: 500;
|
||||
flex: 0 0 auto;
|
||||
|
||||
&.secondary {
|
||||
color: var(--global-secondary-TextColor);
|
||||
}
|
||||
}
|
||||
|
||||
&__time {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 0.75rem;
|
||||
color: var(--global-tertiary-TextColor);
|
||||
white-space: nowrap;
|
||||
margin-left: auto;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
&__column-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
gap: var(--spacing-0_5);
|
||||
font-weight: 500;
|
||||
color: var(--global-primary-TextColor);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user