mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-07 00:12:50 +00:00
Merge remote-tracking branch 'origin/develop' into staging
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
commit
f3246b00bc
@ -1110,7 +1110,6 @@ export function devTool (
|
||||
.action(
|
||||
async (cmd: { workspace: string, move: string, blobLimit: string, concurrency: string, disabled: boolean }) => {
|
||||
const params = {
|
||||
blobSizeLimitMb: parseInt(cmd.blobLimit),
|
||||
concurrency: parseInt(cmd.concurrency),
|
||||
move: cmd.move === 'true'
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ import { type Db } from 'mongodb'
|
||||
import { PassThrough } from 'stream'
|
||||
|
||||
export interface MoveFilesParams {
|
||||
blobSizeLimitMb: number
|
||||
concurrency: number
|
||||
move: boolean
|
||||
}
|
||||
@ -86,7 +85,7 @@ export async function moveFiles (
|
||||
for (const [name, adapter] of exAdapter.adapters.entries()) {
|
||||
if (name === exAdapter.defaultAdapter) continue
|
||||
|
||||
console.log('moving from', name, 'limit', params.blobSizeLimitMb, 'concurrency', params.concurrency)
|
||||
console.log('moving from', name, 'limit', 'concurrency', params.concurrency)
|
||||
|
||||
// we attempt retry the whole process in case of failure
|
||||
// files that were already moved will be skipped
|
||||
@ -129,10 +128,13 @@ async function processAdapter (
|
||||
workspaceId: WorkspaceId,
|
||||
params: MoveFilesParams
|
||||
): Promise<void> {
|
||||
if (source === target) {
|
||||
// Just in case
|
||||
return
|
||||
}
|
||||
let time = Date.now()
|
||||
let processedCnt = 0
|
||||
let processedBytes = 0
|
||||
let skippedCnt = 0
|
||||
let movedCnt = 0
|
||||
let movedBytes = 0
|
||||
let batchBytes = 0
|
||||
@ -140,47 +142,62 @@ async function processAdapter (
|
||||
const rateLimiter = new RateLimiter(params.concurrency)
|
||||
|
||||
const iterator = await source.listStream(ctx, workspaceId)
|
||||
|
||||
const toRemove: string[] = []
|
||||
try {
|
||||
while (true) {
|
||||
const dataBulk = await iterator.next()
|
||||
if (dataBulk.length === 0) break
|
||||
|
||||
for (const data of dataBulk) {
|
||||
const blob =
|
||||
(await exAdapter.stat(ctx, workspaceId, data._id)) ?? (await source.stat(ctx, workspaceId, data._id))
|
||||
let targetBlob = await target.stat(ctx, workspaceId, data._id)
|
||||
const sourceBlob = await source.stat(ctx, workspaceId, data._id)
|
||||
|
||||
if (blob === undefined) {
|
||||
if (sourceBlob === undefined) {
|
||||
console.error('blob not found', data._id)
|
||||
continue
|
||||
}
|
||||
if (targetBlob !== undefined) {
|
||||
console.log('Target blob already exists', targetBlob._id, targetBlob.contentType)
|
||||
}
|
||||
|
||||
if (blob.provider !== exAdapter.defaultAdapter) {
|
||||
if (blob.size <= params.blobSizeLimitMb * 1024 * 1024) {
|
||||
await rateLimiter.exec(async () => {
|
||||
try {
|
||||
await retryOnFailure(
|
||||
ctx,
|
||||
5,
|
||||
async () => {
|
||||
await processFile(ctx, source, params.move ? exAdapter : target, workspaceId, blob)
|
||||
},
|
||||
50
|
||||
)
|
||||
movedCnt += 1
|
||||
movedBytes += blob.size
|
||||
batchBytes += blob.size
|
||||
} catch (err) {
|
||||
console.error('failed to process blob', data._id, err)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
skippedCnt += 1
|
||||
console.log('skipping large blob', data._id, Math.round(blob.size / 1024 / 1024))
|
||||
}
|
||||
if (targetBlob === undefined) {
|
||||
await rateLimiter.exec(async () => {
|
||||
try {
|
||||
await retryOnFailure(
|
||||
ctx,
|
||||
5,
|
||||
async () => {
|
||||
await processFile(ctx, source, target, workspaceId, sourceBlob)
|
||||
// We need to sync and update aggregator table for now.
|
||||
await exAdapter.syncBlobFromStorage(ctx, workspaceId, sourceBlob._id, exAdapter.defaultAdapter)
|
||||
},
|
||||
50
|
||||
)
|
||||
movedCnt += 1
|
||||
movedBytes += sourceBlob.size
|
||||
batchBytes += sourceBlob.size
|
||||
} catch (err) {
|
||||
console.error('failed to process blob', data._id, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (targetBlob === undefined) {
|
||||
targetBlob = await target.stat(ctx, workspaceId, data._id)
|
||||
}
|
||||
|
||||
if (
|
||||
targetBlob !== undefined &&
|
||||
targetBlob.size === sourceBlob.size &&
|
||||
targetBlob.contentType === sourceBlob.contentType
|
||||
) {
|
||||
// We could safely delete source blob
|
||||
toRemove.push(sourceBlob._id)
|
||||
}
|
||||
|
||||
processedCnt += 1
|
||||
processedBytes += blob.size
|
||||
processedBytes += sourceBlob.size
|
||||
|
||||
if (processedCnt % 100 === 0) {
|
||||
await rateLimiter.waitProcessing()
|
||||
@ -195,8 +212,6 @@ async function processAdapter (
|
||||
movedCnt,
|
||||
Math.round(movedBytes / 1024 / 1024) + 'MB',
|
||||
'+' + Math.round(batchBytes / 1024 / 1024) + 'MB',
|
||||
'skipped',
|
||||
skippedCnt,
|
||||
Math.round(duration / 1000) + 's'
|
||||
)
|
||||
|
||||
@ -207,6 +222,9 @@ async function processAdapter (
|
||||
}
|
||||
|
||||
await rateLimiter.waitProcessing()
|
||||
if (toRemove.length > 0 && params.move) {
|
||||
await source.remove(ctx, workspaceId, toRemove)
|
||||
}
|
||||
} finally {
|
||||
await iterator.close()
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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>,
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -76,8 +76,9 @@
|
||||
use:tooltip={{
|
||||
component: Label,
|
||||
props: { label: attribute.label }
|
||||
}}><Label label={attribute.label} /></span
|
||||
>
|
||||
}}
|
||||
><Label label={attribute.label} />
|
||||
</span>
|
||||
<div class="flex flex-grow min-w-0">
|
||||
<svelte:component
|
||||
this={editor}
|
||||
|
@ -26,7 +26,9 @@
|
||||
</script>
|
||||
|
||||
{#if node}
|
||||
<pre class="proseCodeBlock" style:margin={preview ? '0' : null}><code
|
||||
><Component is={diffview.component.Highlight} props={{ value, language }} /></code
|
||||
></pre>
|
||||
<pre class="proseCodeBlock" style:margin={preview ? '0' : null}>
|
||||
<code>
|
||||
<Component is={diffview.component.Highlight} props={{ value, language }} />
|
||||
</code>
|
||||
</pre>
|
||||
{/if}
|
||||
|
@ -17,7 +17,7 @@ import { textblockTypeInputRule } from '@tiptap/core'
|
||||
import CodeBlock, { CodeBlockOptions } from '@tiptap/extension-code-block'
|
||||
|
||||
export const codeBlockOptions: CodeBlockOptions = {
|
||||
defaultLanguage: null,
|
||||
defaultLanguage: 'plaintext',
|
||||
languageClassPrefix: 'language-',
|
||||
exitOnArrowDown: true,
|
||||
exitOnTripleEnter: true,
|
||||
|
@ -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; }
|
||||
|
@ -270,6 +270,9 @@
|
||||
aspect-ratio: 1;
|
||||
border-radius: 50%;
|
||||
|
||||
&.relative {
|
||||
position: relative;
|
||||
}
|
||||
&.xx-small,
|
||||
&.inline,
|
||||
&.tiny,
|
||||
|
@ -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}
|
||||
|
@ -121,7 +121,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
>
|
||||
|
||||
<style lang="scss">
|
||||
.img {
|
||||
|
@ -87,7 +87,6 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
>
|
||||
|
||||
<style lang="scss">
|
||||
.editbox {
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -157,7 +157,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
>
|
||||
|
||||
<style lang="scss">
|
||||
.editbox-container {
|
||||
|
@ -73,7 +73,6 @@
|
||||
on:keypress
|
||||
on:blur
|
||||
/>
|
||||
>
|
||||
|
||||
<style lang="scss">
|
||||
.root {
|
||||
|
@ -86,7 +86,6 @@
|
||||
<IconClose size={'small'} />
|
||||
</button>
|
||||
</label>
|
||||
>
|
||||
|
||||
<style lang="scss">
|
||||
.searchInput-wrapper {
|
||||
|
@ -55,7 +55,6 @@
|
||||
on:blur
|
||||
/>
|
||||
</div>
|
||||
>
|
||||
|
||||
<style lang="scss">
|
||||
.textarea {
|
||||
|
@ -84,4 +84,3 @@
|
||||
>
|
||||
{time}
|
||||
</span>
|
||||
span>
|
||||
|
@ -89,9 +89,9 @@
|
||||
{#if selectedTZ}
|
||||
<div class="header flex-col">
|
||||
<div class="flex-between min-h-4" style:margin-right={'-.5rem'}>
|
||||
<span class="text-xs font-medium uppercase content-darker-color flex-grow"
|
||||
><Label label={ui.string.Selected} /></span
|
||||
>
|
||||
<span class="text-xs font-medium uppercase content-darker-color flex-grow">
|
||||
<Label label={ui.string.Selected} />
|
||||
</span>
|
||||
{#if reset !== null}
|
||||
<ActionIcon
|
||||
icon={IconUndo}
|
||||
|
@ -112,9 +112,9 @@
|
||||
{#if viewDate}
|
||||
<div class="caption">
|
||||
{#each [...Array(7).keys()] as dayOfWeek}
|
||||
<span class="weekdays ui-regular-12"
|
||||
>{capitalizeFirstLetter(getWeekDayName(day(firstDayOfCurrentMonth, dayOfWeek), 'short'))}</span
|
||||
>
|
||||
<span class="weekdays ui-regular-12">
|
||||
{capitalizeFirstLetter(getWeekDayName(day(firstDayOfCurrentMonth, dayOfWeek), 'short'))}
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="calendar">
|
||||
|
@ -134,9 +134,9 @@
|
||||
{#if viewDate}
|
||||
<div class="calendar" class:noPadding>
|
||||
{#each [...Array(7).keys()] as dayOfWeek}
|
||||
<span class="caption"
|
||||
>{capitalizeFirstLetter(getWeekDayName(day(firstDayOfCurrentMonth, dayOfWeek), 'short'))}</span
|
||||
>
|
||||
<span class="caption">
|
||||
{capitalizeFirstLetter(getWeekDayName(day(firstDayOfCurrentMonth, dayOfWeek), 'short'))}
|
||||
</span>
|
||||
{/each}
|
||||
|
||||
{#each [...Array(displayedWeeksCount).keys()] as weekIndex}
|
||||
|
@ -8,5 +8,5 @@
|
||||
<path
|
||||
{opacity}
|
||||
d="M 8 1.320313 L 0.660156 8.132813 L 1.339844 8.867188 L 2 8.253906 L 2 14 L 7 14 L 7 9 L 9 9 L 9 14 L 14 14 L 14 8.253906 L 14.660156 8.867188 L 15.339844 8.132813 Z M 8 2.679688 L 13 7.328125 L 13 13 L 10 13 L 10 8 L 6 8 L 6 13 L 3 13 L 3 7.328125 Z"
|
||||
/></svg
|
||||
>
|
||||
/>
|
||||
</svg>
|
||||
|
@ -8,5 +8,5 @@
|
||||
<path
|
||||
{opacity}
|
||||
d="M 14.5 2.792969 L 5.5 11.792969 L 1.851563 8.148438 L 1.5 7.792969 L 0.792969 8.5 L 1.148438 8.851563 L 5.5 13.207031 L 15.207031 3.5 Z"
|
||||
/></svg
|
||||
>
|
||||
/>
|
||||
</svg>
|
||||
|
@ -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);
|
||||
|
@ -47,7 +47,7 @@
|
||||
$: localStorage.setItem('activity-filter', JSON.stringify(selectedFiltersRefs))
|
||||
$: localStorage.setItem('activity-newest-first', JSON.stringify(isNewestFirst))
|
||||
|
||||
client.findAll(activity.class.ActivityMessagesFilter, {}).then((res) => {
|
||||
void client.findAll(activity.class.ActivityMessagesFilter, {}).then((res) => {
|
||||
filters = res
|
||||
|
||||
if (saved !== null && saved !== undefined) {
|
||||
|
@ -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}
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
},
|
||||
|
@ -92,10 +92,8 @@
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="header">
|
||||
<div class="flex fs-title flex-gap-1">
|
||||
<span class="over-underline" on:click={handleMove}>{space?.name}</span>><span
|
||||
class="over-underline"
|
||||
on:click={handleMove}>{state?.name}</span
|
||||
>
|
||||
<span class="over-underline" on:click={handleMove}>{space?.name}</span>>
|
||||
<span class="over-underline" on:click={handleMove}>{state?.name}</span>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="tools">
|
||||
|
@ -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}
|
||||
/>
|
@ -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}
|
||||
/>
|
@ -60,6 +60,7 @@
|
||||
flex-shrink: 0;
|
||||
margin: 0.25rem 0;
|
||||
height: 1.875rem;
|
||||
font-size: 0.75rem;
|
||||
|
||||
&:not(:first-child)::after {
|
||||
position: absolute;
|
||||
|
@ -126,8 +126,10 @@
|
||||
use:tooltip={{
|
||||
component: Label,
|
||||
props: { label: core.string.CreatedBy }
|
||||
}}><Label label={core.string.CreatedBy} /></span
|
||||
}}
|
||||
>
|
||||
<Label label={core.string.CreatedBy} />
|
||||
</span>
|
||||
<div class="flex flex-grow min-w-0">
|
||||
<EmployeeBox
|
||||
value={creatorPersonRef}
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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>,
|
||||
|
@ -102,6 +102,7 @@
|
||||
"People": "People",
|
||||
"For": "For",
|
||||
"SelectUsers": "Select users",
|
||||
"AddGuest": "Add guest"
|
||||
"AddGuest": "Add guest",
|
||||
"ViewProfile": "View profile"
|
||||
}
|
||||
}
|
||||
|
@ -102,6 +102,7 @@
|
||||
"People": "Personas",
|
||||
"For": "Para",
|
||||
"SelectUsers": "Seleccionar usuarios",
|
||||
"AddGuest": "Añadir invitado"
|
||||
"AddGuest": "Añadir invitado",
|
||||
"ViewProfile": "Ver perfil"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -102,6 +102,7 @@
|
||||
"People": "Pessoas",
|
||||
"For": "Para",
|
||||
"SelectUsers": "Selecionar utilizadores",
|
||||
"AddGuest": "Adicionar convidado"
|
||||
"AddGuest": "Adicionar convidado",
|
||||
"ViewProfile": "Ver perfil"
|
||||
}
|
||||
}
|
@ -102,6 +102,7 @@
|
||||
"People": "Люди",
|
||||
"For": "Для",
|
||||
"SelectUsers": "Выберите пользователей",
|
||||
"AddGuest": "Добавить гостя"
|
||||
"AddGuest": "Добавить гостя",
|
||||
"ViewProfile": "Посмотреть профиль"
|
||||
}
|
||||
}
|
||||
|
@ -102,6 +102,7 @@
|
||||
"People": "人员",
|
||||
"For": "为",
|
||||
"SelectUsers": "选择用户",
|
||||
"AddGuest": "添加访客"
|
||||
"AddGuest": "添加访客",
|
||||
"ViewProfile": "查看资料"
|
||||
}
|
||||
}
|
||||
|
@ -186,8 +186,10 @@
|
||||
if (openable) {
|
||||
dispatch('update', 'open')
|
||||
}
|
||||
}}>{value}</span
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</span>
|
||||
<Button
|
||||
focusIndex={3}
|
||||
kind={'ghost'}
|
||||
|
@ -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}
|
||||
|
@ -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}-->
|
||||
<!-- <!– 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={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>
|
||||
|
@ -47,9 +47,9 @@
|
||||
<div class="icon circle">
|
||||
<Company size={'small'} />
|
||||
</div>
|
||||
<span class="overflow-label label" class:no-underline={noUnderline || disabled} class:fs-bold={accent}
|
||||
>{value.name}</span
|
||||
>
|
||||
<span class="overflow-label label" class:no-underline={noUnderline || disabled} class:fs-bold={accent}>
|
||||
{value.name}
|
||||
</span>
|
||||
</div>
|
||||
</DocNavLink>
|
||||
{:else if type === 'text'}
|
||||
|
@ -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}
|
||||
|
@ -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) => {
|
||||
|
@ -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
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -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>
|
||||
|
@ -19,16 +19,21 @@ import { hljsDefineSvelte } from './languages/svelte-hljs'
|
||||
hljs.registerLanguage('svelte', hljsDefineSvelte)
|
||||
|
||||
export interface HighlightOptions {
|
||||
auto?: boolean
|
||||
language: string | undefined
|
||||
}
|
||||
|
||||
export function highlightText (text: string, options: HighlightOptions): string {
|
||||
// We should always use highlighter because it sanitizes the input
|
||||
// We have to always use highlighter to ensure that the input is sanitized
|
||||
const { language } = options
|
||||
const { language, auto } = options
|
||||
const validLanguage = language !== undefined && hljs.getLanguage(language) !== undefined
|
||||
|
||||
const { value: highlighted } = validLanguage ? hljs.highlight(text, { language }) : hljs.highlightAuto(text)
|
||||
const { value: highlighted } = validLanguage
|
||||
? hljs.highlight(text, { language })
|
||||
: auto === true
|
||||
? hljs.highlightAuto(text)
|
||||
: { value: text }
|
||||
|
||||
return normalizeHighlightTags(highlighted)
|
||||
}
|
||||
|
@ -39,9 +39,9 @@
|
||||
{#if shouldShowAvatar}
|
||||
<div class="icon"><Icon icon={lead.icon.Lead} size={'small'} /></div>
|
||||
{/if}
|
||||
<span class="label nowrap" class:no-underline={noUnderline || disabled} class:fs-bold={accent}
|
||||
>{value.identifier}</span
|
||||
>
|
||||
<span class="label nowrap" class:no-underline={noUnderline || disabled} class:fs-bold={accent}>
|
||||
{value.identifier}
|
||||
</span>
|
||||
</div>
|
||||
</DocNavLink>
|
||||
{:else if type === 'text'}
|
||||
|
@ -164,8 +164,10 @@
|
||||
setMetadataLocalStorage(login.metadata.LoginEndpoint, null)
|
||||
setMetadataLocalStorage(login.metadata.LoginEmail, null)
|
||||
goTo('login')
|
||||
}}><Label label={login.string.ChangeAccount} /></NavLink
|
||||
}}
|
||||
>
|
||||
<Label label={login.string.ChangeAccount} />
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
{/await}
|
||||
|
@ -361,7 +361,8 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
width: 15rem;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
user-select: none;
|
||||
|
||||
&:not(.isDock) {
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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"
|
||||
|
@ -70,12 +70,12 @@
|
||||
<tr class="antiTable-body__row">
|
||||
<td><PersonRefPresenter value={requested.employee} /></td>
|
||||
<td><BooleanIcon value={requested.decision} /></td>
|
||||
<td
|
||||
>{#if requested.comment}
|
||||
<td>
|
||||
{#if requested.comment}
|
||||
<ShowMore limit={126} fixed>
|
||||
<MessageViewer message={requested.comment.message} />
|
||||
</ShowMore>{/if}</td
|
||||
>
|
||||
</ShowMore>{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
|
@ -24,5 +24,7 @@
|
||||
|
||||
<span class="label nowrap">
|
||||
{#if shortLabel}
|
||||
{shortLabel}-{/if}{value.name}</span
|
||||
>
|
||||
{shortLabel}-
|
||||
{/if}
|
||||
{value.name}
|
||||
</span>
|
||||
|
@ -33,9 +33,9 @@
|
||||
<div class="icon">
|
||||
<Icon icon={task.icon.Task} size={'small'} />
|
||||
</div>
|
||||
<span class="label nowrap"
|
||||
>{#if shortLabel}{shortLabel}-{/if}{value.number}</span
|
||||
>
|
||||
<span class="label nowrap">
|
||||
{#if shortLabel}{shortLabel}-{/if}{value.number}
|
||||
</span>
|
||||
</div>
|
||||
</DocNavLink>
|
||||
{/if}
|
||||
|
@ -112,24 +112,24 @@
|
||||
maxDigitsAfterPoint={3}
|
||||
kind={'editbox'}
|
||||
/>
|
||||
<Button kind={'link-bordered'} on:click={() => (data.value = 1)}
|
||||
><span slot="content">1<Label label={tracker.string.HourLabel} /></span></Button
|
||||
>
|
||||
<Button kind={'link-bordered'} on:click={() => (data.value = 2)}
|
||||
><span slot="content">2<Label label={tracker.string.HourLabel} /></span></Button
|
||||
>
|
||||
<Button kind={'link-bordered'} on:click={() => (data.value = 4)}
|
||||
><span slot="content">4<Label label={tracker.string.HourLabel} /></span></Button
|
||||
>
|
||||
<Button kind={'link-bordered'} on:click={() => (data.value = 6)}
|
||||
><span slot="content">6<Label label={tracker.string.HourLabel} /></span></Button
|
||||
>
|
||||
<Button kind={'link-bordered'} on:click={() => (data.value = 7)}
|
||||
><span slot="content">7<Label label={tracker.string.HourLabel} /></span></Button
|
||||
>
|
||||
<Button kind={'link-bordered'} on:click={() => (data.value = 8)}
|
||||
><span slot="content">8<Label label={tracker.string.HourLabel} /></span></Button
|
||||
>
|
||||
<Button kind={'link-bordered'} on:click={() => (data.value = 1)}>
|
||||
<span slot="content">1<Label label={tracker.string.HourLabel} /></span>
|
||||
</Button>
|
||||
<Button kind={'link-bordered'} on:click={() => (data.value = 2)}>
|
||||
<span slot="content">2<Label label={tracker.string.HourLabel} /></span>
|
||||
</Button>
|
||||
<Button kind={'link-bordered'} on:click={() => (data.value = 4)}>
|
||||
<span slot="content">4<Label label={tracker.string.HourLabel} /></span>
|
||||
</Button>
|
||||
<Button kind={'link-bordered'} on:click={() => (data.value = 6)}>
|
||||
<span slot="content">6<Label label={tracker.string.HourLabel} /></span>
|
||||
</Button>
|
||||
<Button kind={'link-bordered'} on:click={() => (data.value = 7)}>
|
||||
<span slot="content">7<Label label={tracker.string.HourLabel} /></span>
|
||||
</Button>
|
||||
<Button kind={'link-bordered'} on:click={() => (data.value = 8)}>
|
||||
<span slot="content">8<Label label={tracker.string.HourLabel} /></span>
|
||||
</Button>
|
||||
</div>
|
||||
<EditBox bind:value={data.description} placeholder={tracker.string.TimeSpendReportDescription} kind={'editbox'} />
|
||||
<svelte:fragment slot="pool">
|
||||
|
@ -109,6 +109,6 @@
|
||||
<DatePresenter value={report.date} kind={'ghost'} size={'small'} />
|
||||
</FixedColumn>
|
||||
</div>
|
||||
</div></svelte:fragment
|
||||
>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</ListView>
|
||||
|
@ -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>
|
||||
|
@ -71,7 +71,6 @@ export interface Widget extends Doc {
|
||||
label: IntlString
|
||||
icon: Asset
|
||||
type: WidgetType
|
||||
size?: 'small' | 'medium'
|
||||
|
||||
component: AnyComponent
|
||||
tabComponent?: AnyComponent
|
||||
|
@ -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> {
|
||||
|
@ -156,8 +156,8 @@ export class CommentSyncManager implements DocSyncManager {
|
||||
derivedClient: TxOperations,
|
||||
integration: IntegrationContainer
|
||||
): Promise<void> {
|
||||
const { repository } = await this.provider.getProjectAndRepository(event.repository.node_id)
|
||||
if (repository === undefined) {
|
||||
const { repository: repo } = await this.provider.getProjectAndRepository(event.repository.node_id)
|
||||
if (repo === undefined) {
|
||||
this.ctx.info('No project for repository', {
|
||||
repository: event.repository,
|
||||
workspace: this.provider.getWorkspaceId().name
|
||||
@ -168,11 +168,12 @@ export class CommentSyncManager implements DocSyncManager {
|
||||
const account = (await this.provider.getAccountU(event.sender))?._id ?? core.account.System
|
||||
switch (event.action) {
|
||||
case 'created': {
|
||||
await this.createSyncData(event, derivedClient, repository)
|
||||
await this.createSyncData(event, derivedClient, repo)
|
||||
break
|
||||
}
|
||||
case 'deleted': {
|
||||
const syncData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||
space: repo.githubProject as Ref<GithubProject>,
|
||||
url: (event.comment.url ?? '').toLowerCase()
|
||||
})
|
||||
if (syncData !== undefined) {
|
||||
@ -183,6 +184,7 @@ export class CommentSyncManager implements DocSyncManager {
|
||||
}
|
||||
case 'edited': {
|
||||
const commentData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||
space: repo.githubProject as Ref<GithubProject>,
|
||||
url: (event.comment.url ?? '').toLowerCase()
|
||||
})
|
||||
|
||||
@ -221,6 +223,7 @@ export class CommentSyncManager implements DocSyncManager {
|
||||
repo: GithubIntegrationRepository
|
||||
): Promise<void> {
|
||||
const commentData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||
space: repo.githubProject as Ref<GithubProject>,
|
||||
url: (createdEvent.comment.url ?? '').toLowerCase()
|
||||
})
|
||||
|
||||
@ -266,6 +269,7 @@ export class CommentSyncManager implements DocSyncManager {
|
||||
if (parent === undefined) {
|
||||
// Find parent by issue url
|
||||
parent = await this.client.findOne(github.class.DocSyncInfo, {
|
||||
space: container.project._id,
|
||||
url: (comment.html_url.split('#')?.[0] ?? '').toLowerCase()
|
||||
})
|
||||
}
|
||||
|
@ -201,6 +201,7 @@ export abstract class IssueSyncManagerBase {
|
||||
}
|
||||
)) as any
|
||||
const syncData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||
space: prj._id,
|
||||
url: (actualContent.node.content.url ?? '').toLowerCase()
|
||||
})
|
||||
|
||||
@ -353,7 +354,8 @@ export abstract class IssueSyncManagerBase {
|
||||
}
|
||||
|
||||
syncData =
|
||||
syncData ?? (await this.client.findOne(github.class.DocSyncInfo, { url: (external.url ?? '').toLowerCase() }))
|
||||
syncData ??
|
||||
(await this.client.findOne(github.class.DocSyncInfo, { space: prj._id, url: (external.url ?? '').toLowerCase() }))
|
||||
|
||||
if (syncData !== undefined) {
|
||||
const doc: Issue | undefined = await this.client.findOne<Issue>(syncData.objectClass, {
|
||||
@ -1262,7 +1264,10 @@ export abstract class IssueSyncManagerBase {
|
||||
err: any,
|
||||
_class: Ref<Class<Doc>> = tracker.class.Issue
|
||||
): Promise<void> {
|
||||
const syncData = await this.client.findOne(github.class.DocSyncInfo, { url: url.toLowerCase() })
|
||||
const syncData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||
space: repo.githubProject as Ref<GithubProject>,
|
||||
url: url.toLowerCase()
|
||||
})
|
||||
if (syncData === undefined) {
|
||||
await derivedClient?.createDoc(github.class.DocSyncInfo, repo.githubProject as Ref<GithubProject>, {
|
||||
url,
|
||||
|
@ -203,6 +203,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
|
||||
}
|
||||
case 'deleted': {
|
||||
const syncData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||
space: repo.githubProject as Ref<GithubProject>,
|
||||
url: (event.issue.html_url ?? '').toLowerCase()
|
||||
})
|
||||
if (syncData !== undefined) {
|
||||
@ -291,6 +292,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
|
||||
repo: GithubIntegrationRepository
|
||||
): Promise<void> {
|
||||
const issueSyncData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||
space: repo.githubProject as Ref<GithubProject>,
|
||||
url: (issueExternal.url ?? '').toLowerCase()
|
||||
})
|
||||
if (issueSyncData === undefined) {
|
||||
@ -452,7 +454,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
|
||||
break
|
||||
}
|
||||
|
||||
await this.provider.doSyncFor(attachedDocs)
|
||||
await this.provider.doSyncFor(attachedDocs, container.project)
|
||||
for (const child of attachedDocs) {
|
||||
await derivedClient.update(child, { createId })
|
||||
}
|
||||
|
@ -302,6 +302,7 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
|
||||
}
|
||||
case 'synchronize': {
|
||||
const syncData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||
space: repo.githubProject as Ref<GithubProject>,
|
||||
url: (externalData.url ?? '').toLowerCase()
|
||||
})
|
||||
if (syncData !== undefined) {
|
||||
|
@ -213,6 +213,7 @@ export class ReviewCommentSyncManager implements DocSyncManager {
|
||||
}
|
||||
case 'deleted': {
|
||||
const reviewData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||
space: repo.githubProject as Ref<GithubProject>,
|
||||
url: (event.comment.html_url ?? '').toLowerCase()
|
||||
})
|
||||
if (reviewData !== undefined) {
|
||||
@ -232,6 +233,7 @@ export class ReviewCommentSyncManager implements DocSyncManager {
|
||||
}
|
||||
case 'edited': {
|
||||
const reviewData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||
space: repo.githubProject as Ref<GithubProject>,
|
||||
url: (event.comment.html_url ?? '').toLowerCase()
|
||||
})
|
||||
|
||||
@ -280,6 +282,7 @@ export class ReviewCommentSyncManager implements DocSyncManager {
|
||||
externalData: ReviewCommentExternalData
|
||||
): Promise<void> {
|
||||
const reviewData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||
space: repo.githubProject as Ref<GithubProject>,
|
||||
url: (createdEvent.comment.html_url ?? '').toLowerCase()
|
||||
})
|
||||
|
||||
@ -331,9 +334,10 @@ export class ReviewCommentSyncManager implements DocSyncManager {
|
||||
|
||||
if (info.reviewThreadId === undefined && reviewComment.replyTo?.url !== undefined) {
|
||||
const rthread = await derivedClient.findOne(github.class.GithubReviewComment, {
|
||||
space: container.project._id,
|
||||
url: reviewComment.replyTo?.url?.toLowerCase()
|
||||
})
|
||||
if (rthread !== undefined) {
|
||||
if (rthread !== undefined && info.reviewThreadId !== rthread.reviewThreadId) {
|
||||
info.reviewThreadId = rthread.reviewThreadId
|
||||
await derivedClient.update(info, { reviewThreadId: info.reviewThreadId })
|
||||
}
|
||||
@ -519,8 +523,10 @@ export class ReviewCommentSyncManager implements DocSyncManager {
|
||||
external: reviewExternal,
|
||||
current: existing,
|
||||
repository: repo._id,
|
||||
parent: parent.url.toLocaleLowerCase(),
|
||||
needSync: githubSyncVersion,
|
||||
externalVersion: githubExternalSyncVersion
|
||||
externalVersion: githubExternalSyncVersion,
|
||||
reviewThreadId: info.reviewThreadId ?? existingReview.reviewThreadId
|
||||
}
|
||||
// We need to update in current promise, to prevent event changes.
|
||||
await derivedClient.update(info, upd)
|
||||
|
@ -205,6 +205,7 @@ export class ReviewThreadSyncManager implements DocSyncManager {
|
||||
case 'unresolved': {
|
||||
const isResolved = event.action === 'resolved'
|
||||
const reviewData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||
space: repo.githubProject as Ref<GithubProject>,
|
||||
url: event.thread.node_id.toLocaleLowerCase()
|
||||
})
|
||||
|
||||
@ -241,6 +242,7 @@ export class ReviewThreadSyncManager implements DocSyncManager {
|
||||
}
|
||||
|
||||
const reviewPR = await this.client.findOne(github.class.DocSyncInfo, {
|
||||
space: repo.githubProject as Ref<GithubProject>,
|
||||
url: (reviewData.parent ?? '').toLowerCase()
|
||||
})
|
||||
if (reviewPR !== undefined) {
|
||||
@ -516,6 +518,7 @@ export class ReviewThreadSyncManager implements DocSyncManager {
|
||||
.map((it) => (it.parent ?? '').toLowerCase())
|
||||
.filter((it, idx, arr) => it != null && arr.indexOf(it) === idx)
|
||||
const parents = await derivedClient.findAll(github.class.DocSyncInfo, {
|
||||
space: repo.githubProject as Ref<GithubProject>,
|
||||
url: {
|
||||
$in: allParents
|
||||
}
|
||||
|
@ -198,6 +198,7 @@ export class ReviewSyncManager implements DocSyncManager {
|
||||
await this.createSyncData(event, derivedClient, repo, externalData)
|
||||
|
||||
const parentDoc = await this.client.findOne(github.class.DocSyncInfo, {
|
||||
space: repo.githubProject as Ref<GithubProject>,
|
||||
url: (event.pull_request.html_url ?? '').toLowerCase()
|
||||
})
|
||||
if (parentDoc !== undefined) {
|
||||
@ -211,6 +212,7 @@ export class ReviewSyncManager implements DocSyncManager {
|
||||
}
|
||||
case 'dismissed': {
|
||||
const reviewData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||
space: repo.githubProject as Ref<GithubProject>,
|
||||
url: (event.review.html_url ?? '').toLowerCase()
|
||||
})
|
||||
|
||||
@ -254,6 +256,7 @@ export class ReviewSyncManager implements DocSyncManager {
|
||||
externalData: ReviewExternalData
|
||||
): Promise<void> {
|
||||
const reviewData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||
space: repo.githubProject as Ref<GithubProject>,
|
||||
url: (createdEvent.review.html_url ?? '').toLowerCase()
|
||||
})
|
||||
|
||||
|
@ -340,6 +340,7 @@ export async function syncDerivedDocuments<T extends { url: string }> (
|
||||
extra?: any
|
||||
): Promise<void> {
|
||||
const childDocsOfClass = await derivedClient.findAll(github.class.DocSyncInfo, {
|
||||
space: prj._id,
|
||||
objectClass,
|
||||
parent: (parentDoc.url ?? '').toLowerCase(),
|
||||
...query
|
||||
@ -352,7 +353,7 @@ export async function syncDerivedDocuments<T extends { url: string }> (
|
||||
if (existing === undefined) {
|
||||
await derivedClient.createDoc<DocSyncInfo>(github.class.DocSyncInfo, prj._id, {
|
||||
objectClass,
|
||||
url: r.url.toLowerCase(),
|
||||
url: (r.url ?? '').toLowerCase(),
|
||||
needSync: '', // we need to sync to retrieve patch in background
|
||||
githubNumber: 0,
|
||||
repository: repo._id,
|
||||
|
@ -111,7 +111,7 @@ export interface IntegrationManager {
|
||||
event: T
|
||||
) => Promise<void>
|
||||
|
||||
doSyncFor: (docs: DocSyncInfo[]) => Promise<void>
|
||||
doSyncFor: (docs: DocSyncInfo[], project: GithubProject) => Promise<void>
|
||||
getWorkspaceId: () => WorkspaceIdWithUrl
|
||||
getBranding: () => Branding | null
|
||||
|
||||
|
@ -1114,7 +1114,7 @@ export class GithubWorker implements IntegrationManager {
|
||||
}
|
||||
|
||||
private async performSync (projects: GithubProject[], repositories: GithubIntegrationRepository[]): Promise<boolean> {
|
||||
const _projects = projects.map((it) => it._id)
|
||||
const _projects = toIdMap(projects)
|
||||
const _repositories = repositories.map((it) => it._id)
|
||||
|
||||
const docs = await this.ctx.with(
|
||||
@ -1126,7 +1126,7 @@ export class GithubWorker implements IntegrationManager {
|
||||
{
|
||||
needSync: { $ne: githubSyncVersion },
|
||||
externalVersion: { $in: [githubExternalSyncVersion, '#'] },
|
||||
space: { $in: _projects },
|
||||
space: { $in: Array.from(_projects.keys()) },
|
||||
repository: { $in: [null, ..._repositories] }
|
||||
},
|
||||
{
|
||||
@ -1142,18 +1142,21 @@ export class GithubWorker implements IntegrationManager {
|
||||
this.previousWait += docs.length
|
||||
this.ctx.info('Syncing', { docs: docs.length, workspace: this.workspace.name })
|
||||
|
||||
await this.doSyncFor(docs)
|
||||
const bySpace = groupByArray(docs, (it) => it.space)
|
||||
for (const [k, v] of bySpace.entries()) {
|
||||
await this.doSyncFor(v, _projects.get(k as Ref<GithubProject>) as GithubProject)
|
||||
}
|
||||
}
|
||||
return docs.length !== 0
|
||||
}
|
||||
|
||||
async doSyncFor (docs: DocSyncInfo[]): Promise<void> {
|
||||
async doSyncFor (docs: DocSyncInfo[], project: GithubProject): Promise<void> {
|
||||
const byClass = this.groupByClass(docs)
|
||||
|
||||
// We need to reorder based on our sync mappers
|
||||
|
||||
for (const [_class, clDocs] of byClass.entries()) {
|
||||
await this.syncClass(_class, clDocs)
|
||||
await this.syncClass(_class, clDocs, project)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1220,12 +1223,13 @@ export class GithubWorker implements IntegrationManager {
|
||||
}
|
||||
}
|
||||
|
||||
async syncClass (_class: Ref<Class<Doc>>, syncInfo: DocSyncInfo[]): Promise<void> {
|
||||
async syncClass (_class: Ref<Class<Doc>>, syncInfo: DocSyncInfo[], project: GithubProject): Promise<void> {
|
||||
const externalDocs = await this._client.findAll<Doc>(_class, {
|
||||
_id: { $in: syncInfo.map((it) => it._id as Ref<Doc>) }
|
||||
})
|
||||
|
||||
const parents = await this._client.findAll<DocSyncInfo>(github.class.DocSyncInfo, {
|
||||
space: project._id,
|
||||
url: { $in: syncInfo.map((it) => it.parent?.toLowerCase()).filter((it) => it) as string[] }
|
||||
})
|
||||
|
||||
|
@ -335,7 +335,7 @@ test.describe('Content in the Documents tests', () => {
|
||||
await documentContentPage.applyToolbarCommand('Line 16', 'btnBlockquote')
|
||||
await documentContentPage.applyToolbarCommand('Line 17', 'btnCode')
|
||||
await documentContentPage.applyToolbarCommand('Line 18', 'btnCodeBlock')
|
||||
await documentContentPage.changeCodeBlockLanguage('Line 18', 'auto', 'css')
|
||||
await documentContentPage.changeCodeBlockLanguage('Line 18', 'plaintext', 'css')
|
||||
await documentContentPage.applyNote('Line 19', 'warning', testNote)
|
||||
await documentContentPage.addImage('Line 20')
|
||||
await page.keyboard.type('Cat')
|
||||
|
Loading…
Reference in New Issue
Block a user