mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-23 08:48:01 +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(
|
.action(
|
||||||
async (cmd: { workspace: string, move: string, blobLimit: string, concurrency: string, disabled: boolean }) => {
|
async (cmd: { workspace: string, move: string, blobLimit: string, concurrency: string, disabled: boolean }) => {
|
||||||
const params = {
|
const params = {
|
||||||
blobSizeLimitMb: parseInt(cmd.blobLimit),
|
|
||||||
concurrency: parseInt(cmd.concurrency),
|
concurrency: parseInt(cmd.concurrency),
|
||||||
move: cmd.move === 'true'
|
move: cmd.move === 'true'
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@ import { type Db } from 'mongodb'
|
|||||||
import { PassThrough } from 'stream'
|
import { PassThrough } from 'stream'
|
||||||
|
|
||||||
export interface MoveFilesParams {
|
export interface MoveFilesParams {
|
||||||
blobSizeLimitMb: number
|
|
||||||
concurrency: number
|
concurrency: number
|
||||||
move: boolean
|
move: boolean
|
||||||
}
|
}
|
||||||
@ -86,7 +85,7 @@ export async function moveFiles (
|
|||||||
for (const [name, adapter] of exAdapter.adapters.entries()) {
|
for (const [name, adapter] of exAdapter.adapters.entries()) {
|
||||||
if (name === exAdapter.defaultAdapter) continue
|
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
|
// we attempt retry the whole process in case of failure
|
||||||
// files that were already moved will be skipped
|
// files that were already moved will be skipped
|
||||||
@ -129,10 +128,13 @@ async function processAdapter (
|
|||||||
workspaceId: WorkspaceId,
|
workspaceId: WorkspaceId,
|
||||||
params: MoveFilesParams
|
params: MoveFilesParams
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
if (source === target) {
|
||||||
|
// Just in case
|
||||||
|
return
|
||||||
|
}
|
||||||
let time = Date.now()
|
let time = Date.now()
|
||||||
let processedCnt = 0
|
let processedCnt = 0
|
||||||
let processedBytes = 0
|
let processedBytes = 0
|
||||||
let skippedCnt = 0
|
|
||||||
let movedCnt = 0
|
let movedCnt = 0
|
||||||
let movedBytes = 0
|
let movedBytes = 0
|
||||||
let batchBytes = 0
|
let batchBytes = 0
|
||||||
@ -140,47 +142,62 @@ async function processAdapter (
|
|||||||
const rateLimiter = new RateLimiter(params.concurrency)
|
const rateLimiter = new RateLimiter(params.concurrency)
|
||||||
|
|
||||||
const iterator = await source.listStream(ctx, workspaceId)
|
const iterator = await source.listStream(ctx, workspaceId)
|
||||||
|
|
||||||
|
const toRemove: string[] = []
|
||||||
try {
|
try {
|
||||||
while (true) {
|
while (true) {
|
||||||
const dataBulk = await iterator.next()
|
const dataBulk = await iterator.next()
|
||||||
if (dataBulk.length === 0) break
|
if (dataBulk.length === 0) break
|
||||||
|
|
||||||
for (const data of dataBulk) {
|
for (const data of dataBulk) {
|
||||||
const blob =
|
let targetBlob = await target.stat(ctx, workspaceId, data._id)
|
||||||
(await exAdapter.stat(ctx, workspaceId, data._id)) ?? (await source.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)
|
console.error('blob not found', data._id)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if (targetBlob !== undefined) {
|
||||||
|
console.log('Target blob already exists', targetBlob._id, targetBlob.contentType)
|
||||||
|
}
|
||||||
|
|
||||||
if (blob.provider !== exAdapter.defaultAdapter) {
|
if (targetBlob === undefined) {
|
||||||
if (blob.size <= params.blobSizeLimitMb * 1024 * 1024) {
|
await rateLimiter.exec(async () => {
|
||||||
await rateLimiter.exec(async () => {
|
try {
|
||||||
try {
|
await retryOnFailure(
|
||||||
await retryOnFailure(
|
ctx,
|
||||||
ctx,
|
5,
|
||||||
5,
|
async () => {
|
||||||
async () => {
|
await processFile(ctx, source, target, workspaceId, sourceBlob)
|
||||||
await processFile(ctx, source, params.move ? exAdapter : target, workspaceId, blob)
|
// We need to sync and update aggregator table for now.
|
||||||
},
|
await exAdapter.syncBlobFromStorage(ctx, workspaceId, sourceBlob._id, exAdapter.defaultAdapter)
|
||||||
50
|
},
|
||||||
)
|
50
|
||||||
movedCnt += 1
|
)
|
||||||
movedBytes += blob.size
|
movedCnt += 1
|
||||||
batchBytes += blob.size
|
movedBytes += sourceBlob.size
|
||||||
} catch (err) {
|
batchBytes += sourceBlob.size
|
||||||
console.error('failed to process blob', data._id, err)
|
} 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) {
|
||||||
|
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
|
processedCnt += 1
|
||||||
processedBytes += blob.size
|
processedBytes += sourceBlob.size
|
||||||
|
|
||||||
if (processedCnt % 100 === 0) {
|
if (processedCnt % 100 === 0) {
|
||||||
await rateLimiter.waitProcessing()
|
await rateLimiter.waitProcessing()
|
||||||
@ -195,8 +212,6 @@ async function processAdapter (
|
|||||||
movedCnt,
|
movedCnt,
|
||||||
Math.round(movedBytes / 1024 / 1024) + 'MB',
|
Math.round(movedBytes / 1024 / 1024) + 'MB',
|
||||||
'+' + Math.round(batchBytes / 1024 / 1024) + 'MB',
|
'+' + Math.round(batchBytes / 1024 / 1024) + 'MB',
|
||||||
'skipped',
|
|
||||||
skippedCnt,
|
|
||||||
Math.round(duration / 1000) + 's'
|
Math.round(duration / 1000) + 's'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -207,6 +222,9 @@ async function processAdapter (
|
|||||||
}
|
}
|
||||||
|
|
||||||
await rateLimiter.waitProcessing()
|
await rateLimiter.waitProcessing()
|
||||||
|
if (toRemove.length > 0 && params.move) {
|
||||||
|
await source.remove(ctx, workspaceId, toRemove)
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
await iterator.close()
|
await iterator.close()
|
||||||
}
|
}
|
||||||
|
@ -46,8 +46,8 @@ export function defineActions (builder: Builder): void {
|
|||||||
|
|
||||||
createAction(builder, {
|
createAction(builder, {
|
||||||
action: chunter.actionImpl.StartConversation,
|
action: chunter.actionImpl.StartConversation,
|
||||||
label: chunter.string.StartConversation,
|
label: chunter.string.Message,
|
||||||
icon: chunter.icon.Thread,
|
icon: view.icon.Bubble,
|
||||||
input: 'focus',
|
input: 'focus',
|
||||||
category: chunter.category.Chunter,
|
category: chunter.category.Chunter,
|
||||||
target: contact.mixin.Employee,
|
target: contact.mixin.Employee,
|
||||||
|
@ -21,6 +21,7 @@ import core from '@hcengineering/model-core'
|
|||||||
import view from '@hcengineering/model-view'
|
import view from '@hcengineering/model-view'
|
||||||
import workbench from '@hcengineering/model-workbench'
|
import workbench from '@hcengineering/model-workbench'
|
||||||
import { WidgetType } from '@hcengineering/workbench'
|
import { WidgetType } from '@hcengineering/workbench'
|
||||||
|
import contact from '@hcengineering/contact'
|
||||||
|
|
||||||
import chunter from './plugin'
|
import chunter from './plugin'
|
||||||
import { defineActions } from './actions'
|
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 }]
|
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)
|
defineActions(builder)
|
||||||
defineNotifications(builder)
|
defineNotifications(builder)
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,9 @@ export default mergeIds(chunterId, chunter, {
|
|||||||
ChatMessageNotificationLabel: '' as AnyComponent,
|
ChatMessageNotificationLabel: '' as AnyComponent,
|
||||||
ThreadNotificationPresenter: '' as AnyComponent,
|
ThreadNotificationPresenter: '' as AnyComponent,
|
||||||
JoinChannelNotificationPresenter: '' as AnyComponent,
|
JoinChannelNotificationPresenter: '' as AnyComponent,
|
||||||
WorkbenchTabExtension: '' as AnyComponent
|
WorkbenchTabExtension: '' as AnyComponent,
|
||||||
|
DirectMessageButton: '' as AnyComponent,
|
||||||
|
EmployeePresenter: '' as AnyComponent
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
MarkCommentUnread: '' as Ref<Action>,
|
MarkCommentUnread: '' as Ref<Action>,
|
||||||
|
@ -185,8 +185,7 @@ export function createModel (builder: Builder): void {
|
|||||||
label: love.string.MeetingRoom,
|
label: love.string.MeetingRoom,
|
||||||
type: WidgetType.Flexible,
|
type: WidgetType.Flexible,
|
||||||
icon: love.icon.Cam,
|
icon: love.icon.Cam,
|
||||||
component: love.component.VideoWidget,
|
component: love.component.VideoWidget
|
||||||
size: 'medium'
|
|
||||||
},
|
},
|
||||||
love.ids.VideoWidget
|
love.ids.VideoWidget
|
||||||
)
|
)
|
||||||
|
@ -76,8 +76,9 @@
|
|||||||
use:tooltip={{
|
use:tooltip={{
|
||||||
component: Label,
|
component: Label,
|
||||||
props: { label: attribute.label }
|
props: { label: attribute.label }
|
||||||
}}><Label label={attribute.label} /></span
|
}}
|
||||||
>
|
><Label label={attribute.label} />
|
||||||
|
</span>
|
||||||
<div class="flex flex-grow min-w-0">
|
<div class="flex flex-grow min-w-0">
|
||||||
<svelte:component
|
<svelte:component
|
||||||
this={editor}
|
this={editor}
|
||||||
|
@ -26,7 +26,9 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if node}
|
{#if node}
|
||||||
<pre class="proseCodeBlock" style:margin={preview ? '0' : null}><code
|
<pre class="proseCodeBlock" style:margin={preview ? '0' : null}>
|
||||||
><Component is={diffview.component.Highlight} props={{ value, language }} /></code
|
<code>
|
||||||
></pre>
|
<Component is={diffview.component.Highlight} props={{ value, language }} />
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -17,7 +17,7 @@ import { textblockTypeInputRule } from '@tiptap/core'
|
|||||||
import CodeBlock, { CodeBlockOptions } from '@tiptap/extension-code-block'
|
import CodeBlock, { CodeBlockOptions } from '@tiptap/extension-code-block'
|
||||||
|
|
||||||
export const codeBlockOptions: CodeBlockOptions = {
|
export const codeBlockOptions: CodeBlockOptions = {
|
||||||
defaultLanguage: null,
|
defaultLanguage: 'plaintext',
|
||||||
languageClassPrefix: 'language-',
|
languageClassPrefix: 'language-',
|
||||||
exitOnArrowDown: true,
|
exitOnArrowDown: true,
|
||||||
exitOnTripleEnter: true,
|
exitOnTripleEnter: true,
|
||||||
|
@ -316,6 +316,7 @@ input.search {
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
&:not(.small-gap, .large-gap) { margin-right: .375rem; }
|
&:not(.small-gap, .large-gap) { margin-right: .375rem; }
|
||||||
|
&.no-gap { margin-right: 0; }
|
||||||
&.small-gap { margin-right: .25rem; }
|
&.small-gap { margin-right: .25rem; }
|
||||||
&.large-gap { margin-right: .5rem; }
|
&.large-gap { margin-right: .5rem; }
|
||||||
&.flow:last-child { margin-right: 0; }
|
&.flow:last-child { margin-right: 0; }
|
||||||
|
@ -270,6 +270,9 @@
|
|||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
|
||||||
|
&.relative {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
&.xx-small,
|
&.xx-small,
|
||||||
&.inline,
|
&.inline,
|
||||||
&.tiny,
|
&.tiny,
|
||||||
|
@ -107,9 +107,9 @@
|
|||||||
on:keydown
|
on:keydown
|
||||||
>
|
>
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<div class="icon"><Spinner size={'small'} /></div>
|
<div class="icon no-gap"><Spinner size={'small'} /></div>
|
||||||
{:else if icon}
|
{: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}
|
||||||
{#if label}<span><Label {label} params={labelParams} /></span>{/if}
|
{#if label}<span><Label {label} params={labelParams} /></span>{/if}
|
||||||
{#if title}<span>{title}</span>{/if}
|
{#if title}<span>{title}</span>{/if}
|
||||||
|
@ -121,7 +121,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
>
|
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.img {
|
.img {
|
||||||
|
@ -87,7 +87,6 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
>
|
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.editbox {
|
.editbox {
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
export let lazy = false
|
export let lazy = false
|
||||||
export let minHeight: string | null = null
|
export let minHeight: string | null = null
|
||||||
export let highlightIndex: number | undefined = undefined
|
export let highlightIndex: number | undefined = undefined
|
||||||
|
export let items: any[] = []
|
||||||
export let getKey: (index: number) => string = (index) => index.toString()
|
export let getKey: (index: number) => string = (index) => index.toString()
|
||||||
|
|
||||||
const refs: HTMLElement[] = []
|
const refs: HTMLElement[] = []
|
||||||
@ -65,6 +66,8 @@
|
|||||||
r?.scrollIntoView({ behavior: 'auto', block: 'nearest' })
|
r?.scrollIntoView({ behavior: 'auto', block: 'nearest' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: array = items.length > 0 ? items : Array(count)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if count}
|
{#if count}
|
||||||
@ -75,7 +78,7 @@
|
|||||||
dispatch('changeContent')
|
dispatch('changeContent')
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{#each Array(count) as _, row (getKey(row))}
|
{#each array as _, row (getKey(row))}
|
||||||
{#if lazy}
|
{#if lazy}
|
||||||
<div style="min-height: {minHeight}">
|
<div style="min-height: {minHeight}">
|
||||||
<Lazy>
|
<Lazy>
|
||||||
@ -145,6 +148,6 @@
|
|||||||
.list-container {
|
.list-container {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
// border-radius: 0.25rem;
|
// border-radius: 0.25rem;
|
||||||
user-select: none;
|
//user-select: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -81,6 +81,7 @@
|
|||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.container {
|
.container {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
font-size: 0.75rem;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -157,7 +157,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
>
|
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.editbox-container {
|
.editbox-container {
|
||||||
|
@ -73,7 +73,6 @@
|
|||||||
on:keypress
|
on:keypress
|
||||||
on:blur
|
on:blur
|
||||||
/>
|
/>
|
||||||
>
|
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.root {
|
.root {
|
||||||
|
@ -86,7 +86,6 @@
|
|||||||
<IconClose size={'small'} />
|
<IconClose size={'small'} />
|
||||||
</button>
|
</button>
|
||||||
</label>
|
</label>
|
||||||
>
|
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.searchInput-wrapper {
|
.searchInput-wrapper {
|
||||||
|
@ -55,7 +55,6 @@
|
|||||||
on:blur
|
on:blur
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
>
|
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.textarea {
|
.textarea {
|
||||||
|
@ -84,4 +84,3 @@
|
|||||||
>
|
>
|
||||||
{time}
|
{time}
|
||||||
</span>
|
</span>
|
||||||
span>
|
|
||||||
|
@ -89,9 +89,9 @@
|
|||||||
{#if selectedTZ}
|
{#if selectedTZ}
|
||||||
<div class="header flex-col">
|
<div class="header flex-col">
|
||||||
<div class="flex-between min-h-4" style:margin-right={'-.5rem'}>
|
<div class="flex-between min-h-4" style:margin-right={'-.5rem'}>
|
||||||
<span class="text-xs font-medium uppercase content-darker-color flex-grow"
|
<span class="text-xs font-medium uppercase content-darker-color flex-grow">
|
||||||
><Label label={ui.string.Selected} /></span
|
<Label label={ui.string.Selected} />
|
||||||
>
|
</span>
|
||||||
{#if reset !== null}
|
{#if reset !== null}
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
icon={IconUndo}
|
icon={IconUndo}
|
||||||
|
@ -112,9 +112,9 @@
|
|||||||
{#if viewDate}
|
{#if viewDate}
|
||||||
<div class="caption">
|
<div class="caption">
|
||||||
{#each [...Array(7).keys()] as dayOfWeek}
|
{#each [...Array(7).keys()] as dayOfWeek}
|
||||||
<span class="weekdays ui-regular-12"
|
<span class="weekdays ui-regular-12">
|
||||||
>{capitalizeFirstLetter(getWeekDayName(day(firstDayOfCurrentMonth, dayOfWeek), 'short'))}</span
|
{capitalizeFirstLetter(getWeekDayName(day(firstDayOfCurrentMonth, dayOfWeek), 'short'))}
|
||||||
>
|
</span>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
<div class="calendar">
|
<div class="calendar">
|
||||||
|
@ -134,9 +134,9 @@
|
|||||||
{#if viewDate}
|
{#if viewDate}
|
||||||
<div class="calendar" class:noPadding>
|
<div class="calendar" class:noPadding>
|
||||||
{#each [...Array(7).keys()] as dayOfWeek}
|
{#each [...Array(7).keys()] as dayOfWeek}
|
||||||
<span class="caption"
|
<span class="caption">
|
||||||
>{capitalizeFirstLetter(getWeekDayName(day(firstDayOfCurrentMonth, dayOfWeek), 'short'))}</span
|
{capitalizeFirstLetter(getWeekDayName(day(firstDayOfCurrentMonth, dayOfWeek), 'short'))}
|
||||||
>
|
</span>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
{#each [...Array(displayedWeeksCount).keys()] as weekIndex}
|
{#each [...Array(displayedWeeksCount).keys()] as weekIndex}
|
||||||
|
@ -8,5 +8,5 @@
|
|||||||
<path
|
<path
|
||||||
{opacity}
|
{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"
|
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
|
<path
|
||||||
{opacity}
|
{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"
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/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" />
|
<RootBarExtension position="left" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@ -216,7 +216,7 @@
|
|||||||
min-height: var(--status-bar-height);
|
min-height: var(--status-bar-height);
|
||||||
height: var(--status-bar-height);
|
height: var(--status-bar-height);
|
||||||
// min-width: 600px;
|
// min-width: 600px;
|
||||||
font-size: 12px;
|
font-size: 0.75rem;
|
||||||
line-height: 150%;
|
line-height: 150%;
|
||||||
background-color: var(--theme-statusbar-color);
|
background-color: var(--theme-statusbar-color);
|
||||||
// border-bottom: 1px solid var(--theme-navpanel-divider);
|
// border-bottom: 1px solid var(--theme-navpanel-divider);
|
||||||
|
@ -47,7 +47,7 @@
|
|||||||
$: localStorage.setItem('activity-filter', JSON.stringify(selectedFiltersRefs))
|
$: localStorage.setItem('activity-filter', JSON.stringify(selectedFiltersRefs))
|
||||||
$: localStorage.setItem('activity-newest-first', JSON.stringify(isNewestFirst))
|
$: 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
|
filters = res
|
||||||
|
|
||||||
if (saved !== null && saved !== undefined) {
|
if (saved !== null && saved !== undefined) {
|
||||||
|
@ -14,15 +14,9 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getClient, LiteMessageViewer } from '@hcengineering/presentation'
|
import { ComponentExtensions, getClient, LiteMessageViewer } from '@hcengineering/presentation'
|
||||||
import { Person, type PersonAccount } from '@hcengineering/contact'
|
import { Person, type PersonAccount } from '@hcengineering/contact'
|
||||||
import {
|
import { Avatar, personAccountByIdStore, personByIdStore, SystemAvatar } from '@hcengineering/contact-resources'
|
||||||
Avatar,
|
|
||||||
EmployeePresenter,
|
|
||||||
personAccountByIdStore,
|
|
||||||
personByIdStore,
|
|
||||||
SystemAvatar
|
|
||||||
} from '@hcengineering/contact-resources'
|
|
||||||
import core, { Account, Doc, Ref, Timestamp, type WithLookup } from '@hcengineering/core'
|
import core, { Account, Doc, Ref, Timestamp, type WithLookup } from '@hcengineering/core'
|
||||||
import { Icon, Label, resizeObserver, TimeSince, tooltip } from '@hcengineering/ui'
|
import { Icon, Label, resizeObserver, TimeSince, tooltip } from '@hcengineering/ui'
|
||||||
import { Asset, getEmbeddedLabel, IntlString } from '@hcengineering/platform'
|
import { Asset, getEmbeddedLabel, IntlString } from '@hcengineering/platform'
|
||||||
@ -131,7 +125,7 @@
|
|||||||
/>
|
/>
|
||||||
</DocNavLink>
|
</DocNavLink>
|
||||||
{:else if person}
|
{:else if person}
|
||||||
<EmployeePresenter value={person} shouldShowAvatar={false} compact showStatus={false} />
|
<ComponentExtensions extension={activity.extension.ActivityEmployeePresenter} props={{ person }} />
|
||||||
{:else}
|
{:else}
|
||||||
<Label label={core.string.System} />
|
<Label label={core.string.System} />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -19,9 +19,9 @@
|
|||||||
ActivityMessageViewType
|
ActivityMessageViewType
|
||||||
} from '@hcengineering/activity'
|
} from '@hcengineering/activity'
|
||||||
import { Person } from '@hcengineering/contact'
|
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 core, { Ref } from '@hcengineering/core'
|
||||||
import { getClient } from '@hcengineering/presentation'
|
import { ComponentExtensions, getClient } from '@hcengineering/presentation'
|
||||||
import { Action, Icon, Label } from '@hcengineering/ui'
|
import { Action, Icon, Label } from '@hcengineering/ui'
|
||||||
import { getActions, restrictionStore, showMenu } from '@hcengineering/view-resources'
|
import { getActions, restrictionStore, showMenu } from '@hcengineering/view-resources'
|
||||||
import { Asset } from '@hcengineering/platform'
|
import { Asset } from '@hcengineering/platform'
|
||||||
@ -209,7 +209,7 @@
|
|||||||
<div class="header clear-mins">
|
<div class="header clear-mins">
|
||||||
{#if person}
|
{#if person}
|
||||||
<div class="username">
|
<div class="username">
|
||||||
<EmployeePresenter value={person} shouldShowAvatar={false} compact />
|
<ComponentExtensions extension={activity.extension.ActivityEmployeePresenter} props={{ person }} />
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="strong">
|
<div class="strong">
|
||||||
|
@ -30,7 +30,7 @@ import {
|
|||||||
import type { Asset, IntlString, Plugin, Resource } from '@hcengineering/platform'
|
import type { Asset, IntlString, Plugin, Resource } from '@hcengineering/platform'
|
||||||
import { plugin } from '@hcengineering/platform'
|
import { plugin } from '@hcengineering/platform'
|
||||||
import { Preference } from '@hcengineering/preference'
|
import { Preference } from '@hcengineering/preference'
|
||||||
import type { AnyComponent } from '@hcengineering/ui'
|
import type { AnyComponent, ComponentExtensionId } from '@hcengineering/ui'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -334,6 +334,9 @@ export default plugin(activityId, {
|
|||||||
AllFilter: '' as Ref<ActivityMessagesFilter>,
|
AllFilter: '' as Ref<ActivityMessagesFilter>,
|
||||||
MentionNotification: '' as Ref<Doc>
|
MentionNotification: '' as Ref<Doc>
|
||||||
},
|
},
|
||||||
|
extension: {
|
||||||
|
ActivityEmployeePresenter: '' as ComponentExtensionId
|
||||||
|
},
|
||||||
function: {
|
function: {
|
||||||
ShouldScrollToActivity: '' as Resource<() => boolean>
|
ShouldScrollToActivity: '' as Resource<() => boolean>
|
||||||
},
|
},
|
||||||
|
@ -92,10 +92,8 @@
|
|||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<svelte:fragment slot="header">
|
<svelte:fragment slot="header">
|
||||||
<div class="flex fs-title flex-gap-1">
|
<div class="flex fs-title flex-gap-1">
|
||||||
<span class="over-underline" on:click={handleMove}>{space?.name}</span>><span
|
<span class="over-underline" on:click={handleMove}>{space?.name}</span>>
|
||||||
class="over-underline"
|
<span class="over-underline" on:click={handleMove}>{state?.name}</span>
|
||||||
on:click={handleMove}>{state?.name}</span
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<svelte:fragment slot="tools">
|
<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;
|
flex-shrink: 0;
|
||||||
margin: 0.25rem 0;
|
margin: 0.25rem 0;
|
||||||
height: 1.875rem;
|
height: 1.875rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
|
||||||
&:not(:first-child)::after {
|
&:not(:first-child)::after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -126,8 +126,10 @@
|
|||||||
use:tooltip={{
|
use:tooltip={{
|
||||||
component: Label,
|
component: Label,
|
||||||
props: { label: core.string.CreatedBy }
|
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">
|
<div class="flex flex-grow min-w-0">
|
||||||
<EmployeeBox
|
<EmployeeBox
|
||||||
value={creatorPersonRef}
|
value={creatorPersonRef}
|
||||||
|
@ -102,7 +102,7 @@
|
|||||||
<div class="antiHSpacer" />
|
<div class="antiHSpacer" />
|
||||||
{:else if secondaryNotifyMarker}
|
{:else if secondaryNotifyMarker}
|
||||||
<div class="antiHSpacer" />
|
<div class="antiHSpacer" />
|
||||||
<NotifyMarker count={0} kind="secondary" size="x-small" />
|
<NotifyMarker count={0} kind="simple" />
|
||||||
<div class="antiHSpacer" />
|
<div class="antiHSpacer" />
|
||||||
{/if}
|
{/if}
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
@ -53,6 +53,8 @@ import ThreadViewPanel from './components/threads/ThreadViewPanel.svelte'
|
|||||||
import ChatWidget from './components/ChatWidget.svelte'
|
import ChatWidget from './components/ChatWidget.svelte'
|
||||||
import ChatWidgetTab from './components/ChatWidgetTab.svelte'
|
import ChatWidgetTab from './components/ChatWidgetTab.svelte'
|
||||||
import WorkbenchTabExtension from './components/WorkbenchTabExtension.svelte'
|
import WorkbenchTabExtension from './components/WorkbenchTabExtension.svelte'
|
||||||
|
import DirectMessageButton from './components/DirectMessageButton.svelte'
|
||||||
|
import EmployeePresenter from './components/ChunterEmployeePresenter.svelte'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
chunterSpaceLinkFragmentProvider,
|
chunterSpaceLinkFragmentProvider,
|
||||||
@ -181,7 +183,9 @@ export default async (): Promise<Resources> => ({
|
|||||||
JoinChannelNotificationPresenter,
|
JoinChannelNotificationPresenter,
|
||||||
ChatWidget,
|
ChatWidget,
|
||||||
ChatWidgetTab,
|
ChatWidgetTab,
|
||||||
WorkbenchTabExtension
|
WorkbenchTabExtension,
|
||||||
|
DirectMessageButton,
|
||||||
|
EmployeePresenter
|
||||||
},
|
},
|
||||||
activity: {
|
activity: {
|
||||||
ChannelCreatedMessage,
|
ChannelCreatedMessage,
|
||||||
|
@ -31,7 +31,12 @@ import { getChannelName, isThreadMessage } from './utils'
|
|||||||
import chunter from './plugin'
|
import chunter from './plugin'
|
||||||
import { threadMessagesStore } from './stores'
|
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 loc = getCurrentLocation()
|
||||||
const id = encodeObjectURI(_id, _class)
|
const id = encodeObjectURI(_id, _class)
|
||||||
|
|
||||||
@ -39,6 +44,10 @@ export function openChannel (_id: string, _class: Ref<Class<Doc>>, thread?: Ref<
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (forceApplication) {
|
||||||
|
loc.path[2] = chunterId
|
||||||
|
}
|
||||||
|
|
||||||
loc.path[3] = id
|
loc.path[3] = id
|
||||||
loc.query = { ...loc.query, message: null }
|
loc.query = { ...loc.query, message: null }
|
||||||
|
|
||||||
@ -317,7 +326,12 @@ export async function closeThreadInSidebarChannel (widget: Widget, tab: ChatWidg
|
|||||||
}, 100)
|
}, 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 client = getClient()
|
||||||
|
|
||||||
const widget = client.getModel().findAllSync(workbench.class.Widget, { _id: chunter.ids.ChatWidget })[0]
|
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,
|
_id: object?._id,
|
||||||
_class: object?._class,
|
_class: object?._class,
|
||||||
thread: message._id,
|
thread: message._id,
|
||||||
|
selectedMessageId,
|
||||||
channelName: name
|
channelName: name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -436,7 +451,7 @@ export async function locationDataResolver (loc: Location): Promise<LocationData
|
|||||||
const linkProviders = client.getModel().findAllSync(view.mixin.LinkIdProvider, {})
|
const linkProviders = client.getModel().findAllSync(view.mixin.LinkIdProvider, {})
|
||||||
const _id: Ref<Doc> | undefined = await parseLinkId(linkProviders, id, _class)
|
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)) }
|
if (object === undefined) return { name: await translate(chunter.string.Chat, {}, get(languageStore)) }
|
||||||
|
|
||||||
const titleIntl = client.getHierarchy().getClass(object._class).label
|
const titleIntl = client.getHierarchy().getClass(object._class).label
|
||||||
|
@ -251,7 +251,9 @@ export default plugin(chunterId, {
|
|||||||
},
|
},
|
||||||
function: {
|
function: {
|
||||||
CanTranslateMessage: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>,
|
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<
|
OpenChannelInSidebar: '' as Resource<
|
||||||
(
|
(
|
||||||
_id: Ref<Doc>,
|
_id: Ref<Doc>,
|
||||||
|
@ -102,6 +102,7 @@
|
|||||||
"People": "People",
|
"People": "People",
|
||||||
"For": "For",
|
"For": "For",
|
||||||
"SelectUsers": "Select users",
|
"SelectUsers": "Select users",
|
||||||
"AddGuest": "Add guest"
|
"AddGuest": "Add guest",
|
||||||
|
"ViewProfile": "View profile"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,6 +102,7 @@
|
|||||||
"People": "Personas",
|
"People": "Personas",
|
||||||
"For": "Para",
|
"For": "Para",
|
||||||
"SelectUsers": "Seleccionar usuarios",
|
"SelectUsers": "Seleccionar usuarios",
|
||||||
"AddGuest": "Añadir invitado"
|
"AddGuest": "Añadir invitado",
|
||||||
|
"ViewProfile": "Ver perfil"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -102,6 +102,7 @@
|
|||||||
"People": "Personnes",
|
"People": "Personnes",
|
||||||
"For": "Pour",
|
"For": "Pour",
|
||||||
"SelectUsers": "Sélectionner des utilisateurs",
|
"SelectUsers": "Sélectionner des utilisateurs",
|
||||||
"AddGuest": "Ajouter un invité"
|
"AddGuest": "Ajouter un invité",
|
||||||
|
"ViewProfile": "Voir le profil"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -102,6 +102,7 @@
|
|||||||
"People": "Pessoas",
|
"People": "Pessoas",
|
||||||
"For": "Para",
|
"For": "Para",
|
||||||
"SelectUsers": "Selecionar utilizadores",
|
"SelectUsers": "Selecionar utilizadores",
|
||||||
"AddGuest": "Adicionar convidado"
|
"AddGuest": "Adicionar convidado",
|
||||||
|
"ViewProfile": "Ver perfil"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -102,6 +102,7 @@
|
|||||||
"People": "Люди",
|
"People": "Люди",
|
||||||
"For": "Для",
|
"For": "Для",
|
||||||
"SelectUsers": "Выберите пользователей",
|
"SelectUsers": "Выберите пользователей",
|
||||||
"AddGuest": "Добавить гостя"
|
"AddGuest": "Добавить гостя",
|
||||||
|
"ViewProfile": "Посмотреть профиль"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,6 +102,7 @@
|
|||||||
"People": "人员",
|
"People": "人员",
|
||||||
"For": "为",
|
"For": "为",
|
||||||
"SelectUsers": "选择用户",
|
"SelectUsers": "选择用户",
|
||||||
"AddGuest": "添加访客"
|
"AddGuest": "添加访客",
|
||||||
|
"ViewProfile": "查看资料"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -186,8 +186,10 @@
|
|||||||
if (openable) {
|
if (openable) {
|
||||||
dispatch('update', 'open')
|
dispatch('update', 'open')
|
||||||
}
|
}
|
||||||
}}>{value}</span
|
}}
|
||||||
>
|
>
|
||||||
|
{value}
|
||||||
|
</span>
|
||||||
<Button
|
<Button
|
||||||
focusIndex={3}
|
focusIndex={3}
|
||||||
kind={'ghost'}
|
kind={'ghost'}
|
||||||
|
@ -2,12 +2,14 @@
|
|||||||
import { Employee, Person } from '@hcengineering/contact'
|
import { Employee, Person } from '@hcengineering/contact'
|
||||||
import { Ref, WithLookup } from '@hcengineering/core'
|
import { Ref, WithLookup } from '@hcengineering/core'
|
||||||
import { IntlString } from '@hcengineering/platform'
|
import { IntlString } from '@hcengineering/platform'
|
||||||
import ui, { IconSize } from '@hcengineering/ui'
|
import ui, { IconSize, LabelAndProps } from '@hcengineering/ui'
|
||||||
import { PersonLabelTooltip, employeeByIdStore, personByIdStore } from '..'
|
import { PersonLabelTooltip, employeeByIdStore, personByIdStore } from '..'
|
||||||
import PersonPresenter from '../components/PersonPresenter.svelte'
|
import PersonPresenter from '../components/PersonPresenter.svelte'
|
||||||
import contact from '../plugin'
|
import contact from '../plugin'
|
||||||
|
import EmployeePreviewPopup from './EmployeePreviewPopup.svelte'
|
||||||
|
|
||||||
export let value: Ref<Person> | WithLookup<Person> | null | undefined
|
export let value: Ref<Person> | WithLookup<Person> | null | undefined
|
||||||
|
export let showPopup: boolean = true
|
||||||
export let tooltipLabels: PersonLabelTooltip | undefined = undefined
|
export let tooltipLabels: PersonLabelTooltip | undefined = undefined
|
||||||
export let shouldShowAvatar: boolean = true
|
export let shouldShowAvatar: boolean = true
|
||||||
export let shouldShowName: boolean = true
|
export let shouldShowName: boolean = true
|
||||||
@ -24,16 +26,26 @@
|
|||||||
export let compact: boolean = false
|
export let compact: boolean = false
|
||||||
export let showStatus: 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 =
|
$: active = employeeValue !== undefined ? $employeeByIdStore.get(employeeValue?._id)?.active ?? false : false
|
||||||
employeeValue !== undefined ? $employeeByIdStore.get(employeeValue?._id as Ref<Employee>)?.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>
|
</script>
|
||||||
|
|
||||||
<PersonPresenter
|
<PersonPresenter
|
||||||
value={employeeValue}
|
value={employeeValue}
|
||||||
{tooltipLabels}
|
{tooltipLabels}
|
||||||
onEdit={onEmployeeEdit}
|
onEdit={onEmployeeEdit}
|
||||||
|
customTooltip={getPreviewPopup(active, employeeValue)}
|
||||||
{shouldShowAvatar}
|
{shouldShowAvatar}
|
||||||
{shouldShowName}
|
{shouldShowName}
|
||||||
{avatarSize}
|
{avatarSize}
|
||||||
|
@ -1,103 +1,150 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Employee, PersonAccount, getName, Status } from '@hcengineering/contact'
|
import { Employee } from '@hcengineering/contact'
|
||||||
import { getCurrentAccount, Ref } from '@hcengineering/core'
|
import { Class, Doc, Ref } from '@hcengineering/core'
|
||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { ModernButton, navigate, resizeObserver } from '@hcengineering/ui'
|
||||||
import Avatar from './Avatar.svelte'
|
|
||||||
import { Button, Label, resizeObserver, showPopup } from '@hcengineering/ui'
|
|
||||||
import { DocNavLink } from '@hcengineering/view-resources'
|
|
||||||
import { createEventDispatcher } from 'svelte'
|
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 contact from '../plugin'
|
||||||
import { employeeByIdStore } from '../utils'
|
import Avatar from './Avatar.svelte'
|
||||||
import EmployeeSetStatusPopup from './EmployeeSetStatusPopup.svelte'
|
import { employeeByIdStore, personAccountByPersonId, statusByUserStore } from '../utils'
|
||||||
import EmployeeStatusPresenter from './EmployeeStatusPresenter.svelte'
|
import { EmployeePresenter } from '../index'
|
||||||
import Edit from './icons/Edit.svelte'
|
|
||||||
|
|
||||||
export let employeeId: Ref<Employee>
|
export let employeeId: Ref<Employee>
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
const me = (getCurrentAccount() as PersonAccount).person
|
const hierarchy = client.getHierarchy()
|
||||||
$: 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 dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
function onEdit () {
|
let employee: Employee | undefined = undefined
|
||||||
showPopup(
|
|
||||||
EmployeeSetStatusPopup,
|
$: employee = $employeeByIdStore.get(employeeId)
|
||||||
{
|
$: accounts = $personAccountByPersonId.get(employeeId) ?? []
|
||||||
currentStatus: status
|
$: isOnline = accounts.some((account) => $statusByUserStore.get(account._id)?.online === true)
|
||||||
},
|
|
||||||
undefined,
|
// const statusesQuery = createQuery()
|
||||||
() => {},
|
// let editable = false
|
||||||
(newStatus: Status) => {
|
// let status: Status | undefined = undefined
|
||||||
if (status && newStatus) {
|
// $: editable = employeeId === me
|
||||||
client.updateDoc(contact.class.Status, status.space, status._id, { ...newStatus })
|
// statusesQuery.query(contact.class.Status, { attachedTo: employeeId }, (res) => {
|
||||||
} else if (status && !newStatus) {
|
// status = res[0]
|
||||||
client.removeDoc(contact.class.Status, status.space, status._id)
|
// })
|
||||||
} else {
|
|
||||||
client.addCollection(contact.class.Status, employee.space, employeeId, contact.mixin.Employee, 'statuses', {
|
// function setStatus (): void {
|
||||||
name: newStatus.name,
|
// if (!employee) return
|
||||||
dueDate: newStatus.dueDate
|
// showPopup(
|
||||||
})
|
// EmployeeSetStatusPopup,
|
||||||
}
|
// {
|
||||||
}
|
// currentStatus: status
|
||||||
)
|
// },
|
||||||
dispatch('close')
|
// 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>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="antiPopup p-4 flex-col"
|
class="root flex-col"
|
||||||
use:resizeObserver={() => {
|
use:resizeObserver={() => {
|
||||||
dispatch('changeContent')
|
dispatch('changeContent')
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{#if employee}
|
{#if employee}
|
||||||
<div class="flex-col-center pb-2">
|
<div class="flex-presenter flex-gap-2 p-2">
|
||||||
<Avatar size={'x-large'} person={employee} name={employee.name} />
|
<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>
|
||||||
<div class="pb-2">{getName(client.getHierarchy(), employee)}</div>
|
<div class="separator" />
|
||||||
<DocNavLink object={employee}>
|
<div class="flex-presenter flex-gap-2 p-2">
|
||||||
<Label label={contact.string.ViewFullProfile} />
|
<ComponentExtensions extension={contact.extension.EmployeePopupActions} props={{ employee }} />
|
||||||
</DocNavLink>
|
<ModernButton
|
||||||
{#if status}
|
label={contact.string.ViewProfile}
|
||||||
<div class="pb-2">
|
icon={contact.icon.Person}
|
||||||
<Label label={contact.string.Status} />
|
size="small"
|
||||||
<div class="flex-row-stretch statusContainer">
|
iconSize="small"
|
||||||
<div class="pr-2">
|
on:click={viewProfile}
|
||||||
<EmployeeStatusPresenter {employee} withTooltip={false} />
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#if editable}
|
|
||||||
<div class="setStatusButton">
|
<!--{#if status}-->
|
||||||
<Button icon={Edit} title={contact.string.SetStatus} on:click={onEdit} />
|
<!-- <div class="pb-2">-->
|
||||||
</div>
|
<!-- <Label label={contact.string.Status} />-->
|
||||||
{/if}
|
<!-- <div class="flex-row-stretch statusContainer">-->
|
||||||
</div>
|
<!-- <div class="pr-2">-->
|
||||||
</div>
|
<!-- <EmployeeStatusPresenter {employee} withTooltip={false} />-->
|
||||||
{:else if editable}
|
<!-- </div>-->
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- {#if editable}-->
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- <div class="setStatusButton">-->
|
||||||
<div class="flex-row-stretch over-underline pb-2" on:click={onEdit}>
|
<!-- <Button icon={Edit} title={contact.string.SetStatus} on:click={setStatus} />-->
|
||||||
<Label label={contact.string.SetStatus} />
|
<!-- </div>-->
|
||||||
</div>
|
<!-- {/if}-->
|
||||||
{/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}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.statusContainer {
|
.root {
|
||||||
.setStatusButton {
|
display: flex;
|
||||||
opacity: 0;
|
flex-direction: column;
|
||||||
}
|
width: auto;
|
||||||
|
min-height: 0;
|
||||||
&:hover .setStatusButton {
|
min-width: 0;
|
||||||
opacity: 1;
|
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>
|
</style>
|
||||||
|
@ -47,9 +47,9 @@
|
|||||||
<div class="icon circle">
|
<div class="icon circle">
|
||||||
<Company size={'small'} />
|
<Company size={'small'} />
|
||||||
</div>
|
</div>
|
||||||
<span class="overflow-label label" class:no-underline={noUnderline || disabled} class:fs-bold={accent}
|
<span class="overflow-label label" class:no-underline={noUnderline || disabled} class:fs-bold={accent}>
|
||||||
>{value.name}</span
|
{value.name}
|
||||||
>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</DocNavLink>
|
</DocNavLink>
|
||||||
{:else if type === 'text'}
|
{:else if type === 'text'}
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
export let defaultName: IntlString | undefined = ui.string.NotSelected
|
export let defaultName: IntlString | undefined = ui.string.NotSelected
|
||||||
export let statusLabel: IntlString | undefined = undefined
|
export let statusLabel: IntlString | undefined = undefined
|
||||||
export let tooltipLabels: PersonLabelTooltip | undefined = undefined
|
export let tooltipLabels: PersonLabelTooltip | undefined = undefined
|
||||||
|
export let customTooltip: LabelAndProps | undefined = undefined
|
||||||
export let avatarSize: IconSize = 'x-small'
|
export let avatarSize: IconSize = 'x-small'
|
||||||
export let onEdit: ((event: MouseEvent) => void) | undefined = undefined
|
export let onEdit: ((event: MouseEvent) => void) | undefined = undefined
|
||||||
// export let element: HTMLElement | undefined = undefined
|
// export let element: HTMLElement | undefined = undefined
|
||||||
@ -81,7 +82,7 @@
|
|||||||
|
|
||||||
{#if value || shouldShowPlaceholder}
|
{#if value || shouldShowPlaceholder}
|
||||||
<PersonContent
|
<PersonContent
|
||||||
showTooltip={getTooltip(tooltipLabels, personValue)}
|
showTooltip={customTooltip ?? getTooltip(tooltipLabels, personValue)}
|
||||||
value={personValue}
|
value={personValue}
|
||||||
{inline}
|
{inline}
|
||||||
{onEdit}
|
{onEdit}
|
||||||
|
@ -37,6 +37,7 @@ import core, {
|
|||||||
type Doc,
|
type Doc,
|
||||||
type DocumentQuery,
|
type DocumentQuery,
|
||||||
getCurrentAccount,
|
getCurrentAccount,
|
||||||
|
groupByArray,
|
||||||
type Hierarchy,
|
type Hierarchy,
|
||||||
type IdMap,
|
type IdMap,
|
||||||
matchQuery,
|
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]))
|
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 statusByUserStore = writable<Map<Ref<Account>, UserStatus>>(new Map())
|
||||||
|
|
||||||
export const personByIdStore = derived([personAccountPersonByIdStore, employeeByIdStore], (vals) => {
|
export const personByIdStore = derived([personAccountPersonByIdStore, employeeByIdStore], (vals) => {
|
||||||
|
@ -31,7 +31,7 @@ import {
|
|||||||
import type { Asset, Metadata, Plugin, Resource } from '@hcengineering/platform'
|
import type { Asset, Metadata, Plugin, Resource } from '@hcengineering/platform'
|
||||||
import { IntlString, plugin } from '@hcengineering/platform'
|
import { IntlString, plugin } from '@hcengineering/platform'
|
||||||
import { TemplateField, TemplateFieldCategory } from '@hcengineering/templates'
|
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'
|
import { Action, FilterMode, Viewlet } from '@hcengineering/view'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -301,7 +301,8 @@ export const contactPlugin = plugin(contactId, {
|
|||||||
Members: '' as IntlString,
|
Members: '' as IntlString,
|
||||||
Contacts: '' as IntlString,
|
Contacts: '' as IntlString,
|
||||||
Employees: '' as IntlString,
|
Employees: '' as IntlString,
|
||||||
Persons: '' as IntlString
|
Persons: '' as IntlString,
|
||||||
|
ViewProfile: '' as IntlString
|
||||||
},
|
},
|
||||||
viewlet: {
|
viewlet: {
|
||||||
TableMember: '' as Ref<Viewlet>,
|
TableMember: '' as Ref<Viewlet>,
|
||||||
@ -332,6 +333,9 @@ export const contactPlugin = plugin(contactId, {
|
|||||||
},
|
},
|
||||||
ids: {
|
ids: {
|
||||||
MentionCommonNotificationType: '' as Ref<Doc>
|
MentionCommonNotificationType: '' as Ref<Doc>
|
||||||
|
},
|
||||||
|
extension: {
|
||||||
|
EmployeePopupActions: '' as ComponentExtensionId
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -127,7 +127,8 @@ function getDocumentLinkId (doc: Document): string {
|
|||||||
return `${slug}---${doc._id}`
|
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('---')
|
const parts = shortLink.split('---')
|
||||||
if (parts.length > 1) {
|
if (parts.length > 1) {
|
||||||
return parts[parts.length - 1] as Ref<ControlledDocument>
|
return parts[parts.length - 1] as Ref<ControlledDocument>
|
||||||
|
@ -19,16 +19,21 @@ import { hljsDefineSvelte } from './languages/svelte-hljs'
|
|||||||
hljs.registerLanguage('svelte', hljsDefineSvelte)
|
hljs.registerLanguage('svelte', hljsDefineSvelte)
|
||||||
|
|
||||||
export interface HighlightOptions {
|
export interface HighlightOptions {
|
||||||
|
auto?: boolean
|
||||||
language: string | undefined
|
language: string | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export function highlightText (text: string, options: HighlightOptions): string {
|
export function highlightText (text: string, options: HighlightOptions): string {
|
||||||
// We should always use highlighter because it sanitizes the input
|
// We should always use highlighter because it sanitizes the input
|
||||||
// We have to always use highlighter to ensure that the input is sanitized
|
// 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 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)
|
return normalizeHighlightTags(highlighted)
|
||||||
}
|
}
|
||||||
|
@ -39,9 +39,9 @@
|
|||||||
{#if shouldShowAvatar}
|
{#if shouldShowAvatar}
|
||||||
<div class="icon"><Icon icon={lead.icon.Lead} size={'small'} /></div>
|
<div class="icon"><Icon icon={lead.icon.Lead} size={'small'} /></div>
|
||||||
{/if}
|
{/if}
|
||||||
<span class="label nowrap" class:no-underline={noUnderline || disabled} class:fs-bold={accent}
|
<span class="label nowrap" class:no-underline={noUnderline || disabled} class:fs-bold={accent}>
|
||||||
>{value.identifier}</span
|
{value.identifier}
|
||||||
>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</DocNavLink>
|
</DocNavLink>
|
||||||
{:else if type === 'text'}
|
{:else if type === 'text'}
|
||||||
|
@ -164,8 +164,10 @@
|
|||||||
setMetadataLocalStorage(login.metadata.LoginEndpoint, null)
|
setMetadataLocalStorage(login.metadata.LoginEndpoint, null)
|
||||||
setMetadataLocalStorage(login.metadata.LoginEmail, null)
|
setMetadataLocalStorage(login.metadata.LoginEmail, null)
|
||||||
goTo('login')
|
goTo('login')
|
||||||
}}><Label label={login.string.ChangeAccount} /></NavLink
|
}}
|
||||||
>
|
>
|
||||||
|
<Label label={login.string.ChangeAccount} />
|
||||||
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/await}
|
{/await}
|
||||||
|
@ -361,7 +361,8 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
width: 15rem;
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
&:not(.isDock) {
|
&:not(.isDock) {
|
||||||
|
@ -20,12 +20,12 @@
|
|||||||
import love from '../plugin'
|
import love from '../plugin'
|
||||||
import VideoPopup from './VideoPopup.svelte'
|
import VideoPopup from './VideoPopup.svelte'
|
||||||
|
|
||||||
export let widgetState: WidgetState
|
export let widgetState: WidgetState | undefined
|
||||||
|
|
||||||
let room: Ref<TypeRoom> | undefined = 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)
|
closeWidget(love.ids.VideoWidget)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let count: number = 0
|
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'
|
export let size: 'xx-small' | 'x-small' | 'small' | 'medium' = 'small'
|
||||||
|
|
||||||
const maxNumber = 9
|
const maxNumber = 9
|
||||||
@ -30,10 +30,6 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if kind === 'secondary'}
|
|
||||||
<div class="notifyMarker {size} {kind}" />
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if kind === 'simple'}
|
{#if kind === 'simple'}
|
||||||
<div class="notifyMarker {size} {kind}" />
|
<div class="notifyMarker {size} {kind}" />
|
||||||
{/if}
|
{/if}
|
||||||
@ -53,10 +49,6 @@
|
|||||||
color: var(--global-on-accent-TextColor);
|
color: var(--global-on-accent-TextColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.secondary {
|
|
||||||
background-color: var(--global-subtle-BackgroundColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.xx-small {
|
&.xx-small {
|
||||||
width: 0.5rem;
|
width: 0.5rem;
|
||||||
height: 0.5rem;
|
height: 0.5rem;
|
||||||
|
@ -192,7 +192,7 @@
|
|||||||
|
|
||||||
if (thread !== undefined) {
|
if (thread !== undefined) {
|
||||||
const fn = await getResource(chunter.function.OpenThreadInSidebar)
|
const fn = await getResource(chunter.function.OpenThreadInSidebar)
|
||||||
void fn(thread)
|
void fn(thread, undefined, undefined, selectedMessageId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedMessageId !== undefined) {
|
if (selectedMessageId !== undefined) {
|
||||||
|
@ -107,6 +107,7 @@
|
|||||||
bind:this={list}
|
bind:this={list}
|
||||||
bind:selection={listSelection}
|
bind:selection={listSelection}
|
||||||
count={displayData.length}
|
count={displayData.length}
|
||||||
|
items={displayData}
|
||||||
highlightIndex={displayData.findIndex(([context]) => context === selectedContext)}
|
highlightIndex={displayData.findIndex(([context]) => context === selectedContext)}
|
||||||
noScroll
|
noScroll
|
||||||
minHeight="5.625rem"
|
minHeight="5.625rem"
|
||||||
|
@ -70,12 +70,12 @@
|
|||||||
<tr class="antiTable-body__row">
|
<tr class="antiTable-body__row">
|
||||||
<td><PersonRefPresenter value={requested.employee} /></td>
|
<td><PersonRefPresenter value={requested.employee} /></td>
|
||||||
<td><BooleanIcon value={requested.decision} /></td>
|
<td><BooleanIcon value={requested.decision} /></td>
|
||||||
<td
|
<td>
|
||||||
>{#if requested.comment}
|
{#if requested.comment}
|
||||||
<ShowMore limit={126} fixed>
|
<ShowMore limit={126} fixed>
|
||||||
<MessageViewer message={requested.comment.message} />
|
<MessageViewer message={requested.comment.message} />
|
||||||
</ShowMore>{/if}</td
|
</ShowMore>{/if}
|
||||||
>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -24,5 +24,7 @@
|
|||||||
|
|
||||||
<span class="label nowrap">
|
<span class="label nowrap">
|
||||||
{#if shortLabel}
|
{#if shortLabel}
|
||||||
{shortLabel}-{/if}{value.name}</span
|
{shortLabel}-
|
||||||
>
|
{/if}
|
||||||
|
{value.name}
|
||||||
|
</span>
|
||||||
|
@ -33,9 +33,9 @@
|
|||||||
<div class="icon">
|
<div class="icon">
|
||||||
<Icon icon={task.icon.Task} size={'small'} />
|
<Icon icon={task.icon.Task} size={'small'} />
|
||||||
</div>
|
</div>
|
||||||
<span class="label nowrap"
|
<span class="label nowrap">
|
||||||
>{#if shortLabel}{shortLabel}-{/if}{value.number}</span
|
{#if shortLabel}{shortLabel}-{/if}{value.number}
|
||||||
>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</DocNavLink>
|
</DocNavLink>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -112,24 +112,24 @@
|
|||||||
maxDigitsAfterPoint={3}
|
maxDigitsAfterPoint={3}
|
||||||
kind={'editbox'}
|
kind={'editbox'}
|
||||||
/>
|
/>
|
||||||
<Button kind={'link-bordered'} on:click={() => (data.value = 1)}
|
<Button kind={'link-bordered'} on:click={() => (data.value = 1)}>
|
||||||
><span slot="content">1<Label label={tracker.string.HourLabel} /></span></Button
|
<span slot="content">1<Label label={tracker.string.HourLabel} /></span>
|
||||||
>
|
</Button>
|
||||||
<Button kind={'link-bordered'} on:click={() => (data.value = 2)}
|
<Button kind={'link-bordered'} on:click={() => (data.value = 2)}>
|
||||||
><span slot="content">2<Label label={tracker.string.HourLabel} /></span></Button
|
<span slot="content">2<Label label={tracker.string.HourLabel} /></span>
|
||||||
>
|
</Button>
|
||||||
<Button kind={'link-bordered'} on:click={() => (data.value = 4)}
|
<Button kind={'link-bordered'} on:click={() => (data.value = 4)}>
|
||||||
><span slot="content">4<Label label={tracker.string.HourLabel} /></span></Button
|
<span slot="content">4<Label label={tracker.string.HourLabel} /></span>
|
||||||
>
|
</Button>
|
||||||
<Button kind={'link-bordered'} on:click={() => (data.value = 6)}
|
<Button kind={'link-bordered'} on:click={() => (data.value = 6)}>
|
||||||
><span slot="content">6<Label label={tracker.string.HourLabel} /></span></Button
|
<span slot="content">6<Label label={tracker.string.HourLabel} /></span>
|
||||||
>
|
</Button>
|
||||||
<Button kind={'link-bordered'} on:click={() => (data.value = 7)}
|
<Button kind={'link-bordered'} on:click={() => (data.value = 7)}>
|
||||||
><span slot="content">7<Label label={tracker.string.HourLabel} /></span></Button
|
<span slot="content">7<Label label={tracker.string.HourLabel} /></span>
|
||||||
>
|
</Button>
|
||||||
<Button kind={'link-bordered'} on:click={() => (data.value = 8)}
|
<Button kind={'link-bordered'} on:click={() => (data.value = 8)}>
|
||||||
><span slot="content">8<Label label={tracker.string.HourLabel} /></span></Button
|
<span slot="content">8<Label label={tracker.string.HourLabel} /></span>
|
||||||
>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<EditBox bind:value={data.description} placeholder={tracker.string.TimeSpendReportDescription} kind={'editbox'} />
|
<EditBox bind:value={data.description} placeholder={tracker.string.TimeSpendReportDescription} kind={'editbox'} />
|
||||||
<svelte:fragment slot="pool">
|
<svelte:fragment slot="pool">
|
||||||
|
@ -109,6 +109,6 @@
|
|||||||
<DatePresenter value={report.date} kind={'ghost'} size={'small'} />
|
<DatePresenter value={report.date} kind={'ghost'} size={'small'} />
|
||||||
</FixedColumn>
|
</FixedColumn>
|
||||||
</div>
|
</div>
|
||||||
</div></svelte:fragment
|
</div>
|
||||||
>
|
</svelte:fragment>
|
||||||
</ListView>
|
</ListView>
|
||||||
|
@ -33,9 +33,7 @@
|
|||||||
preferences = res
|
preferences = res
|
||||||
})
|
})
|
||||||
|
|
||||||
$: widgetId = $sidebarStore.widget
|
$: size = $sidebarStore.variant === SidebarVariant.MINI ? 'mini' : undefined
|
||||||
$: widget = widgets.find((it) => it._id === widgetId)
|
|
||||||
$: size = $sidebarStore.variant === SidebarVariant.MINI ? 'mini' : widget?.size
|
|
||||||
|
|
||||||
function txListener (tx: Tx): void {
|
function txListener (tx: Tx): void {
|
||||||
if (tx._class === workbench.class.TxSidebarEvent) {
|
if (tx._class === workbench.class.TxSidebarEvent) {
|
||||||
@ -79,17 +77,5 @@
|
|||||||
min-width: 3.5rem !important;
|
min-width: 3.5rem !important;
|
||||||
max-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>
|
</style>
|
||||||
|
@ -71,7 +71,6 @@ export interface Widget extends Doc {
|
|||||||
label: IntlString
|
label: IntlString
|
||||||
icon: Asset
|
icon: Asset
|
||||||
type: WidgetType
|
type: WidgetType
|
||||||
size?: 'small' | 'medium'
|
|
||||||
|
|
||||||
component: AnyComponent
|
component: AnyComponent
|
||||||
tabComponent?: AnyComponent
|
tabComponent?: AnyComponent
|
||||||
|
@ -76,9 +76,7 @@ export class DocumentsPage extends CalendarPage {
|
|||||||
|
|
||||||
async changeSpaceInCreateDocumentForm (space: string): Promise<void> {
|
async changeSpaceInCreateDocumentForm (space: string): Promise<void> {
|
||||||
await this.changeSpaceButton.click()
|
await this.changeSpaceButton.click()
|
||||||
await this.page
|
await this.page.locator(`div.selectPopup >> div.list-container >> text=${space}`).click({ force: true })
|
||||||
.locator(`div.list-container.flex-col.flex-grow.svelte-15na0wa >> text=${space}`)
|
|
||||||
.click({ force: true })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async createTemplate (title: string, description: string, category: string, spaceName: string): Promise<void> {
|
async createTemplate (title: string, description: string, category: string, spaceName: string): Promise<void> {
|
||||||
|
@ -156,8 +156,8 @@ export class CommentSyncManager implements DocSyncManager {
|
|||||||
derivedClient: TxOperations,
|
derivedClient: TxOperations,
|
||||||
integration: IntegrationContainer
|
integration: IntegrationContainer
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { repository } = await this.provider.getProjectAndRepository(event.repository.node_id)
|
const { repository: repo } = await this.provider.getProjectAndRepository(event.repository.node_id)
|
||||||
if (repository === undefined) {
|
if (repo === undefined) {
|
||||||
this.ctx.info('No project for repository', {
|
this.ctx.info('No project for repository', {
|
||||||
repository: event.repository,
|
repository: event.repository,
|
||||||
workspace: this.provider.getWorkspaceId().name
|
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
|
const account = (await this.provider.getAccountU(event.sender))?._id ?? core.account.System
|
||||||
switch (event.action) {
|
switch (event.action) {
|
||||||
case 'created': {
|
case 'created': {
|
||||||
await this.createSyncData(event, derivedClient, repository)
|
await this.createSyncData(event, derivedClient, repo)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'deleted': {
|
case 'deleted': {
|
||||||
const syncData = await this.client.findOne(github.class.DocSyncInfo, {
|
const syncData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||||
|
space: repo.githubProject as Ref<GithubProject>,
|
||||||
url: (event.comment.url ?? '').toLowerCase()
|
url: (event.comment.url ?? '').toLowerCase()
|
||||||
})
|
})
|
||||||
if (syncData !== undefined) {
|
if (syncData !== undefined) {
|
||||||
@ -183,6 +184,7 @@ export class CommentSyncManager implements DocSyncManager {
|
|||||||
}
|
}
|
||||||
case 'edited': {
|
case 'edited': {
|
||||||
const commentData = await this.client.findOne(github.class.DocSyncInfo, {
|
const commentData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||||
|
space: repo.githubProject as Ref<GithubProject>,
|
||||||
url: (event.comment.url ?? '').toLowerCase()
|
url: (event.comment.url ?? '').toLowerCase()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -221,6 +223,7 @@ export class CommentSyncManager implements DocSyncManager {
|
|||||||
repo: GithubIntegrationRepository
|
repo: GithubIntegrationRepository
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const commentData = await this.client.findOne(github.class.DocSyncInfo, {
|
const commentData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||||
|
space: repo.githubProject as Ref<GithubProject>,
|
||||||
url: (createdEvent.comment.url ?? '').toLowerCase()
|
url: (createdEvent.comment.url ?? '').toLowerCase()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -266,6 +269,7 @@ export class CommentSyncManager implements DocSyncManager {
|
|||||||
if (parent === undefined) {
|
if (parent === undefined) {
|
||||||
// Find parent by issue url
|
// Find parent by issue url
|
||||||
parent = await this.client.findOne(github.class.DocSyncInfo, {
|
parent = await this.client.findOne(github.class.DocSyncInfo, {
|
||||||
|
space: container.project._id,
|
||||||
url: (comment.html_url.split('#')?.[0] ?? '').toLowerCase()
|
url: (comment.html_url.split('#')?.[0] ?? '').toLowerCase()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -201,6 +201,7 @@ export abstract class IssueSyncManagerBase {
|
|||||||
}
|
}
|
||||||
)) as any
|
)) as any
|
||||||
const syncData = await this.client.findOne(github.class.DocSyncInfo, {
|
const syncData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||||
|
space: prj._id,
|
||||||
url: (actualContent.node.content.url ?? '').toLowerCase()
|
url: (actualContent.node.content.url ?? '').toLowerCase()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -353,7 +354,8 @@ export abstract class IssueSyncManagerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
syncData =
|
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) {
|
if (syncData !== undefined) {
|
||||||
const doc: Issue | undefined = await this.client.findOne<Issue>(syncData.objectClass, {
|
const doc: Issue | undefined = await this.client.findOne<Issue>(syncData.objectClass, {
|
||||||
@ -1262,7 +1264,10 @@ export abstract class IssueSyncManagerBase {
|
|||||||
err: any,
|
err: any,
|
||||||
_class: Ref<Class<Doc>> = tracker.class.Issue
|
_class: Ref<Class<Doc>> = tracker.class.Issue
|
||||||
): Promise<void> {
|
): 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) {
|
if (syncData === undefined) {
|
||||||
await derivedClient?.createDoc(github.class.DocSyncInfo, repo.githubProject as Ref<GithubProject>, {
|
await derivedClient?.createDoc(github.class.DocSyncInfo, repo.githubProject as Ref<GithubProject>, {
|
||||||
url,
|
url,
|
||||||
|
@ -203,6 +203,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
|
|||||||
}
|
}
|
||||||
case 'deleted': {
|
case 'deleted': {
|
||||||
const syncData = await this.client.findOne(github.class.DocSyncInfo, {
|
const syncData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||||
|
space: repo.githubProject as Ref<GithubProject>,
|
||||||
url: (event.issue.html_url ?? '').toLowerCase()
|
url: (event.issue.html_url ?? '').toLowerCase()
|
||||||
})
|
})
|
||||||
if (syncData !== undefined) {
|
if (syncData !== undefined) {
|
||||||
@ -291,6 +292,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
|
|||||||
repo: GithubIntegrationRepository
|
repo: GithubIntegrationRepository
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const issueSyncData = await this.client.findOne(github.class.DocSyncInfo, {
|
const issueSyncData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||||
|
space: repo.githubProject as Ref<GithubProject>,
|
||||||
url: (issueExternal.url ?? '').toLowerCase()
|
url: (issueExternal.url ?? '').toLowerCase()
|
||||||
})
|
})
|
||||||
if (issueSyncData === undefined) {
|
if (issueSyncData === undefined) {
|
||||||
@ -452,7 +454,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.provider.doSyncFor(attachedDocs)
|
await this.provider.doSyncFor(attachedDocs, container.project)
|
||||||
for (const child of attachedDocs) {
|
for (const child of attachedDocs) {
|
||||||
await derivedClient.update(child, { createId })
|
await derivedClient.update(child, { createId })
|
||||||
}
|
}
|
||||||
|
@ -302,6 +302,7 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
|
|||||||
}
|
}
|
||||||
case 'synchronize': {
|
case 'synchronize': {
|
||||||
const syncData = await this.client.findOne(github.class.DocSyncInfo, {
|
const syncData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||||
|
space: repo.githubProject as Ref<GithubProject>,
|
||||||
url: (externalData.url ?? '').toLowerCase()
|
url: (externalData.url ?? '').toLowerCase()
|
||||||
})
|
})
|
||||||
if (syncData !== undefined) {
|
if (syncData !== undefined) {
|
||||||
|
@ -213,6 +213,7 @@ export class ReviewCommentSyncManager implements DocSyncManager {
|
|||||||
}
|
}
|
||||||
case 'deleted': {
|
case 'deleted': {
|
||||||
const reviewData = await this.client.findOne(github.class.DocSyncInfo, {
|
const reviewData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||||
|
space: repo.githubProject as Ref<GithubProject>,
|
||||||
url: (event.comment.html_url ?? '').toLowerCase()
|
url: (event.comment.html_url ?? '').toLowerCase()
|
||||||
})
|
})
|
||||||
if (reviewData !== undefined) {
|
if (reviewData !== undefined) {
|
||||||
@ -232,6 +233,7 @@ export class ReviewCommentSyncManager implements DocSyncManager {
|
|||||||
}
|
}
|
||||||
case 'edited': {
|
case 'edited': {
|
||||||
const reviewData = await this.client.findOne(github.class.DocSyncInfo, {
|
const reviewData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||||
|
space: repo.githubProject as Ref<GithubProject>,
|
||||||
url: (event.comment.html_url ?? '').toLowerCase()
|
url: (event.comment.html_url ?? '').toLowerCase()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -280,6 +282,7 @@ export class ReviewCommentSyncManager implements DocSyncManager {
|
|||||||
externalData: ReviewCommentExternalData
|
externalData: ReviewCommentExternalData
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const reviewData = await this.client.findOne(github.class.DocSyncInfo, {
|
const reviewData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||||
|
space: repo.githubProject as Ref<GithubProject>,
|
||||||
url: (createdEvent.comment.html_url ?? '').toLowerCase()
|
url: (createdEvent.comment.html_url ?? '').toLowerCase()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -331,9 +334,10 @@ export class ReviewCommentSyncManager implements DocSyncManager {
|
|||||||
|
|
||||||
if (info.reviewThreadId === undefined && reviewComment.replyTo?.url !== undefined) {
|
if (info.reviewThreadId === undefined && reviewComment.replyTo?.url !== undefined) {
|
||||||
const rthread = await derivedClient.findOne(github.class.GithubReviewComment, {
|
const rthread = await derivedClient.findOne(github.class.GithubReviewComment, {
|
||||||
|
space: container.project._id,
|
||||||
url: reviewComment.replyTo?.url?.toLowerCase()
|
url: reviewComment.replyTo?.url?.toLowerCase()
|
||||||
})
|
})
|
||||||
if (rthread !== undefined) {
|
if (rthread !== undefined && info.reviewThreadId !== rthread.reviewThreadId) {
|
||||||
info.reviewThreadId = rthread.reviewThreadId
|
info.reviewThreadId = rthread.reviewThreadId
|
||||||
await derivedClient.update(info, { reviewThreadId: info.reviewThreadId })
|
await derivedClient.update(info, { reviewThreadId: info.reviewThreadId })
|
||||||
}
|
}
|
||||||
@ -519,8 +523,10 @@ export class ReviewCommentSyncManager implements DocSyncManager {
|
|||||||
external: reviewExternal,
|
external: reviewExternal,
|
||||||
current: existing,
|
current: existing,
|
||||||
repository: repo._id,
|
repository: repo._id,
|
||||||
|
parent: parent.url.toLocaleLowerCase(),
|
||||||
needSync: githubSyncVersion,
|
needSync: githubSyncVersion,
|
||||||
externalVersion: githubExternalSyncVersion
|
externalVersion: githubExternalSyncVersion,
|
||||||
|
reviewThreadId: info.reviewThreadId ?? existingReview.reviewThreadId
|
||||||
}
|
}
|
||||||
// We need to update in current promise, to prevent event changes.
|
// We need to update in current promise, to prevent event changes.
|
||||||
await derivedClient.update(info, upd)
|
await derivedClient.update(info, upd)
|
||||||
|
@ -205,6 +205,7 @@ export class ReviewThreadSyncManager implements DocSyncManager {
|
|||||||
case 'unresolved': {
|
case 'unresolved': {
|
||||||
const isResolved = event.action === 'resolved'
|
const isResolved = event.action === 'resolved'
|
||||||
const reviewData = await this.client.findOne(github.class.DocSyncInfo, {
|
const reviewData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||||
|
space: repo.githubProject as Ref<GithubProject>,
|
||||||
url: event.thread.node_id.toLocaleLowerCase()
|
url: event.thread.node_id.toLocaleLowerCase()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -241,6 +242,7 @@ export class ReviewThreadSyncManager implements DocSyncManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const reviewPR = await this.client.findOne(github.class.DocSyncInfo, {
|
const reviewPR = await this.client.findOne(github.class.DocSyncInfo, {
|
||||||
|
space: repo.githubProject as Ref<GithubProject>,
|
||||||
url: (reviewData.parent ?? '').toLowerCase()
|
url: (reviewData.parent ?? '').toLowerCase()
|
||||||
})
|
})
|
||||||
if (reviewPR !== undefined) {
|
if (reviewPR !== undefined) {
|
||||||
@ -516,6 +518,7 @@ export class ReviewThreadSyncManager implements DocSyncManager {
|
|||||||
.map((it) => (it.parent ?? '').toLowerCase())
|
.map((it) => (it.parent ?? '').toLowerCase())
|
||||||
.filter((it, idx, arr) => it != null && arr.indexOf(it) === idx)
|
.filter((it, idx, arr) => it != null && arr.indexOf(it) === idx)
|
||||||
const parents = await derivedClient.findAll(github.class.DocSyncInfo, {
|
const parents = await derivedClient.findAll(github.class.DocSyncInfo, {
|
||||||
|
space: repo.githubProject as Ref<GithubProject>,
|
||||||
url: {
|
url: {
|
||||||
$in: allParents
|
$in: allParents
|
||||||
}
|
}
|
||||||
|
@ -198,6 +198,7 @@ export class ReviewSyncManager implements DocSyncManager {
|
|||||||
await this.createSyncData(event, derivedClient, repo, externalData)
|
await this.createSyncData(event, derivedClient, repo, externalData)
|
||||||
|
|
||||||
const parentDoc = await this.client.findOne(github.class.DocSyncInfo, {
|
const parentDoc = await this.client.findOne(github.class.DocSyncInfo, {
|
||||||
|
space: repo.githubProject as Ref<GithubProject>,
|
||||||
url: (event.pull_request.html_url ?? '').toLowerCase()
|
url: (event.pull_request.html_url ?? '').toLowerCase()
|
||||||
})
|
})
|
||||||
if (parentDoc !== undefined) {
|
if (parentDoc !== undefined) {
|
||||||
@ -211,6 +212,7 @@ export class ReviewSyncManager implements DocSyncManager {
|
|||||||
}
|
}
|
||||||
case 'dismissed': {
|
case 'dismissed': {
|
||||||
const reviewData = await this.client.findOne(github.class.DocSyncInfo, {
|
const reviewData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||||
|
space: repo.githubProject as Ref<GithubProject>,
|
||||||
url: (event.review.html_url ?? '').toLowerCase()
|
url: (event.review.html_url ?? '').toLowerCase()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -254,6 +256,7 @@ export class ReviewSyncManager implements DocSyncManager {
|
|||||||
externalData: ReviewExternalData
|
externalData: ReviewExternalData
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const reviewData = await this.client.findOne(github.class.DocSyncInfo, {
|
const reviewData = await this.client.findOne(github.class.DocSyncInfo, {
|
||||||
|
space: repo.githubProject as Ref<GithubProject>,
|
||||||
url: (createdEvent.review.html_url ?? '').toLowerCase()
|
url: (createdEvent.review.html_url ?? '').toLowerCase()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -340,6 +340,7 @@ export async function syncDerivedDocuments<T extends { url: string }> (
|
|||||||
extra?: any
|
extra?: any
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const childDocsOfClass = await derivedClient.findAll(github.class.DocSyncInfo, {
|
const childDocsOfClass = await derivedClient.findAll(github.class.DocSyncInfo, {
|
||||||
|
space: prj._id,
|
||||||
objectClass,
|
objectClass,
|
||||||
parent: (parentDoc.url ?? '').toLowerCase(),
|
parent: (parentDoc.url ?? '').toLowerCase(),
|
||||||
...query
|
...query
|
||||||
@ -352,7 +353,7 @@ export async function syncDerivedDocuments<T extends { url: string }> (
|
|||||||
if (existing === undefined) {
|
if (existing === undefined) {
|
||||||
await derivedClient.createDoc<DocSyncInfo>(github.class.DocSyncInfo, prj._id, {
|
await derivedClient.createDoc<DocSyncInfo>(github.class.DocSyncInfo, prj._id, {
|
||||||
objectClass,
|
objectClass,
|
||||||
url: r.url.toLowerCase(),
|
url: (r.url ?? '').toLowerCase(),
|
||||||
needSync: '', // we need to sync to retrieve patch in background
|
needSync: '', // we need to sync to retrieve patch in background
|
||||||
githubNumber: 0,
|
githubNumber: 0,
|
||||||
repository: repo._id,
|
repository: repo._id,
|
||||||
|
@ -111,7 +111,7 @@ export interface IntegrationManager {
|
|||||||
event: T
|
event: T
|
||||||
) => Promise<void>
|
) => Promise<void>
|
||||||
|
|
||||||
doSyncFor: (docs: DocSyncInfo[]) => Promise<void>
|
doSyncFor: (docs: DocSyncInfo[], project: GithubProject) => Promise<void>
|
||||||
getWorkspaceId: () => WorkspaceIdWithUrl
|
getWorkspaceId: () => WorkspaceIdWithUrl
|
||||||
getBranding: () => Branding | null
|
getBranding: () => Branding | null
|
||||||
|
|
||||||
|
@ -1114,7 +1114,7 @@ export class GithubWorker implements IntegrationManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async performSync (projects: GithubProject[], repositories: GithubIntegrationRepository[]): Promise<boolean> {
|
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 _repositories = repositories.map((it) => it._id)
|
||||||
|
|
||||||
const docs = await this.ctx.with(
|
const docs = await this.ctx.with(
|
||||||
@ -1126,7 +1126,7 @@ export class GithubWorker implements IntegrationManager {
|
|||||||
{
|
{
|
||||||
needSync: { $ne: githubSyncVersion },
|
needSync: { $ne: githubSyncVersion },
|
||||||
externalVersion: { $in: [githubExternalSyncVersion, '#'] },
|
externalVersion: { $in: [githubExternalSyncVersion, '#'] },
|
||||||
space: { $in: _projects },
|
space: { $in: Array.from(_projects.keys()) },
|
||||||
repository: { $in: [null, ..._repositories] }
|
repository: { $in: [null, ..._repositories] }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1142,18 +1142,21 @@ export class GithubWorker implements IntegrationManager {
|
|||||||
this.previousWait += docs.length
|
this.previousWait += docs.length
|
||||||
this.ctx.info('Syncing', { docs: docs.length, workspace: this.workspace.name })
|
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
|
return docs.length !== 0
|
||||||
}
|
}
|
||||||
|
|
||||||
async doSyncFor (docs: DocSyncInfo[]): Promise<void> {
|
async doSyncFor (docs: DocSyncInfo[], project: GithubProject): Promise<void> {
|
||||||
const byClass = this.groupByClass(docs)
|
const byClass = this.groupByClass(docs)
|
||||||
|
|
||||||
// We need to reorder based on our sync mappers
|
// We need to reorder based on our sync mappers
|
||||||
|
|
||||||
for (const [_class, clDocs] of byClass.entries()) {
|
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, {
|
const externalDocs = await this._client.findAll<Doc>(_class, {
|
||||||
_id: { $in: syncInfo.map((it) => it._id as Ref<Doc>) }
|
_id: { $in: syncInfo.map((it) => it._id as Ref<Doc>) }
|
||||||
})
|
})
|
||||||
|
|
||||||
const parents = await this._client.findAll<DocSyncInfo>(github.class.DocSyncInfo, {
|
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[] }
|
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 16', 'btnBlockquote')
|
||||||
await documentContentPage.applyToolbarCommand('Line 17', 'btnCode')
|
await documentContentPage.applyToolbarCommand('Line 17', 'btnCode')
|
||||||
await documentContentPage.applyToolbarCommand('Line 18', 'btnCodeBlock')
|
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.applyNote('Line 19', 'warning', testNote)
|
||||||
await documentContentPage.addImage('Line 20')
|
await documentContentPage.addImage('Line 20')
|
||||||
await page.keyboard.type('Cat')
|
await page.keyboard.type('Cat')
|
||||||
|
Loading…
Reference in New Issue
Block a user