Chat and sidebar fixes (#6813)

Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
Kristina 2024-10-07 12:34:50 +04:00 committed by GitHub
parent 030bbb589b
commit 304beeae31
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 353 additions and 155 deletions

View File

@ -46,8 +46,8 @@ export function defineActions (builder: Builder): void {
createAction(builder, {
action: chunter.actionImpl.StartConversation,
label: chunter.string.StartConversation,
icon: chunter.icon.Thread,
label: chunter.string.Message,
icon: view.icon.Bubble,
input: 'focus',
category: chunter.category.Chunter,
target: contact.mixin.Employee,

View File

@ -21,6 +21,7 @@ import core from '@hcengineering/model-core'
import view from '@hcengineering/model-view'
import workbench from '@hcengineering/model-workbench'
import { WidgetType } from '@hcengineering/workbench'
import contact from '@hcengineering/contact'
import chunter from './plugin'
import { defineActions } from './actions'
@ -303,6 +304,16 @@ export function createModel (builder: Builder): void {
disabled: [{ _class: 1 }, { space: 1 }, { modifiedBy: 1 }, { createdBy: 1 }, { createdOn: -1 }]
})
// Extensions
builder.createDoc(presentation.class.ComponentPointExtension, core.space.Model, {
extension: contact.extension.EmployeePopupActions,
component: chunter.component.DirectMessageButton
})
builder.createDoc(presentation.class.ComponentPointExtension, core.space.Model, {
extension: activity.extension.ActivityEmployeePresenter,
component: chunter.component.EmployeePresenter
})
defineActions(builder)
defineNotifications(builder)
}

View File

@ -35,7 +35,9 @@ export default mergeIds(chunterId, chunter, {
ChatMessageNotificationLabel: '' as AnyComponent,
ThreadNotificationPresenter: '' as AnyComponent,
JoinChannelNotificationPresenter: '' as AnyComponent,
WorkbenchTabExtension: '' as AnyComponent
WorkbenchTabExtension: '' as AnyComponent,
DirectMessageButton: '' as AnyComponent,
EmployeePresenter: '' as AnyComponent
},
action: {
MarkCommentUnread: '' as Ref<Action>,

View File

@ -185,8 +185,7 @@ export function createModel (builder: Builder): void {
label: love.string.MeetingRoom,
type: WidgetType.Flexible,
icon: love.icon.Cam,
component: love.component.VideoWidget,
size: 'medium'
component: love.component.VideoWidget
},
love.ids.VideoWidget
)

View File

@ -316,6 +316,7 @@ input.search {
border-radius: 50%;
}
&:not(.small-gap, .large-gap) { margin-right: .375rem; }
&.no-gap { margin-right: 0; }
&.small-gap { margin-right: .25rem; }
&.large-gap { margin-right: .5rem; }
&.flow:last-child { margin-right: 0; }

View File

@ -270,6 +270,9 @@
aspect-ratio: 1;
border-radius: 50%;
&.relative {
position: relative;
}
&.xx-small,
&.inline,
&.tiny,

View File

@ -107,9 +107,9 @@
on:keydown
>
{#if loading}
<div class="icon"><Spinner size={'small'} /></div>
<div class="icon no-gap"><Spinner size={'small'} /></div>
{:else if icon}
<div class="icon"><Icon {icon} {iconProps} size={actualIconSize} /></div>
<div class="icon no-gap"><Icon {icon} {iconProps} size={actualIconSize} /></div>
{/if}
{#if label}<span><Label {label} params={labelParams} /></span>{/if}
{#if title}<span>{title}</span>{/if}

View File

@ -28,6 +28,7 @@
export let lazy = false
export let minHeight: string | null = null
export let highlightIndex: number | undefined = undefined
export let items: any[] = []
export let getKey: (index: number) => string = (index) => index.toString()
const refs: HTMLElement[] = []
@ -65,6 +66,8 @@
r?.scrollIntoView({ behavior: 'auto', block: 'nearest' })
}
}
$: array = items.length > 0 ? items : Array(count)
</script>
{#if count}
@ -75,7 +78,7 @@
dispatch('changeContent')
}}
>
{#each Array(count) as _, row (getKey(row))}
{#each array as _, row (getKey(row))}
{#if lazy}
<div style="min-height: {minHeight}">
<Lazy>
@ -145,6 +148,6 @@
.list-container {
min-width: 0;
// border-radius: 0.25rem;
user-select: none;
//user-select: none;
}
</style>

View File

@ -81,6 +81,7 @@
<style lang="scss">
.container {
font-weight: 500;
font-size: 0.75rem;
border: 1px solid transparent;
border-radius: 0.25rem;
cursor: pointer;

View File

@ -161,7 +161,7 @@
</button>
</div>
{/if}
<div class="flex-row-center left-items" style:-webkit-app-region={'no-drag'}>
<div class="flex-row-center left-items flex-gap-0-5" style:-webkit-app-region={'no-drag'}>
<RootBarExtension position="left" />
</div>
<div
@ -216,7 +216,7 @@
min-height: var(--status-bar-height);
height: var(--status-bar-height);
// min-width: 600px;
font-size: 12px;
font-size: 0.75rem;
line-height: 150%;
background-color: var(--theme-statusbar-color);
// border-bottom: 1px solid var(--theme-navpanel-divider);

View File

@ -14,15 +14,9 @@
-->
<script lang="ts">
import { getClient, LiteMessageViewer } from '@hcengineering/presentation'
import { ComponentExtensions, getClient, LiteMessageViewer } from '@hcengineering/presentation'
import { Person, type PersonAccount } from '@hcengineering/contact'
import {
Avatar,
EmployeePresenter,
personAccountByIdStore,
personByIdStore,
SystemAvatar
} from '@hcengineering/contact-resources'
import { Avatar, personAccountByIdStore, personByIdStore, SystemAvatar } from '@hcengineering/contact-resources'
import core, { Account, Doc, Ref, Timestamp, type WithLookup } from '@hcengineering/core'
import { Icon, Label, resizeObserver, TimeSince, tooltip } from '@hcengineering/ui'
import { Asset, getEmbeddedLabel, IntlString } from '@hcengineering/platform'
@ -131,7 +125,7 @@
/>
</DocNavLink>
{:else if person}
<EmployeePresenter value={person} shouldShowAvatar={false} compact showStatus={false} />
<ComponentExtensions extension={activity.extension.ActivityEmployeePresenter} props={{ person }} />
{:else}
<Label label={core.string.System} />
{/if}

View File

@ -19,9 +19,9 @@
ActivityMessageViewType
} from '@hcengineering/activity'
import { Person } from '@hcengineering/contact'
import { Avatar, EmployeePresenter, SystemAvatar } from '@hcengineering/contact-resources'
import { Avatar, SystemAvatar } from '@hcengineering/contact-resources'
import core, { Ref } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { ComponentExtensions, getClient } from '@hcengineering/presentation'
import { Action, Icon, Label } from '@hcengineering/ui'
import { getActions, restrictionStore, showMenu } from '@hcengineering/view-resources'
import { Asset } from '@hcengineering/platform'
@ -209,7 +209,7 @@
<div class="header clear-mins">
{#if person}
<div class="username">
<EmployeePresenter value={person} shouldShowAvatar={false} compact />
<ComponentExtensions extension={activity.extension.ActivityEmployeePresenter} props={{ person }} />
</div>
{:else}
<div class="strong">

View File

@ -30,7 +30,7 @@ import {
import type { Asset, IntlString, Plugin, Resource } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform'
import { Preference } from '@hcengineering/preference'
import type { AnyComponent } from '@hcengineering/ui'
import type { AnyComponent, ComponentExtensionId } from '@hcengineering/ui'
/**
* @public
@ -334,6 +334,9 @@ export default plugin(activityId, {
AllFilter: '' as Ref<ActivityMessagesFilter>,
MentionNotification: '' as Ref<Doc>
},
extension: {
ActivityEmployeePresenter: '' as ComponentExtensionId
},
function: {
ShouldScrollToActivity: '' as Resource<() => boolean>
},

View File

@ -0,0 +1,57 @@
<script lang="ts">
import contact, { Person, Employee } from '@hcengineering/contact'
import { EmployeePresenter } from '@hcengineering/contact-resources'
import { getClient } from '@hcengineering/presentation'
import { getCurrentLocation, location, Location } from '@hcengineering/ui'
import { decodeObjectURI } from '@hcengineering/view'
import { Ref } from '@hcengineering/core'
import { chunterId } from '@hcengineering/chunter'
import { notificationId } from '@hcengineering/notification'
import { createDirect } from '../utils'
import { openChannel } from '../navigation'
import chunter from '../plugin'
export let person: Person | undefined
const client = getClient()
const hierarchy = client.getHierarchy()
function canNavigateToDirect (location: Location, person: Person | undefined): boolean {
const app = location.path[2]
if (app !== chunterId && app !== notificationId) {
return false
}
if (person === undefined) {
return false
}
return hierarchy.hasMixin(person, contact.mixin.Employee) && (person as Employee).active
}
async function openEmployeeDirect (): Promise<void> {
if (person === undefined) return
const dm = await createDirect([person._id as Ref<Employee>])
if (dm === undefined) {
return
}
const loc = getCurrentLocation()
const [_id] = decodeObjectURI(loc.path[3]) ?? []
if (_id === dm) {
return
}
openChannel(dm, chunter.class.DirectMessage, undefined, true)
}
</script>
<EmployeePresenter
value={person}
shouldShowAvatar={false}
compact
onEmployeeEdit={canNavigateToDirect($location, person) ? openEmployeeDirect : undefined}
/>

View File

@ -0,0 +1,49 @@
<!--
// 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">
import { ModernButton, getCurrentLocation } from '@hcengineering/ui'
import view, { decodeObjectURI } from '@hcengineering/view'
import { Employee } from '@hcengineering/contact'
import chunter from '../plugin'
import { createDirect } from '../utils'
import { openChannelInSidebar } from '../navigation'
export let employee: Employee
async function openDirect (): Promise<void> {
const dm = await createDirect([employee._id])
if (dm === undefined) {
return
}
const loc = getCurrentLocation()
const [_id] = decodeObjectURI(loc.path[3]) ?? []
if (_id === dm) {
return
}
await openChannelInSidebar(dm, chunter.class.DirectMessage, undefined, undefined, true)
}
</script>
<ModernButton
label={chunter.string.Message}
icon={view.icon.Bubble}
size="small"
iconSize="small"
on:click={openDirect}
/>

View File

@ -60,6 +60,7 @@
flex-shrink: 0;
margin: 0.25rem 0;
height: 1.875rem;
font-size: 0.75rem;
&:not(:first-child)::after {
position: absolute;

View File

@ -102,7 +102,7 @@
<div class="antiHSpacer" />
{:else if secondaryNotifyMarker}
<div class="antiHSpacer" />
<NotifyMarker count={0} kind="secondary" size="x-small" />
<NotifyMarker count={0} kind="simple" />
<div class="antiHSpacer" />
{/if}
</svelte:fragment>

View File

@ -53,6 +53,8 @@ import ThreadViewPanel from './components/threads/ThreadViewPanel.svelte'
import ChatWidget from './components/ChatWidget.svelte'
import ChatWidgetTab from './components/ChatWidgetTab.svelte'
import WorkbenchTabExtension from './components/WorkbenchTabExtension.svelte'
import DirectMessageButton from './components/DirectMessageButton.svelte'
import EmployeePresenter from './components/ChunterEmployeePresenter.svelte'
import {
chunterSpaceLinkFragmentProvider,
@ -181,7 +183,9 @@ export default async (): Promise<Resources> => ({
JoinChannelNotificationPresenter,
ChatWidget,
ChatWidgetTab,
WorkbenchTabExtension
WorkbenchTabExtension,
DirectMessageButton,
EmployeePresenter
},
activity: {
ChannelCreatedMessage,

View File

@ -31,7 +31,12 @@ import { getChannelName, isThreadMessage } from './utils'
import chunter from './plugin'
import { threadMessagesStore } from './stores'
export function openChannel (_id: string, _class: Ref<Class<Doc>>, thread?: Ref<ActivityMessage>): void {
export function openChannel (
_id: string,
_class: Ref<Class<Doc>>,
thread?: Ref<ActivityMessage>,
forceApplication = false
): void {
const loc = getCurrentLocation()
const id = encodeObjectURI(_id, _class)
@ -39,6 +44,10 @@ export function openChannel (_id: string, _class: Ref<Class<Doc>>, thread?: Ref<
return
}
if (forceApplication) {
loc.path[2] = chunterId
}
loc.path[3] = id
loc.query = { ...loc.query, message: null }
@ -317,7 +326,12 @@ export async function closeThreadInSidebarChannel (widget: Widget, tab: ChatWidg
}, 100)
}
export async function openThreadInSidebar (_id: Ref<ActivityMessage>, msg?: ActivityMessage, doc?: Doc): Promise<void> {
export async function openThreadInSidebar (
_id: Ref<ActivityMessage>,
msg?: ActivityMessage,
doc?: Doc,
selectedMessageId?: Ref<ActivityMessage>
): Promise<void> {
const client = getClient()
const widget = client.getModel().findAllSync(workbench.class.Widget, { _id: chunter.ids.ChatWidget })[0]
@ -362,6 +376,7 @@ export async function openThreadInSidebar (_id: Ref<ActivityMessage>, msg?: Acti
_id: object?._id,
_class: object?._class,
thread: message._id,
selectedMessageId,
channelName: name
}
}
@ -436,7 +451,7 @@ export async function locationDataResolver (loc: Location): Promise<LocationData
const linkProviders = client.getModel().findAllSync(view.mixin.LinkIdProvider, {})
const _id: Ref<Doc> | undefined = await parseLinkId(linkProviders, id, _class)
const object = await client.findOne(_class, { _id })
const object = hierarchy.hasClass(_class) ? await client.findOne(_class, { _id }) : undefined
if (object === undefined) return { name: await translate(chunter.string.Chat, {}, get(languageStore)) }
const titleIntl = client.getHierarchy().getClass(object._class).label

View File

@ -251,7 +251,9 @@ export default plugin(chunterId, {
},
function: {
CanTranslateMessage: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>,
OpenThreadInSidebar: '' as Resource<(_id: Ref<ActivityMessage>, msg?: ActivityMessage, doc?: Doc) => Promise<void>>,
OpenThreadInSidebar: '' as Resource<
(_id: Ref<ActivityMessage>, msg?: ActivityMessage, doc?: Doc, selectedId?: Ref<ActivityMessage>) => Promise<void>
>,
OpenChannelInSidebar: '' as Resource<
(
_id: Ref<Doc>,

View File

@ -102,6 +102,7 @@
"People": "People",
"For": "For",
"SelectUsers": "Select users",
"AddGuest": "Add guest"
"AddGuest": "Add guest",
"ViewProfile": "View profile"
}
}

View File

@ -102,6 +102,7 @@
"People": "Personas",
"For": "Para",
"SelectUsers": "Seleccionar usuarios",
"AddGuest": "Añadir invitado"
"AddGuest": "Añadir invitado",
"ViewProfile": "Ver perfil"
}
}

View File

@ -102,6 +102,7 @@
"People": "Personnes",
"For": "Pour",
"SelectUsers": "Sélectionner des utilisateurs",
"AddGuest": "Ajouter un invité"
"AddGuest": "Ajouter un invité",
"ViewProfile": "Voir le profil"
}
}

View File

@ -102,6 +102,7 @@
"People": "Pessoas",
"For": "Para",
"SelectUsers": "Selecionar utilizadores",
"AddGuest": "Adicionar convidado"
"AddGuest": "Adicionar convidado",
"ViewProfile": "Ver perfil"
}
}

View File

@ -102,6 +102,7 @@
"People": "Люди",
"For": "Для",
"SelectUsers": "Выберите пользователей",
"AddGuest": "Добавить гостя"
"AddGuest": "Добавить гостя",
"ViewProfile": "Посмотреть профиль"
}
}

View File

@ -102,6 +102,7 @@
"People": "人员",
"For": "为",
"SelectUsers": "选择用户",
"AddGuest": "添加访客"
"AddGuest": "添加访客",
"ViewProfile": "查看资料"
}
}

View File

@ -2,12 +2,14 @@
import { Employee, Person } from '@hcengineering/contact'
import { Ref, WithLookup } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import ui, { IconSize } from '@hcengineering/ui'
import ui, { IconSize, LabelAndProps } from '@hcengineering/ui'
import { PersonLabelTooltip, employeeByIdStore, personByIdStore } from '..'
import PersonPresenter from '../components/PersonPresenter.svelte'
import contact from '../plugin'
import EmployeePreviewPopup from './EmployeePreviewPopup.svelte'
export let value: Ref<Person> | WithLookup<Person> | null | undefined
export let showPopup: boolean = true
export let tooltipLabels: PersonLabelTooltip | undefined = undefined
export let shouldShowAvatar: boolean = true
export let shouldShowName: boolean = true
@ -24,16 +26,26 @@
export let compact: boolean = false
export let showStatus: boolean = false
$: employeeValue = typeof value === 'string' ? $personByIdStore.get(value) : value
$: employeeValue = typeof value === 'string' ? ($personByIdStore.get(value) as Employee) : (value as Employee)
$: active =
employeeValue !== undefined ? $employeeByIdStore.get(employeeValue?._id as Ref<Employee>)?.active ?? false : false
$: active = employeeValue !== undefined ? $employeeByIdStore.get(employeeValue?._id)?.active ?? false : false
function getPreviewPopup (active: boolean, value: Employee | undefined): LabelAndProps | undefined {
if (!active || value === undefined || !showPopup) {
return undefined
}
return {
component: EmployeePreviewPopup,
props: { employeeId: value._id }
}
}
</script>
<PersonPresenter
value={employeeValue}
{tooltipLabels}
onEdit={onEmployeeEdit}
customTooltip={getPreviewPopup(active, employeeValue)}
{shouldShowAvatar}
{shouldShowName}
{avatarSize}

View File

@ -1,103 +1,150 @@
<script lang="ts">
import { Employee, PersonAccount, getName, Status } from '@hcengineering/contact'
import { getCurrentAccount, Ref } from '@hcengineering/core'
import { createQuery, getClient } from '@hcengineering/presentation'
import Avatar from './Avatar.svelte'
import { Button, Label, resizeObserver, showPopup } from '@hcengineering/ui'
import { DocNavLink } from '@hcengineering/view-resources'
import { Employee } from '@hcengineering/contact'
import { Class, Doc, Ref } from '@hcengineering/core'
import { ModernButton, navigate, resizeObserver } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import view from '@hcengineering/view'
import { getObjectLinkFragment } from '@hcengineering/view-resources'
import { ComponentExtensions, getClient } from '@hcengineering/presentation'
import contact from '../plugin'
import { employeeByIdStore } from '../utils'
import EmployeeSetStatusPopup from './EmployeeSetStatusPopup.svelte'
import EmployeeStatusPresenter from './EmployeeStatusPresenter.svelte'
import Edit from './icons/Edit.svelte'
import Avatar from './Avatar.svelte'
import { employeeByIdStore, personAccountByPersonId, statusByUserStore } from '../utils'
import { EmployeePresenter } from '../index'
export let employeeId: Ref<Employee>
const client = getClient()
const me = (getCurrentAccount() as PersonAccount).person
$: editable = employeeId === me
const statusesQuery = createQuery()
let status: Status | undefined = undefined
$: employee = $employeeByIdStore.get(employeeId) as Employee
statusesQuery.query(contact.class.Status, { attachedTo: employeeId }, (res) => {
status = res[0]
})
const hierarchy = client.getHierarchy()
const dispatch = createEventDispatcher()
function onEdit () {
showPopup(
EmployeeSetStatusPopup,
{
currentStatus: status
},
undefined,
() => {},
(newStatus: Status) => {
if (status && newStatus) {
client.updateDoc(contact.class.Status, status.space, status._id, { ...newStatus })
} else if (status && !newStatus) {
client.removeDoc(contact.class.Status, status.space, status._id)
} else {
client.addCollection(contact.class.Status, employee.space, employeeId, contact.mixin.Employee, 'statuses', {
name: newStatus.name,
dueDate: newStatus.dueDate
})
}
}
)
dispatch('close')
let employee: Employee | undefined = undefined
$: employee = $employeeByIdStore.get(employeeId)
$: accounts = $personAccountByPersonId.get(employeeId) ?? []
$: isOnline = accounts.some((account) => $statusByUserStore.get(account._id)?.online === true)
// const statusesQuery = createQuery()
// let editable = false
// let status: Status | undefined = undefined
// $: editable = employeeId === me
// statusesQuery.query(contact.class.Status, { attachedTo: employeeId }, (res) => {
// status = res[0]
// })
// function setStatus (): void {
// if (!employee) return
// showPopup(
// EmployeeSetStatusPopup,
// {
// currentStatus: status
// },
// undefined,
// () => {},
// async (newStatus: Status) => {
// if (status && newStatus) {
// await client.updateDoc(contact.class.Status, status.space, status._id, { ...newStatus })
// } else if (status && !newStatus) {
// await client.removeDoc(contact.class.Status, status.space, status._id)
// } else {
// await client.addCollection(contact.class.Status, employee.space, employeeId, contact.mixin.Employee, 'statuses', {
// name: newStatus.name,
// dueDate: newStatus.dueDate
// })
// }
// }
// )
// dispatch('close')
// }
async function viewProfile (): Promise<void> {
if (employee === undefined) return
const panelComponent = hierarchy.classHierarchyMixin(employee._class as Ref<Class<Doc>>, view.mixin.ObjectPanel)
const comp = panelComponent?.component ?? view.component.EditDoc
const loc = await getObjectLinkFragment(hierarchy, employee, {}, comp)
navigate(loc)
}
</script>
<div
class="antiPopup p-4 flex-col"
class="root flex-col"
use:resizeObserver={() => {
dispatch('changeContent')
}}
>
{#if employee}
<div class="flex-col-center pb-2">
<Avatar size={'x-large'} person={employee} name={employee.name} />
<div class="flex-presenter flex-gap-2 p-2">
<Avatar size="large" person={employee} name={employee.name} />
<span class="username">
<EmployeePresenter value={employee} shouldShowAvatar={false} showPopup={false} compact />
</span>
<span class="hulyAvatar-statusMarker small relative mt-0-5" class:online={isOnline} class:offline={!isOnline} />
</div>
<div class="pb-2">{getName(client.getHierarchy(), employee)}</div>
<DocNavLink object={employee}>
<Label label={contact.string.ViewFullProfile} />
</DocNavLink>
{#if status}
<div class="pb-2">
<Label label={contact.string.Status} />
<div class="flex-row-stretch statusContainer">
<div class="pr-2">
<EmployeeStatusPresenter {employee} withTooltip={false} />
</div>
{#if editable}
<div class="setStatusButton">
<Button icon={Edit} title={contact.string.SetStatus} on:click={onEdit} />
</div>
{/if}
</div>
</div>
{:else if editable}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div class="flex-row-stretch over-underline pb-2" on:click={onEdit}>
<Label label={contact.string.SetStatus} />
</div>
{/if}
<div class="separator" />
<div class="flex-presenter flex-gap-2 p-2">
<ComponentExtensions extension={contact.extension.EmployeePopupActions} props={{ employee }} />
<ModernButton
label={contact.string.ViewProfile}
icon={contact.icon.Person}
size="small"
iconSize="small"
on:click={viewProfile}
/>
</div>
<!--{#if status}-->
<!-- <div class="pb-2">-->
<!-- <Label label={contact.string.Status} />-->
<!-- <div class="flex-row-stretch statusContainer">-->
<!-- <div class="pr-2">-->
<!-- <EmployeeStatusPresenter {employee} withTooltip={false} />-->
<!-- </div>-->
<!-- {#if editable}-->
<!-- <div class="setStatusButton">-->
<!-- <Button icon={Edit} title={contact.string.SetStatus} on:click={setStatus} />-->
<!-- </div>-->
<!-- {/if}-->
<!-- </div>-->
<!-- </div>-->
<!--{:else if editable}-->
<!-- &lt;!&ndash; svelte-ignore a11y-click-events-have-key-events &ndash;&gt;-->
<!-- &lt;!&ndash; svelte-ignore a11y-no-static-element-interactions &ndash;&gt;-->
<!-- <div class="flex-row-stretch over-underline pb-2" on:click={setStatus}>-->
<!-- <Label label={contact.string.SetStatus} />-->
<!-- </div>-->
<!--{/if}-->
{/if}
</div>
<style lang="scss">
.statusContainer {
.setStatusButton {
opacity: 0;
}
&:hover .setStatusButton {
opacity: 1;
}
.root {
display: flex;
flex-direction: column;
width: auto;
min-height: 0;
min-width: 0;
max-width: 30rem;
background: var(--theme-popup-color);
user-select: none;
}
.separator {
height: 1px;
width: 100%;
background: var(--global-ui-BorderColor);
}
.username {
font-weight: 500;
}
//.statusContainer {
// .setStatusButton {
// opacity: 0;
// }
//
// &:hover .setStatusButton {
// opacity: 1;
// }
//}
</style>

View File

@ -34,6 +34,7 @@
export let defaultName: IntlString | undefined = ui.string.NotSelected
export let statusLabel: IntlString | undefined = undefined
export let tooltipLabels: PersonLabelTooltip | undefined = undefined
export let customTooltip: LabelAndProps | undefined = undefined
export let avatarSize: IconSize = 'x-small'
export let onEdit: ((event: MouseEvent) => void) | undefined = undefined
// export let element: HTMLElement | undefined = undefined
@ -81,7 +82,7 @@
{#if value || shouldShowPlaceholder}
<PersonContent
showTooltip={getTooltip(tooltipLabels, personValue)}
showTooltip={customTooltip ?? getTooltip(tooltipLabels, personValue)}
value={personValue}
{inline}
{onEdit}

View File

@ -37,6 +37,7 @@ import core, {
type Doc,
type DocumentQuery,
getCurrentAccount,
groupByArray,
type Hierarchy,
type IdMap,
matchQuery,
@ -310,6 +311,10 @@ export const personIdByAccountId = derived(personAccountByIdStore, (vals) => {
return new Map<Ref<PersonAccount>, Ref<Person>>(Array.from(vals.values()).map((it) => [it._id, it.person]))
})
export const personAccountByPersonId = derived(personAccountByIdStore, (vals) => {
return groupByArray(Array.from(vals.values()), (it) => it.person)
})
export const statusByUserStore = writable<Map<Ref<Account>, UserStatus>>(new Map())
export const personByIdStore = derived([personAccountPersonByIdStore, employeeByIdStore], (vals) => {

View File

@ -31,7 +31,7 @@ import {
import type { Asset, Metadata, Plugin, Resource } from '@hcengineering/platform'
import { IntlString, plugin } from '@hcengineering/platform'
import { TemplateField, TemplateFieldCategory } from '@hcengineering/templates'
import type { AnyComponent, ColorDefinition, ResolvedLocation, Location } from '@hcengineering/ui'
import type { AnyComponent, ColorDefinition, ResolvedLocation, Location, ComponentExtensionId } from '@hcengineering/ui'
import { Action, FilterMode, Viewlet } from '@hcengineering/view'
/**
@ -301,7 +301,8 @@ export const contactPlugin = plugin(contactId, {
Members: '' as IntlString,
Contacts: '' as IntlString,
Employees: '' as IntlString,
Persons: '' as IntlString
Persons: '' as IntlString,
ViewProfile: '' as IntlString
},
viewlet: {
TableMember: '' as Ref<Viewlet>,
@ -332,6 +333,9 @@ export const contactPlugin = plugin(contactId, {
},
ids: {
MentionCommonNotificationType: '' as Ref<Doc>
},
extension: {
EmployeePopupActions: '' as ComponentExtensionId
}
})

View File

@ -127,7 +127,8 @@ function getDocumentLinkId (doc: Document): string {
return `${slug}---${doc._id}`
}
function parseDocumentId (shortLink: string): Ref<ControlledDocument> | undefined {
function parseDocumentId (shortLink?: string): Ref<ControlledDocument> | undefined {
if (shortLink === undefined) return undefined
const parts = shortLink.split('---')
if (parts.length > 1) {
return parts[parts.length - 1] as Ref<ControlledDocument>

View File

@ -361,7 +361,8 @@
display: flex;
flex-direction: column;
align-items: stretch;
width: 15rem;
width: 100%;
max-width: 100%;
user-select: none;
&:not(.isDock) {

View File

@ -20,12 +20,12 @@
import love from '../plugin'
import VideoPopup from './VideoPopup.svelte'
export let widgetState: WidgetState
export let widgetState: WidgetState | undefined
let room: Ref<TypeRoom> | undefined = undefined
$: room = widgetState.data?.room
$: room = widgetState?.data?.room
$: if (widgetState.data?.room === undefined) {
$: if (widgetState?.data?.room === undefined) {
closeWidget(love.ids.VideoWidget)
}
</script>

View File

@ -14,7 +14,7 @@
-->
<script lang="ts">
export let count: number = 0
export let kind: 'primary' | 'secondary' | 'simple' = 'primary'
export let kind: 'primary' | 'simple' = 'primary'
export let size: 'xx-small' | 'x-small' | 'small' | 'medium' = 'small'
const maxNumber = 9
@ -30,10 +30,6 @@
</div>
{/if}
{#if kind === 'secondary'}
<div class="notifyMarker {size} {kind}" />
{/if}
{#if kind === 'simple'}
<div class="notifyMarker {size} {kind}" />
{/if}
@ -53,10 +49,6 @@
color: var(--global-on-accent-TextColor);
}
&.secondary {
background-color: var(--global-subtle-BackgroundColor);
}
&.xx-small {
width: 0.5rem;
height: 0.5rem;

View File

@ -192,7 +192,7 @@
if (thread !== undefined) {
const fn = await getResource(chunter.function.OpenThreadInSidebar)
void fn(thread)
void fn(thread, undefined, undefined, selectedMessageId)
}
if (selectedMessageId !== undefined) {

View File

@ -107,6 +107,7 @@
bind:this={list}
bind:selection={listSelection}
count={displayData.length}
items={displayData}
highlightIndex={displayData.findIndex(([context]) => context === selectedContext)}
noScroll
minHeight="5.625rem"

View File

@ -33,9 +33,7 @@
preferences = res
})
$: widgetId = $sidebarStore.widget
$: widget = widgets.find((it) => it._id === widgetId)
$: size = $sidebarStore.variant === SidebarVariant.MINI ? 'mini' : widget?.size
$: size = $sidebarStore.variant === SidebarVariant.MINI ? 'mini' : undefined
function txListener (tx: Tx): void {
if (tx._class === workbench.class.TxSidebarEvent) {
@ -79,17 +77,5 @@
min-width: 3.5rem !important;
max-width: 3.5rem !important;
}
&.size-small {
width: 10rem !important;
min-width: 10rem !important;
max-width: 10rem !important;
}
&.size-medium {
width: 20rem !important;
min-width: 20rem !important;
max-width: 20rem !important;
}
}
</style>

View File

@ -71,7 +71,6 @@ export interface Widget extends Doc {
label: IntlString
icon: Asset
type: WidgetType
size?: 'small' | 'medium'
component: AnyComponent
tabComponent?: AnyComponent

View File

@ -76,9 +76,7 @@ export class DocumentsPage extends CalendarPage {
async changeSpaceInCreateDocumentForm (space: string): Promise<void> {
await this.changeSpaceButton.click()
await this.page
.locator(`div.list-container.flex-col.flex-grow.svelte-15na0wa >> text=${space}`)
.click({ force: true })
await this.page.locator(`div.selectPopup >> div.list-container >> text=${space}`).click({ force: true })
}
async createTemplate (title: string, description: string, category: string, spaceName: string): Promise<void> {