mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 21:50:34 +03:00
UBERF-6068 Show collaborators in collaborative editors (#5335)
Signed-off-by: Alexander Onnikov <Alexander.Onnikov@xored.com>
This commit is contained in:
parent
07ff2a9d3c
commit
2220d7e19e
@ -20650,7 +20650,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/panel.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-P4H97igqWddz9UBNpLY742cNaADue6UfT+1y00SNB/MmJE7qop/lttaCMAvFXgZJ4HvlN7d18II/AHpMZAnABg==, tarball: file:projects/panel.tgz}
|
||||
resolution: {integrity: sha512-cFoTjnZ6mHJtNSppmfDuDx2wE5B4xGLxee96kAfHWSufWMYuHF69977P0VSlDEen1iXSNu2jQAWUux7wiEaKQQ==, tarball: file:projects/panel.tgz}
|
||||
id: file:projects/panel.tgz
|
||||
name: '@rush-temp/panel'
|
||||
version: 0.0.0
|
||||
|
@ -39,7 +39,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/ui": "^0.6.11",
|
||||
"@hcengineering/text-editor": "^0.6.0",
|
||||
"svelte": "^4.2.12",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
|
@ -41,7 +41,6 @@
|
||||
"@hcengineering/presentation": "^0.6.2",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/contact": "^0.6.20",
|
||||
"@hcengineering/ui": "^0.6.11",
|
||||
"@hcengineering/view": "^0.6.9",
|
||||
"@hcengineering/text": "^0.6.1",
|
||||
|
@ -15,7 +15,7 @@
|
||||
//
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact from '@hcengineering/contact'
|
||||
import core from '@hcengineering/core'
|
||||
import { Component } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { CollaborationUser } from '../types'
|
||||
@ -27,7 +27,7 @@
|
||||
is={view.component.ObjectPresenter}
|
||||
props={{
|
||||
objectId: user.id,
|
||||
_class: contact.class.PersonAccount,
|
||||
_class: core.class.Account,
|
||||
shouldShowAvatar: true,
|
||||
shouldShowName: true
|
||||
}}
|
||||
|
@ -0,0 +1,97 @@
|
||||
<!--
|
||||
//
|
||||
// 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 { AnySvelteComponent, Button, DelayedCaller } from '@hcengineering/ui'
|
||||
import { onMount } from 'svelte'
|
||||
import { Editor } from '@tiptap/core'
|
||||
import { createRelativePositionFromJSON } from 'yjs'
|
||||
import { relativePositionToAbsolutePosition, ySyncPluginKey } from 'y-prosemirror'
|
||||
|
||||
import { TiptapCollabProvider } from '../provider/tiptap'
|
||||
import { AwarenessChangeEvent, CollaborationUserState } from '../types'
|
||||
|
||||
export let provider: TiptapCollabProvider
|
||||
export let editor: Editor
|
||||
export let component: AnySvelteComponent
|
||||
|
||||
let states: CollaborationUserState[] = []
|
||||
|
||||
const debounce = new DelayedCaller(100)
|
||||
const onAwarenessChange = (event: AwarenessChangeEvent): void => {
|
||||
debounce.call(() => {
|
||||
states = event.states.filter((p) => p.user != null).filter((p) => p.clientId !== provider.awareness?.clientID)
|
||||
})
|
||||
}
|
||||
|
||||
function goToCursor (state: CollaborationUserState): void {
|
||||
const cursor = state.cursor
|
||||
if (cursor?.head != null) {
|
||||
try {
|
||||
const ystate = ySyncPluginKey.getState(editor.state)
|
||||
const abs = relativePositionToAbsolutePosition(
|
||||
ystate.doc,
|
||||
ystate.type,
|
||||
createRelativePositionFromJSON(cursor.head),
|
||||
ystate.binding.mapping
|
||||
)
|
||||
if (abs != null) {
|
||||
editor.commands.focus(abs, { scrollIntoView: true })
|
||||
}
|
||||
} catch (err) {
|
||||
// relative to absolute position conversion sometimes fails
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
provider.on('awarenessUpdate', onAwarenessChange)
|
||||
return () => provider.off('awarenessUpdate', onAwarenessChange)
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if states.length > 0}
|
||||
<div class="container flex-col flex-gap-2 pt-2">
|
||||
{#each states as state}
|
||||
<Button
|
||||
kind="icon"
|
||||
shape="round-small"
|
||||
padding="0"
|
||||
size="x-small"
|
||||
noFocus
|
||||
on:click={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
goToCursor(state)
|
||||
}}
|
||||
>
|
||||
<svelte:fragment slot="icon">
|
||||
<svelte:component this={component} user={state.user} lastUpdate={state.lastUpdate ?? 0} size={'x-small'} />
|
||||
</svelte:fragment>
|
||||
</Button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
width: 1.5rem;
|
||||
}
|
||||
</style>
|
@ -16,12 +16,12 @@
|
||||
import core, { CollaborativeDoc, Doc, getCollaborativeDoc, getCollaborativeDocId } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { KeyedAttribute, getAttribute, getClient } from '@hcengineering/presentation'
|
||||
import { registerFocus } from '@hcengineering/ui'
|
||||
import { AnySvelteComponent, registerFocus } from '@hcengineering/ui'
|
||||
import CollaborativeTextEditor from './CollaborativeTextEditor.svelte'
|
||||
import { FocusExtension } from './extension/focus'
|
||||
import { type FileAttachFunction } from './extension/types'
|
||||
import textEditorPlugin from '../plugin'
|
||||
import { RefAction, TextNodeAction } from '../types'
|
||||
import { CollaborationUser, RefAction, TextNodeAction } from '../types'
|
||||
|
||||
export let object: Doc
|
||||
export let key: KeyedAttribute
|
||||
@ -29,6 +29,9 @@
|
||||
export let textNodeActions: TextNodeAction[] = []
|
||||
export let refActions: RefAction[] = []
|
||||
|
||||
export let user: CollaborationUser
|
||||
export let userComponent: AnySvelteComponent | undefined = undefined
|
||||
|
||||
export let placeholder: IntlString = textEditorPlugin.string.EditorPlaceholder
|
||||
export let attachFile: FileAttachFunction | undefined = undefined
|
||||
export let boundary: HTMLElement | undefined = undefined
|
||||
@ -105,6 +108,8 @@
|
||||
objectClass={object._class}
|
||||
objectId={object._id}
|
||||
objectAttr={key.key}
|
||||
{user}
|
||||
{userComponent}
|
||||
{textNodeActions}
|
||||
{refActions}
|
||||
{extensions}
|
||||
|
@ -19,12 +19,16 @@
|
||||
import { Label, Icon } from '@hcengineering/ui'
|
||||
import type { AnySvelteComponent } from '@hcengineering/ui'
|
||||
import textEditorPlugin from '../plugin'
|
||||
import { CollaborationUser } from '../types'
|
||||
import CollaborativeAttributeBox from './CollaborativeAttributeBox.svelte'
|
||||
import IconDescription from './icons/Description.svelte'
|
||||
|
||||
export let object: Doc
|
||||
export let key: KeyedAttribute
|
||||
|
||||
export let user: CollaborationUser
|
||||
export let userComponent: AnySvelteComponent | undefined = undefined
|
||||
|
||||
export let label: IntlString = textEditorPlugin.string.FullDescription
|
||||
export let icon: Asset | AnySvelteComponent = IconDescription
|
||||
|
||||
@ -40,5 +44,14 @@
|
||||
<Label {label} />
|
||||
</span>
|
||||
</div>
|
||||
<CollaborativeAttributeBox {object} {key} boundary={element?.parentElement ?? undefined} on:focus on:blur on:update />
|
||||
<CollaborativeAttributeBox
|
||||
{object}
|
||||
{key}
|
||||
{user}
|
||||
{userComponent}
|
||||
boundary={element?.parentElement ?? undefined}
|
||||
on:focus
|
||||
on:blur
|
||||
on:update
|
||||
/>
|
||||
</div>
|
||||
|
@ -20,7 +20,7 @@
|
||||
import { IntlString, getMetadata, translate } from '@hcengineering/platform'
|
||||
import presentation from '@hcengineering/presentation'
|
||||
import { markupToJSON } from '@hcengineering/text'
|
||||
import { Button, IconSize, Loading, themeStore } from '@hcengineering/ui'
|
||||
import { AnySvelteComponent, Button, IconSize, Loading, ThrottledCaller, themeStore } from '@hcengineering/ui'
|
||||
import { AnyExtension, Editor, FocusPosition, mergeAttributes } from '@tiptap/core'
|
||||
import Collaboration, { isChangeOrigin } from '@tiptap/extension-collaboration'
|
||||
import CollaborationCursor from '@tiptap/extension-collaboration-cursor'
|
||||
@ -38,14 +38,15 @@
|
||||
import { formatCollaborativeDocumentId, formatPlatformDocumentId } from '../provider/utils'
|
||||
import {
|
||||
CollaborationIds,
|
||||
CollaborationUser,
|
||||
RefAction,
|
||||
TextEditorCommandHandler,
|
||||
TextEditorHandler,
|
||||
TextFormatCategory,
|
||||
TextNodeAction
|
||||
} from '../types'
|
||||
import { getCollaborationUser } from '../utils'
|
||||
|
||||
import CollaborationUsers from './CollaborationUsers.svelte'
|
||||
import ImageStyleToolbar from './ImageStyleToolbar.svelte'
|
||||
import TextEditorStyleToolbar from './TextEditorStyleToolbar.svelte'
|
||||
import { noSelectionRender, renderCursor } from './editor/collaboration'
|
||||
@ -66,6 +67,9 @@
|
||||
export let objectId: Ref<Doc> | undefined
|
||||
export let objectAttr: string | undefined
|
||||
|
||||
export let user: CollaborationUser
|
||||
export let userComponent: AnySvelteComponent | undefined = undefined
|
||||
|
||||
export let readonly = false
|
||||
|
||||
export let buttonSize: IconSize = 'small'
|
||||
@ -256,9 +260,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
const throttle = new ThrottledCaller(100)
|
||||
const updateLastUpdateTime = (): void => {
|
||||
remoteProvider.awareness?.setLocalStateField('lastUpdate', Date.now())
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
await ph
|
||||
const user = await getCollaborationUser()
|
||||
|
||||
editor = new Editor({
|
||||
element,
|
||||
@ -326,6 +334,7 @@
|
||||
// ignore non-local changes
|
||||
if (isChangeOrigin(transaction)) return
|
||||
|
||||
throttle.call(updateLastUpdateTime)
|
||||
dispatch('update')
|
||||
}
|
||||
})
|
||||
@ -384,6 +393,9 @@
|
||||
|
||||
<div class="textInput">
|
||||
<div class="select-text" class:hidden={loading} style="width: 100%;" bind:this={element} />
|
||||
{#if remoteProvider && editor && userComponent}
|
||||
<CollaborationUsers provider={remoteProvider} {editor} component={userComponent} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if refActions.length > 0}
|
||||
@ -430,7 +442,7 @@
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
align-items: flex-start;
|
||||
min-height: 1.25rem;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
@ -17,12 +17,12 @@
|
||||
<script lang="ts">
|
||||
import { type Class, type CollaborativeDoc, type Doc, type Ref } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { IconSize, registerFocus } from '@hcengineering/ui'
|
||||
import { AnySvelteComponent, IconSize, registerFocus } from '@hcengineering/ui'
|
||||
import { AnyExtension, Editor, FocusPosition, getMarkRange } from '@tiptap/core'
|
||||
import { TextSelection } from '@tiptap/pm/state'
|
||||
|
||||
import textEditorPlugin from '../plugin'
|
||||
import { TextEditorCommandHandler, TextFormatCategory, TextNodeAction } from '../types'
|
||||
import { CollaborationUser, TextEditorCommandHandler, TextFormatCategory, TextNodeAction } from '../types'
|
||||
|
||||
import CollaborativeTextEditor from './CollaborativeTextEditor.svelte'
|
||||
import { FileAttachFunction } from './extension/types'
|
||||
@ -36,6 +36,9 @@
|
||||
export let objectId: Ref<Doc> | undefined = undefined
|
||||
export let objectAttr: string | undefined = undefined
|
||||
|
||||
export let user: CollaborationUser
|
||||
export let userComponent: AnySvelteComponent | undefined = undefined
|
||||
|
||||
export let readonly = false
|
||||
|
||||
export let buttonSize: IconSize = 'small'
|
||||
@ -162,6 +165,8 @@
|
||||
{objectClass}
|
||||
{objectId}
|
||||
{objectAttr}
|
||||
{user}
|
||||
{userComponent}
|
||||
{readonly}
|
||||
{buttonSize}
|
||||
{placeholder}
|
||||
|
@ -14,15 +14,17 @@
|
||||
//
|
||||
|
||||
import { type DecorationAttrs } from '@tiptap/pm/view'
|
||||
import { showTooltip } from '@hcengineering/ui'
|
||||
import { getPlatformColor, showTooltip } from '@hcengineering/ui'
|
||||
import { type CollaborationUser } from '../../types'
|
||||
import CollaborationUserPopup from '../CollaborationUserPopup.svelte'
|
||||
|
||||
export const renderCursor = (user: CollaborationUser): HTMLElement => {
|
||||
const color = getPlatformColor(user.color, false)
|
||||
|
||||
const cursor = document.createElement('span')
|
||||
|
||||
cursor.classList.add('collaboration-cursor__caret')
|
||||
cursor.setAttribute('style', `border-color: ${user.color}`)
|
||||
cursor.setAttribute('style', `border-color: ${color}`)
|
||||
|
||||
cursor.addEventListener('mousemove', () => {
|
||||
showTooltip(undefined, cursor, 'top', CollaborationUserPopup, { user })
|
||||
|
@ -55,8 +55,8 @@ export class TiptapCollabProvider extends HocuspocusProvider {
|
||||
}
|
||||
|
||||
destroy (): void {
|
||||
this.configuration.websocketProvider.disconnect()
|
||||
super.destroy()
|
||||
this.configuration.websocketProvider.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ import { type Asset, type IntlString, type Resource } from '@hcengineering/platf
|
||||
import { type Account, type Doc, type Markup, type Ref } from '@hcengineering/core'
|
||||
import type { AnySvelteComponent } from '@hcengineering/ui'
|
||||
import { type Editor, type SingleCommands } from '@tiptap/core'
|
||||
import { type RelativePosition } from 'yjs'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -97,12 +98,26 @@ export interface TextEditorCommandHandler {
|
||||
chain: (...commands: TextEditorCommand[]) => boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
/** @public */
|
||||
export interface CollaborationUser {
|
||||
id: Ref<Account>
|
||||
name: string
|
||||
email: string
|
||||
color: string
|
||||
color: number
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface CollaborationUserState {
|
||||
clientId: number
|
||||
user: CollaborationUser
|
||||
cursor?: {
|
||||
anchor: RelativePosition
|
||||
head: RelativePosition
|
||||
} | null
|
||||
lastUpdate?: number
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface AwarenessChangeEvent {
|
||||
states: CollaborationUserState[]
|
||||
}
|
||||
|
@ -14,19 +14,6 @@
|
||||
//
|
||||
|
||||
import { type Attribute } from '@tiptap/core'
|
||||
import { get } from 'svelte/store'
|
||||
|
||||
import contact, { type PersonAccount, formatName, AvatarType } from '@hcengineering/contact'
|
||||
import { getCurrentAccount } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
type ColorDefinition,
|
||||
getPlatformAvatarColorByName,
|
||||
getPlatformAvatarColorForTextDef,
|
||||
themeStore
|
||||
} from '@hcengineering/ui'
|
||||
|
||||
import { type CollaborationUser } from './types'
|
||||
|
||||
export function getDataAttribute (
|
||||
name: string,
|
||||
@ -50,27 +37,3 @@ export function getDataAttribute (
|
||||
...(options ?? {})
|
||||
}
|
||||
}
|
||||
|
||||
function getAvatarColor (name: string, avatar: string, darkTheme: boolean): ColorDefinition {
|
||||
const [type, color] = avatar.split('://')
|
||||
if (type === AvatarType.COLOR) {
|
||||
return getPlatformAvatarColorByName(color, darkTheme)
|
||||
}
|
||||
return getPlatformAvatarColorForTextDef(name, darkTheme)
|
||||
}
|
||||
|
||||
export async function getCollaborationUser (): Promise<CollaborationUser> {
|
||||
const client = getClient()
|
||||
|
||||
const me = getCurrentAccount() as PersonAccount
|
||||
const person = await client.findOne(contact.class.Person, { _id: me.person })
|
||||
const name = person !== undefined ? formatName(person.name) : me.email
|
||||
const color = getAvatarColor(name, person?.avatar ?? '', get(themeStore).dark)
|
||||
|
||||
return {
|
||||
id: me._id,
|
||||
name,
|
||||
email: me.email,
|
||||
color: color.icon ?? 'var(--theme-button-default)'
|
||||
}
|
||||
}
|
||||
|
@ -267,6 +267,22 @@ export class DelayedCaller {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export class ThrottledCaller {
|
||||
timeout?: any
|
||||
constructor (readonly delay: number = 10) {}
|
||||
call (op: () => void): void {
|
||||
if (this.timeout === undefined) {
|
||||
op()
|
||||
this.timeout = setTimeout(() => {
|
||||
this.timeout = undefined
|
||||
}, this.delay)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const testing = (localStorage.getItem('#platform.testing.enabled') ?? 'false') === 'true'
|
||||
|
||||
export const rootBarExtensions = writable<Array<['left' | 'right', AnyComponent]>>([])
|
||||
|
@ -14,13 +14,14 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||
import contact from '@hcengineering/contact'
|
||||
import { Account, Doc, Ref, generateId } from '@hcengineering/core'
|
||||
import { IntlString, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import { IntlString, getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import { KeyedAttribute, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import textEditor, { AttachIcon, CollaborativeAttributeBox, RefAction } from '@hcengineering/text-editor'
|
||||
import { navigate } from '@hcengineering/ui'
|
||||
import { AnySvelteComponent, navigate } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { getObjectLinkFragment } from '@hcengineering/view-resources'
|
||||
import { getCollaborationUser, getObjectLinkFragment } from '@hcengineering/view-resources'
|
||||
import AttachmentsGrid from './AttachmentsGrid.svelte'
|
||||
import { uploadFile } from '../utils'
|
||||
import { defaultRefActions, getModelRefActions } from '@hcengineering/text-editor/src/components/editor/actions'
|
||||
@ -38,6 +39,12 @@
|
||||
|
||||
const client = getClient()
|
||||
|
||||
const user = getCollaborationUser()
|
||||
let userComponent: AnySvelteComponent | undefined
|
||||
void getResource(contact.component.CollaborationUserAvatar).then((component) => {
|
||||
userComponent = component
|
||||
})
|
||||
|
||||
let editor: CollaborativeAttributeBox
|
||||
|
||||
let refActions: RefAction[] = []
|
||||
@ -235,6 +242,8 @@
|
||||
bind:this={editor}
|
||||
{object}
|
||||
{key}
|
||||
{user}
|
||||
{userComponent}
|
||||
{focusIndex}
|
||||
{placeholder}
|
||||
{boundary}
|
||||
|
@ -54,6 +54,7 @@
|
||||
"@hcengineering/attachment": "^0.6.9",
|
||||
"@hcengineering/login": "^0.6.8",
|
||||
"@hcengineering/templates": "^0.6.7",
|
||||
"@hcengineering/image-cropper": "^0.6.0"
|
||||
"@hcengineering/image-cropper": "^0.6.0",
|
||||
"@hcengineering/text-editor": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,7 @@
|
||||
timer = null
|
||||
update = undefined
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
timer = setTimeout(() => update?.(), 500)
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,43 @@
|
||||
<!--
|
||||
//
|
||||
// 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 { Employee, PersonAccount } from '@hcengineering/contact'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { CollaborationUser } from '@hcengineering/text-editor'
|
||||
import { IconSize } from '@hcengineering/ui'
|
||||
|
||||
import { employeeByIdStore, personAccountByIdStore, personByIdStore } from '../utils'
|
||||
import Avatar from './Avatar.svelte'
|
||||
|
||||
export let user: CollaborationUser
|
||||
export let lastUpdate: number
|
||||
export let size: IconSize = 'x-small'
|
||||
|
||||
let avatar: Avatar | undefined
|
||||
$: lastUpdate !== 0 && avatar?.pulse()
|
||||
|
||||
$: personAccount = $personAccountByIdStore.get(user.id as Ref<PersonAccount>)
|
||||
$: person =
|
||||
personAccount?.person !== undefined
|
||||
? $employeeByIdStore.get(personAccount.person as Ref<Employee>) ?? $personByIdStore.get(personAccount.person)
|
||||
: undefined
|
||||
</script>
|
||||
|
||||
{#if person}
|
||||
<Avatar bind:this={avatar} {size} avatar={person.avatar} name={person.name} borderColor={user.color} />
|
||||
{/if}
|
@ -57,6 +57,7 @@ import ContactsTabs from './components/ContactsTabs.svelte'
|
||||
import CreateEmployee from './components/CreateEmployee.svelte'
|
||||
import CreateOrganization from './components/CreateOrganization.svelte'
|
||||
import CreatePerson from './components/CreatePerson.svelte'
|
||||
import CollaborationUserAvatar from './components/CollaborationUserAvatar.svelte'
|
||||
import DeleteConfirmationPopup from './components/DeleteConfirmationPopup.svelte'
|
||||
import EditEmployee from './components/EditEmployee.svelte'
|
||||
import EditMember from './components/EditMember.svelte'
|
||||
@ -303,6 +304,7 @@ export default async (): Promise<Resources> => ({
|
||||
OrganizationPresenter,
|
||||
ChannelsPresenter,
|
||||
CreatePerson,
|
||||
CollaborationUserAvatar,
|
||||
CreateOrganization,
|
||||
EditPerson,
|
||||
EditEmployee,
|
||||
|
@ -198,7 +198,8 @@ export const contactPlugin = plugin(contactId, {
|
||||
DeleteConfirmationPopup: '' as AnyComponent,
|
||||
AccountArrayEditor: '' as AnyComponent,
|
||||
PersonIcon: '' as AnyComponent,
|
||||
EditOrganizationPanel: '' as AnyComponent
|
||||
EditOrganizationPanel: '' as AnyComponent,
|
||||
CollaborationUserAvatar: '' as AnyComponent
|
||||
},
|
||||
channelProvider: {
|
||||
Email: '' as Ref<ChannelProvider>,
|
||||
|
@ -15,8 +15,9 @@
|
||||
//
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Extensions, FocusPosition } from '@tiptap/core'
|
||||
import contact from '@hcengineering/contact'
|
||||
import { Document } from '@hcengineering/document'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import {
|
||||
CollaboratorEditor,
|
||||
HeadingsExtension,
|
||||
@ -25,6 +26,9 @@
|
||||
TodoItemExtension,
|
||||
TodoListExtension
|
||||
} from '@hcengineering/text-editor'
|
||||
import { AnySvelteComponent } from '@hcengineering/ui'
|
||||
import { getCollaborationUser } from '@hcengineering/view-resources'
|
||||
import { Extensions, FocusPosition } from '@tiptap/core'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import ToDoItemNodeView from './node-view/ToDoItemNodeView.svelte'
|
||||
@ -38,6 +42,12 @@
|
||||
export let overflow: 'auto' | 'none' = 'none'
|
||||
export let editorAttributes: Record<string, string> = {}
|
||||
|
||||
const user = getCollaborationUser()
|
||||
let userComponent: AnySvelteComponent | undefined
|
||||
void getResource(contact.component.CollaborationUserAvatar).then((component) => {
|
||||
userComponent = component
|
||||
})
|
||||
|
||||
let collabEditor: CollaboratorEditor
|
||||
|
||||
export function focus (position?: FocusPosition): void {
|
||||
@ -83,6 +93,8 @@
|
||||
objectClass={object._class}
|
||||
objectId={object._id}
|
||||
objectAttr="content"
|
||||
{user}
|
||||
{userComponent}
|
||||
{focusIndex}
|
||||
{readonly}
|
||||
{attachFile}
|
||||
|
@ -13,17 +13,26 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact from '@hcengineering/contact'
|
||||
import { Doc } from '@hcengineering/core'
|
||||
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { KeyedAttribute } from '@hcengineering/presentation'
|
||||
import { CollaborativeAttributeSectionBox } from '@hcengineering/text-editor'
|
||||
import { AnySvelteComponent } from '@hcengineering/ui'
|
||||
import { getCollaborationUser } from '../utils'
|
||||
|
||||
export let object: Doc
|
||||
export let key: KeyedAttribute
|
||||
|
||||
const user = getCollaborationUser()
|
||||
let userComponent: AnySvelteComponent | undefined
|
||||
void getResource(contact.component.CollaborationUserAvatar).then((component) => {
|
||||
userComponent = component
|
||||
})
|
||||
</script>
|
||||
|
||||
{#key object._id}
|
||||
{#key key.key}
|
||||
<CollaborativeAttributeSectionBox {object} {key} label={key.attr.label} />
|
||||
<CollaborativeAttributeSectionBox {object} {key} {user} {userComponent} label={key.attr.label} />
|
||||
{/key}
|
||||
{/key}
|
||||
|
@ -13,15 +13,24 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact from '@hcengineering/contact'
|
||||
import { Doc } from '@hcengineering/core'
|
||||
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { KeyedAttribute } from '@hcengineering/presentation'
|
||||
import { CollaborativeAttributeSectionBox } from '@hcengineering/text-editor'
|
||||
import { AnySvelteComponent } from '@hcengineering/ui'
|
||||
import { getCollaborationUser } from '../utils'
|
||||
|
||||
export let object: Doc
|
||||
export let key: KeyedAttribute
|
||||
|
||||
const user = getCollaborationUser()
|
||||
let userComponent: AnySvelteComponent | undefined
|
||||
void getResource(contact.component.CollaborationUserAvatar).then((component) => {
|
||||
userComponent = component
|
||||
})
|
||||
</script>
|
||||
|
||||
{#key object._id}
|
||||
<CollaborativeAttributeSectionBox {object} {key} label={key.attr.label} />
|
||||
<CollaborativeAttributeSectionBox {object} {key} {user} {userComponent} label={key.attr.label} />
|
||||
{/key}
|
||||
|
@ -1,6 +1,6 @@
|
||||
//
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 Hardcore Engineering Inc.
|
||||
// Copyright © 2021, 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
|
||||
@ -65,8 +65,10 @@ import {
|
||||
isAdminUser,
|
||||
createQuery
|
||||
} from '@hcengineering/presentation'
|
||||
import { type CollaborationUser } from '@hcengineering/text-editor'
|
||||
import {
|
||||
ErrorPresenter,
|
||||
getColorNumberByText,
|
||||
getCurrentResolvedLocation,
|
||||
getPanelURI,
|
||||
getPlatformColorForText,
|
||||
@ -1402,3 +1404,15 @@ permissionsQuery.query(core.class.Space, {}, (res) => {
|
||||
whitelist: whitelistedSpaces
|
||||
})
|
||||
})
|
||||
|
||||
export function getCollaborationUser (): CollaborationUser {
|
||||
const me = getCurrentAccount() as PersonAccount
|
||||
const color = getColorNumberByText(me.email)
|
||||
|
||||
return {
|
||||
id: me._id,
|
||||
name: me.email,
|
||||
email: me.email,
|
||||
color
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user