mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-01 20:59:48 +00:00
Merge remote-tracking branch 'origin/develop' into staging
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
commit
17912ea4ed
@ -9,12 +9,19 @@ then
|
||||
c=( ${a[2]//[^0-9]*/ } )
|
||||
((c++))
|
||||
version="${a[0]}.${a[1]}.${c}-staging"
|
||||
echo "Tagging stating $1 with version ${version}"
|
||||
echo "Tagging staging $1 with version ${version}"
|
||||
docker tag "$1:$rev_version" "$1:$version"
|
||||
for n in {1..5}; do
|
||||
docker push "$1:$version" && break
|
||||
echo 'Docker failed to push, wait 5 seconds'
|
||||
sleep 5
|
||||
|
||||
if (( $n < 5 ))
|
||||
then
|
||||
echo 'Docker failed to push, wait 5 second'
|
||||
sleep 5
|
||||
else
|
||||
echo '5 push attempts failed, exiting with failure'
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "Tagging release $1 with version ${version}"
|
||||
@ -22,12 +29,26 @@ else
|
||||
docker tag "$1:$rev_version" "$1:latest"
|
||||
for n in {1..5}; do
|
||||
docker push "$1:$version" && break
|
||||
echo 'Docker failed to push, wait 5 seconds'
|
||||
sleep 5
|
||||
|
||||
if (( $n < 5 ))
|
||||
then
|
||||
echo 'Docker failed to push, wait 5 second'
|
||||
sleep 5
|
||||
else
|
||||
echo '5 push attempts failed, exiting with failure'
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
for n in {1..5}; do
|
||||
docker push "$1:latest" && break
|
||||
echo 'Docker failed to push, wait 5 seconds'
|
||||
sleep 5
|
||||
|
||||
if (( $n < 5 ))
|
||||
then
|
||||
echo 'Docker failed to push, wait 5 second'
|
||||
sleep 5
|
||||
else
|
||||
echo '5 push attempts failed, exiting with failure'
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
@ -140,6 +140,7 @@ export async function moveAccountDbFromMongoToPG (
|
||||
mongoDb: AccountDB,
|
||||
pgDb: AccountDB
|
||||
): Promise<void> {
|
||||
// [accountId, workspaceId]
|
||||
const workspaceAssignments: [ObjectId, ObjectId][] = []
|
||||
const accounts = await listAccounts(mongoDb)
|
||||
const workspaces = await listWorkspacesPure(mongoDb)
|
||||
@ -153,14 +154,26 @@ export async function moveAccountDbFromMongoToPG (
|
||||
|
||||
delete (pgAccount as any).workspaces
|
||||
|
||||
if (pgAccount.createdOn == null) {
|
||||
pgAccount.createdOn = Date.now()
|
||||
}
|
||||
|
||||
if (pgAccount.first == null) {
|
||||
pgAccount.first = 'NotSet'
|
||||
}
|
||||
|
||||
if (pgAccount.last == null) {
|
||||
pgAccount.last = 'NotSet'
|
||||
}
|
||||
|
||||
for (const workspaceString of new Set(mongoAccount.workspaces.map((w) => w.toString()))) {
|
||||
workspaceAssignments.push([pgAccount._id, workspaceString])
|
||||
}
|
||||
|
||||
const exists = await getAccount(pgDb, pgAccount.email)
|
||||
if (exists === null) {
|
||||
await pgDb.account.insertOne(pgAccount)
|
||||
ctx.info('Moved account', { email: pgAccount.email })
|
||||
|
||||
for (const workspace of mongoAccount.workspaces) {
|
||||
workspaceAssignments.push([pgAccount._id, workspace.toString()])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -170,6 +183,10 @@ export async function moveAccountDbFromMongoToPG (
|
||||
_id: mongoWorkspace._id.toString()
|
||||
}
|
||||
|
||||
if (pgWorkspace.createdOn == null) {
|
||||
pgWorkspace.createdOn = Date.now()
|
||||
}
|
||||
|
||||
// delete deprecated fields
|
||||
delete (pgWorkspace as any).createProgress
|
||||
delete (pgWorkspace as any).creating
|
||||
@ -202,9 +219,19 @@ export async function moveAccountDbFromMongoToPG (
|
||||
}
|
||||
}
|
||||
|
||||
if (workspaceAssignments.length > 0) {
|
||||
for (const [accountId, workspaceId] of workspaceAssignments) {
|
||||
await pgDb.assignWorkspace(accountId, workspaceId)
|
||||
}
|
||||
const pgAssignments = (await listAccounts(pgDb)).reduce<Record<ObjectId, ObjectId[]>>((assignments, acc) => {
|
||||
assignments[acc._id] = acc.workspaces
|
||||
|
||||
return assignments
|
||||
}, {})
|
||||
const assignmentsToInsert = workspaceAssignments.filter(
|
||||
([accountId, workspaceId]) =>
|
||||
pgAssignments[accountId] === undefined || !pgAssignments[accountId].includes(workspaceId)
|
||||
)
|
||||
|
||||
for (const [accountId, workspaceId] of assignmentsToInsert) {
|
||||
await pgDb.assignWorkspace(accountId, workspaceId)
|
||||
}
|
||||
|
||||
ctx.info('Assignments made', { count: assignmentsToInsert.length })
|
||||
}
|
||||
|
@ -51,6 +51,7 @@ import { documentsOperation } from '@hcengineering/model-controlled-documents'
|
||||
import { productsOperation } from '@hcengineering/model-products'
|
||||
import { requestOperation } from '@hcengineering/model-request'
|
||||
import { analyticsCollectorOperation } from '@hcengineering/model-analytics-collector'
|
||||
import { workbenchOperation } from '@hcengineering/model-workbench'
|
||||
|
||||
export const migrateOperations: [string, MigrateOperation][] = [
|
||||
['core', coreOperation],
|
||||
@ -90,5 +91,6 @@ export const migrateOperations: [string, MigrateOperation][] = [
|
||||
['textEditorOperation', textEditorOperation],
|
||||
// We should call notification migration after activityServer and chunter
|
||||
['notification', notificationOperation],
|
||||
['analyticsCollector', analyticsCollectorOperation]
|
||||
['analyticsCollector', analyticsCollectorOperation],
|
||||
['workbench', workbenchOperation]
|
||||
]
|
||||
|
@ -40,6 +40,7 @@
|
||||
"@hcengineering/ui": "^0.6.15",
|
||||
"@hcengineering/view": "^0.6.13",
|
||||
"@hcengineering/model-presentation": "^0.6.0",
|
||||
"@hcengineering/model-uploader": "^0.6.0"
|
||||
"@hcengineering/model-uploader": "^0.6.0",
|
||||
"@hcengineering/workbench": "^0.6.16"
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,8 @@ import {
|
||||
import core, { TAttachedDoc } from '@hcengineering/model-core'
|
||||
import preference, { TPreference } from '@hcengineering/model-preference'
|
||||
import view, { createAction } from '@hcengineering/model-view'
|
||||
import workbench, { WidgetType } from '@hcengineering/workbench'
|
||||
import presentation from '@hcengineering/model-presentation'
|
||||
|
||||
import attachment from './plugin'
|
||||
|
||||
@ -97,6 +99,24 @@ export function createModel (builder: Builder): void {
|
||||
editor: attachment.component.Photos
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
workbench.class.Widget,
|
||||
core.space.Model,
|
||||
{
|
||||
label: attachment.string.Files,
|
||||
type: WidgetType.Flexible,
|
||||
icon: attachment.icon.Attachment,
|
||||
component: attachment.component.PreviewWidget,
|
||||
closeIfNoTabs: true
|
||||
},
|
||||
attachment.ids.PreviewWidget
|
||||
)
|
||||
|
||||
builder.createDoc(presentation.class.ComponentPointExtension, core.space.Model, {
|
||||
extension: presentation.extension.FilePreviewPopupActions,
|
||||
component: attachment.component.PreviewPopupActions
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
activity.class.DocUpdateMessageViewlet,
|
||||
core.space.Model,
|
||||
|
@ -24,7 +24,9 @@ import type { ActionCategory } from '@hcengineering/view'
|
||||
|
||||
export default mergeIds(attachmentId, attachment, {
|
||||
component: {
|
||||
AttachmentPresenter: '' as AnyComponent
|
||||
AttachmentPresenter: '' as AnyComponent,
|
||||
PreviewWidget: '' as AnyComponent,
|
||||
PreviewPopupActions: '' as AnyComponent
|
||||
},
|
||||
string: {
|
||||
AddAttachment: '' as IntlString,
|
||||
|
@ -388,7 +388,7 @@ export function createModel (builder: Builder): void {
|
||||
core.space.Model,
|
||||
{
|
||||
label: notification.string.Inbox,
|
||||
icon: notification.icon.Inbox,
|
||||
icon: notification.icon.Notifications,
|
||||
alias: notificationId,
|
||||
hidden: true,
|
||||
locationResolver: notification.resolver.Location,
|
||||
|
@ -35,6 +35,7 @@
|
||||
"@hcengineering/model-presentation": "^0.6.0",
|
||||
"@hcengineering/model-view": "^0.6.0",
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/preference": "^0.6.13",
|
||||
"@hcengineering/ui": "^0.6.15",
|
||||
"@hcengineering/view": "^0.6.13",
|
||||
"@hcengineering/workbench": "^0.6.16",
|
||||
|
@ -40,6 +40,7 @@ import presentation from '@hcengineering/model-presentation'
|
||||
import workbench from './plugin'
|
||||
|
||||
export { workbenchId } from '@hcengineering/workbench'
|
||||
export { workbenchOperation } from './migration'
|
||||
export type { Application }
|
||||
|
||||
@Model(workbench.class.Application, core.class.Doc, DOMAIN_MODEL)
|
||||
|
40
models/workbench/src/migration.ts
Normal file
40
models/workbench/src/migration.ts
Normal file
@ -0,0 +1,40 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
import {
|
||||
type MigrateOperation,
|
||||
type MigrationClient,
|
||||
type MigrationUpgradeClient,
|
||||
tryMigrate
|
||||
} from '@hcengineering/model'
|
||||
import { DOMAIN_PREFERENCE } from '@hcengineering/preference'
|
||||
import workbench from '@hcengineering/workbench'
|
||||
|
||||
import { workbenchId } from '.'
|
||||
|
||||
async function removeTabs (client: MigrationClient): Promise<void> {
|
||||
await client.deleteMany(DOMAIN_PREFERENCE, { _class: workbench.class.WorkbenchTab })
|
||||
}
|
||||
|
||||
export const workbenchOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient): Promise<void> {
|
||||
await tryMigrate(client, workbenchId, [
|
||||
{
|
||||
state: 'remove-wrong-tabs-v1',
|
||||
func: removeTabs
|
||||
}
|
||||
])
|
||||
},
|
||||
async upgrade (state: Map<string, Set<string>>, client: () => Promise<MigrationUpgradeClient>): Promise<void> {}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
<!--
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Ref, Blob } from '@hcengineering/core'
|
||||
import { Button } from '@hcengineering/ui'
|
||||
|
||||
import { getFileUrl } from '../file'
|
||||
import Download from './icons/Download.svelte'
|
||||
import presentation from '../plugin'
|
||||
|
||||
export let file: Ref<Blob> | undefined
|
||||
export let name: string
|
||||
|
||||
let download: HTMLAnchorElement
|
||||
$: srcRef = file !== undefined ? getFileUrl(file, name) : undefined
|
||||
</script>
|
||||
|
||||
{#await srcRef then src}
|
||||
{#if src !== ''}
|
||||
<a class="no-line" href={src} download={name} bind:this={download}>
|
||||
<Button
|
||||
icon={Download}
|
||||
kind={'icon'}
|
||||
on:click={() => {
|
||||
download.click()
|
||||
}}
|
||||
showTooltip={{ label: presentation.string.Download }}
|
||||
/>
|
||||
</a>
|
||||
{/if}
|
||||
{/await}
|
@ -15,17 +15,17 @@
|
||||
<script lang="ts">
|
||||
import { type Blob, type Ref } from '@hcengineering/core'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { Button, Dialog, tooltip } from '@hcengineering/ui'
|
||||
import { Dialog, tooltip } from '@hcengineering/ui'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
|
||||
import presentation from '../plugin'
|
||||
|
||||
import { getFileUrl } from '../file'
|
||||
import { BlobMetadata } from '../types'
|
||||
|
||||
import ActionContext from './ActionContext.svelte'
|
||||
import FilePreview from './FilePreview.svelte'
|
||||
import Download from './icons/Download.svelte'
|
||||
import DownloadFileButton from './DownloadFileButton.svelte'
|
||||
import { ComponentExtensions } from '../index'
|
||||
import presentation from '../plugin'
|
||||
import FileTypeIcon from './FileTypeIcon.svelte'
|
||||
|
||||
export let file: Ref<Blob> | undefined
|
||||
export let name: string
|
||||
@ -38,21 +38,11 @@
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let download: HTMLAnchorElement
|
||||
|
||||
onMount(() => {
|
||||
if (fullSize) {
|
||||
dispatch('fullsize')
|
||||
}
|
||||
})
|
||||
|
||||
function iconLabel (name: string): string {
|
||||
const parts = `${name}`.split('.')
|
||||
const ext = parts[parts.length - 1]
|
||||
return ext.substring(0, 4).toUpperCase()
|
||||
}
|
||||
|
||||
$: srcRef = file !== undefined ? getFileUrl(file, name) : undefined
|
||||
</script>
|
||||
|
||||
<ActionContext context={{ mode: 'browser' }} />
|
||||
@ -67,9 +57,7 @@
|
||||
<div class="antiTitle icon-wrapper">
|
||||
{#if showIcon}
|
||||
<div class="wrapped-icon">
|
||||
<div class="flex-center icon">
|
||||
{iconLabel(name)}
|
||||
</div>
|
||||
<FileTypeIcon {name} />
|
||||
</div>
|
||||
{/if}
|
||||
<span class="wrapped-title" use:tooltip={{ label: getEmbeddedLabel(name) }}>{name}</span>
|
||||
@ -77,39 +65,19 @@
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="utils">
|
||||
{#await srcRef then src}
|
||||
{#if src !== ''}
|
||||
<a class="no-line" href={src} download={name} bind:this={download}>
|
||||
<Button
|
||||
icon={Download}
|
||||
kind={'ghost'}
|
||||
on:click={() => {
|
||||
download.click()
|
||||
}}
|
||||
showTooltip={{ label: presentation.string.Download }}
|
||||
/>
|
||||
</a>
|
||||
{/if}
|
||||
{/await}
|
||||
<DownloadFileButton {name} {file} />
|
||||
<ComponentExtensions
|
||||
extension={presentation.extension.FilePreviewPopupActions}
|
||||
props={{
|
||||
file,
|
||||
name,
|
||||
contentType,
|
||||
metadata
|
||||
}}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
|
||||
{#if file}
|
||||
<FilePreview {file} {contentType} {name} {metadata} {props} fit />
|
||||
{/if}
|
||||
</Dialog>
|
||||
|
||||
<style lang="scss">
|
||||
.icon {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
font-weight: 500;
|
||||
font-size: 0.625rem;
|
||||
color: var(--primary-button-color);
|
||||
background-color: var(--primary-button-default);
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 0.5rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
42
packages/presentation/src/components/FileTypeIcon.svelte
Normal file
42
packages/presentation/src/components/FileTypeIcon.svelte
Normal file
@ -0,0 +1,42 @@
|
||||
<!--
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
export let name: string
|
||||
|
||||
function iconLabel (name: string): string {
|
||||
const parts = `${name}`.split('.')
|
||||
const ext = parts[parts.length - 1]
|
||||
return ext.substring(0, 4).toUpperCase()
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex-center icon">
|
||||
{iconLabel(name)}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.icon {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
font-weight: 500;
|
||||
font-size: 0.625rem;
|
||||
color: var(--primary-button-color);
|
||||
background-color: var(--primary-button-default);
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,26 @@
|
||||
<!--
|
||||
// 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 { markupToJSON } from '@hcengineering/text'
|
||||
import LiteNode from './markup/lite/LiteNode.svelte'
|
||||
|
||||
export let message: string
|
||||
|
||||
$: node = markupToJSON(message)
|
||||
</script>
|
||||
|
||||
<div class="text-markup-view">
|
||||
<LiteNode {node} />
|
||||
</div>
|
@ -0,0 +1,30 @@
|
||||
<!--
|
||||
// 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 { MarkupNode } from '@hcengineering/text'
|
||||
|
||||
import LiteNodeContent from './LiteNodeContent.svelte'
|
||||
import NodeMarks from '../NodeMarks.svelte'
|
||||
|
||||
export let node: MarkupNode
|
||||
</script>
|
||||
|
||||
{#if node}
|
||||
{@const marks = node.marks ?? []}
|
||||
|
||||
<NodeMarks {marks}>
|
||||
<LiteNodeContent {node} />
|
||||
</NodeMarks>
|
||||
{/if}
|
@ -0,0 +1,105 @@
|
||||
<!--
|
||||
// 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 { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { AttrValue, MarkupNode, MarkupNodeType, MarkupMarkType } from '@hcengineering/text'
|
||||
|
||||
import LiteNodes from './LiteNodes.svelte'
|
||||
import ObjectNode from '../ObjectNode.svelte'
|
||||
import NodeMarks from '../NodeMarks.svelte'
|
||||
|
||||
export let node: MarkupNode
|
||||
|
||||
function toRef (objectId: string): Ref<Doc> {
|
||||
return objectId as Ref<Doc>
|
||||
}
|
||||
|
||||
function toClassRef (objectClass: string): Ref<Class<Doc>> {
|
||||
if (objectClass === 'contact:class:Employee') {
|
||||
return 'contact:mixin:Employee' as Ref<Class<Doc>>
|
||||
}
|
||||
return objectClass as Ref<Class<Doc>>
|
||||
}
|
||||
|
||||
function toString (value: AttrValue | undefined): string | undefined {
|
||||
return value !== undefined ? `${value}` : undefined
|
||||
}
|
||||
|
||||
function toNumber (value: AttrValue | undefined): number | undefined {
|
||||
if (typeof value === 'boolean') {
|
||||
return value ? 1 : 0
|
||||
}
|
||||
|
||||
return value !== undefined ? (typeof value === 'string' ? parseInt(value) : value) : undefined
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if node}
|
||||
{@const attrs = node.attrs ?? {}}
|
||||
{@const nodes = node.content ?? []}
|
||||
|
||||
{#if node.type === MarkupNodeType.doc}
|
||||
<LiteNodes {nodes} />
|
||||
{:else if node.type === MarkupNodeType.text}
|
||||
{node.text}
|
||||
{:else if node.type === MarkupNodeType.paragraph}
|
||||
<p class="p-inline contrast" class:overflow-label={true} style:margin="0">
|
||||
<LiteNodes {nodes} />
|
||||
</p>
|
||||
{:else if node.type === MarkupNodeType.blockquote}
|
||||
<LiteNodes {nodes} />
|
||||
{:else if node.type === MarkupNodeType.horizontal_rule}
|
||||
<!-- nothing-->
|
||||
{:else if node.type === MarkupNodeType.heading}
|
||||
{@const level = toNumber(node.attrs?.level) ?? 1}
|
||||
{@const element = `h${level}`}
|
||||
<svelte:element this={element}>
|
||||
<LiteNodes {nodes} />
|
||||
</svelte:element>
|
||||
{:else if node.type === MarkupNodeType.code_block}
|
||||
<p class="p-inline contrast" class:overflow-label={true} style:margin="0">
|
||||
<NodeMarks
|
||||
marks={[
|
||||
{
|
||||
type: MarkupMarkType.code,
|
||||
attrs: {}
|
||||
}
|
||||
]}
|
||||
>
|
||||
<LiteNodes {nodes} />
|
||||
</NodeMarks>
|
||||
</p>
|
||||
{:else if node.type === MarkupNodeType.reference}
|
||||
{@const objectId = toString(attrs.id)}
|
||||
{@const objectClass = toString(attrs.objectclass)}
|
||||
{@const objectLabel = toString(attrs.label)}
|
||||
|
||||
{#if objectClass !== undefined && objectId !== undefined}
|
||||
<ObjectNode _id={toRef(objectId)} _class={toClassRef(objectClass)} title={objectLabel} />
|
||||
{:else}
|
||||
<LiteNodes {nodes} />
|
||||
{/if}
|
||||
{:else if node.type === MarkupNodeType.taskList}
|
||||
<!-- TODO not implemented -->
|
||||
{:else if node.type === MarkupNodeType.taskItem}
|
||||
<!-- TODO not implemented -->
|
||||
{:else if node.type === MarkupNodeType.subLink}
|
||||
<sub>
|
||||
<LiteNodes {nodes} />
|
||||
</sub>
|
||||
{:else}
|
||||
<LiteNodes {nodes} />
|
||||
{/if}
|
||||
{/if}
|
@ -0,0 +1,27 @@
|
||||
<!--
|
||||
// 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 { MarkupNode } from '@hcengineering/text'
|
||||
|
||||
import LiteNode from './LiteNode.svelte'
|
||||
|
||||
export let nodes: MarkupNode[]
|
||||
</script>
|
||||
|
||||
{#if nodes}
|
||||
{#each nodes as node}
|
||||
<LiteNode {node} />
|
||||
{/each}
|
||||
{/if}
|
@ -47,6 +47,9 @@ export { default as BreadcrumbsElement } from './components/breadcrumbs/Breadcru
|
||||
export { default as ComponentExtensions } from './components/extensions/ComponentExtensions.svelte'
|
||||
export { default as DocCreateExtComponent } from './components/extensions/DocCreateExtComponent.svelte'
|
||||
export { default as SearchResult } from './components/SearchResult.svelte'
|
||||
export { default as LiteMessageViewer } from './components/LiteMessageViewer.svelte'
|
||||
export { default as DownloadFileButton } from './components/DownloadFileButton.svelte'
|
||||
export { default as FileTypeIcon } from './components/FileTypeIcon.svelte'
|
||||
export { default } from './plugin'
|
||||
export * from './types'
|
||||
export * from './utils'
|
||||
|
@ -123,7 +123,8 @@ export default plugin(presentationId, {
|
||||
ContentTypeNotSupported: '' as IntlString
|
||||
},
|
||||
extension: {
|
||||
FilePreviewExtension: '' as ComponentExtensionId
|
||||
FilePreviewExtension: '' as ComponentExtensionId,
|
||||
FilePreviewPopupActions: '' as ComponentExtensionId
|
||||
},
|
||||
metadata: {
|
||||
ModelVersion: '' as Metadata<string>,
|
||||
|
@ -108,7 +108,7 @@ li {
|
||||
}
|
||||
|
||||
p {
|
||||
user-select: text;
|
||||
user-select:inherit;
|
||||
|
||||
a {
|
||||
word-break: break-all;
|
||||
|
@ -22,6 +22,7 @@
|
||||
export let icon: Asset | AnySvelteComponent | ComponentType | undefined = undefined
|
||||
export let iconProps: any | undefined = undefined
|
||||
export let iconWidth: string | undefined = undefined
|
||||
export let iconMargin: string | undefined = undefined
|
||||
export let withoutIconBackground = false
|
||||
export let label: IntlString | undefined = undefined
|
||||
export let title: string | undefined = undefined
|
||||
@ -31,7 +32,12 @@
|
||||
|
||||
<button class="hulyBreadcrumb-container {size}" class:current={isCurrent} on:click>
|
||||
{#if size === 'large' && icon}
|
||||
<div class="hulyBreadcrumb-avatar" style:width={iconWidth ?? null} class:withoutIconBackground>
|
||||
<div
|
||||
class="hulyBreadcrumb-avatar"
|
||||
style:width={iconWidth ?? null}
|
||||
style:margin={iconMargin}
|
||||
class:withoutIconBackground
|
||||
>
|
||||
<Icon {icon} size={'small'} {iconProps} />
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -166,7 +166,7 @@ export const settingsSeparators: DefSeparators = [
|
||||
|
||||
export const mainSeparators: DefSeparators = [
|
||||
{ minSize: 30, size: 'auto', maxSize: 'auto' },
|
||||
{ minSize: 20, size: 30, maxSize: 45, float: 'sidebar' }
|
||||
{ minSize: 20, size: 30, maxSize: 80, float: 'sidebar' }
|
||||
]
|
||||
|
||||
export const secondNavSeparators: DefSeparators = [{ minSize: 7, size: 7.5, maxSize: 15, float: 'navigator' }, null]
|
||||
|
@ -130,6 +130,7 @@ export interface BreadcrumbItem {
|
||||
icon?: Asset | AnySvelteComponent | ComponentType
|
||||
iconProps?: any
|
||||
iconWidth?: string
|
||||
iconMargin?: string
|
||||
withoutIconBackground?: boolean
|
||||
label?: IntlString
|
||||
title?: string
|
||||
|
@ -14,7 +14,7 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { getClient, MessageViewer } from '@hcengineering/presentation'
|
||||
import { getClient, LiteMessageViewer } from '@hcengineering/presentation'
|
||||
import { Person, type PersonAccount } from '@hcengineering/contact'
|
||||
import {
|
||||
Avatar,
|
||||
@ -150,7 +150,7 @@
|
||||
<Label label={intlLabel} />
|
||||
{/if}
|
||||
{#if text}
|
||||
<MessageViewer message={text} preview />
|
||||
<LiteMessageViewer message={text} />
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
|
@ -289,6 +289,7 @@
|
||||
border: 1px solid transparent;
|
||||
border-radius: 0.25rem;
|
||||
width: 100%;
|
||||
user-select: text;
|
||||
|
||||
&.clickable {
|
||||
cursor: pointer;
|
||||
|
@ -52,6 +52,7 @@
|
||||
"UnPinAttachment": "Mark less important",
|
||||
"FilterAttachments": "Attachments",
|
||||
"RemovedAttachment": "Removed attachment",
|
||||
"ContentType": "Content type"
|
||||
"ContentType": "Content type",
|
||||
"OpenInWindow": "Open in window"
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,7 @@
|
||||
"UnPinAttachment": "Marcar como menos importante",
|
||||
"FilterAttachments": "Adjuntos",
|
||||
"RemovedAttachment": "Adjunto eliminado",
|
||||
"ContentType": "Tipo de contenido"
|
||||
"ContentType": "Tipo de contenido",
|
||||
"OpenInWindow": "Abrir en ventana"
|
||||
}
|
||||
}
|
@ -52,6 +52,7 @@
|
||||
"UnPinAttachment": "Marquer comme moins important",
|
||||
"FilterAttachments": "Pièces jointes",
|
||||
"RemovedAttachment": "Pièce jointe supprimée",
|
||||
"ContentType": "Type de contenu"
|
||||
"ContentType": "Type de contenu",
|
||||
"OpenInWindow": "Ouvrir dans une fenêtre"
|
||||
}
|
||||
}
|
@ -52,6 +52,7 @@
|
||||
"UnPinAttachment": "Marcar como menos importante",
|
||||
"FilterAttachments": "Anexos",
|
||||
"RemovedAttachment": "Anexo removido",
|
||||
"ContentType": "Tipo de conteúdo"
|
||||
"ContentType": "Tipo de conteúdo",
|
||||
"OpenInWindow": "Abrir numa janela"
|
||||
}
|
||||
}
|
@ -52,6 +52,7 @@
|
||||
"UnPinAttachment": "Убрать пометку важное",
|
||||
"FilterAttachments": "Вложения",
|
||||
"RemovedAttachment": "Удалил(а) вложение",
|
||||
"ContentType": "Тип контента"
|
||||
"ContentType": "Тип контента",
|
||||
"OpenInWindow": "Открыть в окне"
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,7 @@
|
||||
"UnPinAttachment": "标记为不重要",
|
||||
"FilterAttachments": "附件",
|
||||
"RemovedAttachment": "移除附件",
|
||||
"ContentType": "内容类型"
|
||||
"ContentType": "内容类型",
|
||||
"OpenInWindow": "在新窗口中打开"
|
||||
}
|
||||
}
|
||||
|
@ -38,8 +38,8 @@
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/analytics": "^0.6.0",
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/analytics": "^0.6.0",
|
||||
"@hcengineering/attachment": "^0.6.14",
|
||||
"@hcengineering/contact": "^0.6.24",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
@ -55,6 +55,7 @@
|
||||
"@hcengineering/uploader": "^0.6.0",
|
||||
"@hcengineering/view": "^0.6.13",
|
||||
"@hcengineering/view-resources": "^0.6.0",
|
||||
"@hcengineering/workbench": "^0.6.16",
|
||||
"filesize": "^8.0.3",
|
||||
"svelte": "^4.2.12"
|
||||
},
|
||||
|
@ -25,10 +25,12 @@
|
||||
} from '@hcengineering/presentation'
|
||||
import { IconMoreH, Menu, Action as UIAction, closeTooltip, showPopup, tooltip } from '@hcengineering/ui'
|
||||
import view, { Action } from '@hcengineering/view'
|
||||
import workbench from '@hcengineering/workbench'
|
||||
|
||||
import AttachmentAction from './AttachmentAction.svelte'
|
||||
import FileDownload from './icons/FileDownload.svelte'
|
||||
import attachmentPlugin from '../plugin'
|
||||
import { openAttachmentInSidebar } from '../utils'
|
||||
|
||||
export let attachment: WithLookup<Attachment>
|
||||
export let isSaved = false
|
||||
@ -95,6 +97,13 @@
|
||||
if (canPreview) {
|
||||
actions.push(openAction)
|
||||
}
|
||||
actions.push({
|
||||
icon: view.icon.DetailsFilled,
|
||||
label: workbench.string.OpenInSidebar,
|
||||
action: async () => {
|
||||
await openAttachmentInSidebar(attachment)
|
||||
}
|
||||
})
|
||||
actions.push({
|
||||
label: saveAttachmentAction.label,
|
||||
icon: saveAttachmentAction.icon,
|
||||
|
@ -17,11 +17,10 @@
|
||||
import type { Attachment } from '@hcengineering/attachment'
|
||||
import core, { type WithLookup } from '@hcengineering/core'
|
||||
import presentation, {
|
||||
FilePreviewPopup,
|
||||
canPreviewFile,
|
||||
FilePreviewPopup,
|
||||
getBlobRef,
|
||||
getFileUrl,
|
||||
getPreviewAlignment,
|
||||
previewTypes,
|
||||
sizeToWidth
|
||||
} from '@hcengineering/presentation'
|
||||
@ -29,7 +28,7 @@
|
||||
import { permissionsStore } from '@hcengineering/view-resources'
|
||||
import filesize from 'filesize'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { getType } from '../utils'
|
||||
import { getType, openAttachmentInSidebar } from '../utils'
|
||||
|
||||
import AttachmentName from './AttachmentName.svelte'
|
||||
|
||||
@ -70,7 +69,7 @@
|
||||
canPreview = false
|
||||
}
|
||||
|
||||
function clickHandler (e: MouseEvent): void {
|
||||
async function clickHandler (e: MouseEvent): Promise<void> {
|
||||
if (value === undefined || !canPreview) return
|
||||
|
||||
e.preventDefault()
|
||||
@ -80,16 +79,20 @@
|
||||
return
|
||||
}
|
||||
closeTooltip()
|
||||
showPopup(
|
||||
FilePreviewPopup,
|
||||
{
|
||||
file: value.file,
|
||||
contentType: value.type,
|
||||
name: value.name,
|
||||
metadata: value.metadata
|
||||
},
|
||||
getPreviewAlignment(value.type)
|
||||
)
|
||||
if (value.type.startsWith('image/') || value.type.startsWith('video/') || value.type.startsWith('audio/')) {
|
||||
showPopup(
|
||||
FilePreviewPopup,
|
||||
{
|
||||
file: value.file,
|
||||
contentType: value.type,
|
||||
name: value.name,
|
||||
metadata: value.metadata
|
||||
},
|
||||
'centered'
|
||||
)
|
||||
} else {
|
||||
await openAttachmentInSidebar(value)
|
||||
}
|
||||
}
|
||||
|
||||
function middleClickHandler (e: MouseEvent): void {
|
||||
|
@ -14,13 +14,13 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Attachment } from '@hcengineering/attachment'
|
||||
import { Attachment } from '@hcengineering/attachment'
|
||||
import { FilePreviewPopup } from '@hcengineering/presentation'
|
||||
import { closeTooltip, showPopup } from '@hcengineering/ui'
|
||||
import { ListSelectionProvider } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { WithLookup } from '@hcengineering/core'
|
||||
|
||||
import type { WithLookup } from '@hcengineering/core'
|
||||
import { AttachmentImageSize } from '../types'
|
||||
import { getType } from '../utils'
|
||||
import AttachmentActions from './AttachmentActions.svelte'
|
||||
|
@ -0,0 +1,41 @@
|
||||
<!--
|
||||
// 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 type { Blob, Ref } from '@hcengineering/core'
|
||||
import { BlobMetadata } from '@hcengineering/presentation'
|
||||
import { Button, closePopup, closeTooltip, IconDetailsFilled } from '@hcengineering/ui'
|
||||
import workbench from '@hcengineering/workbench'
|
||||
|
||||
import { openFilePreviewInSidebar } from '../utils'
|
||||
|
||||
export let file: Ref<Blob> | undefined
|
||||
export let name: string
|
||||
export let contentType: string
|
||||
export let metadata: BlobMetadata | undefined
|
||||
</script>
|
||||
|
||||
{#if file}
|
||||
<Button
|
||||
icon={IconDetailsFilled}
|
||||
kind="icon"
|
||||
on:click={() => {
|
||||
if (file === undefined) return
|
||||
closeTooltip()
|
||||
closePopup()
|
||||
void openFilePreviewInSidebar(file, name, contentType, metadata)
|
||||
}}
|
||||
showTooltip={{ label: workbench.string.OpenInSidebar }}
|
||||
/>
|
||||
{/if}
|
@ -0,0 +1,68 @@
|
||||
<!--
|
||||
// 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 workbench, { Widget, WidgetTab } from '@hcengineering/workbench'
|
||||
import { FilePreview, DownloadFileButton, FilePreviewPopup, FileTypeIcon } from '@hcengineering/presentation'
|
||||
import { Breadcrumbs, Button, closeTooltip, Header, IconOpen, showPopup } from '@hcengineering/ui'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import view from '@hcengineering/view'
|
||||
|
||||
import attachment from '../plugin'
|
||||
|
||||
export let widget: Widget
|
||||
export let tab: WidgetTab
|
||||
|
||||
$: file = tab.data?.file
|
||||
$: fileName = tab.data?.name ?? ''
|
||||
$: contentType = tab.data?.contentType
|
||||
$: metadata = tab.data?.metadata
|
||||
|
||||
async function closeTab (): Promise<void> {
|
||||
const fn = await getResource(workbench.function.CloseWidgetTab)
|
||||
await fn(widget, tab.id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<Header
|
||||
allowFullsize={false}
|
||||
type="type-aside"
|
||||
hideBefore={true}
|
||||
hideActions={false}
|
||||
hideDescription={true}
|
||||
adaptive="disabled"
|
||||
closeOnEscape={false}
|
||||
on:close={closeTab}
|
||||
>
|
||||
<Breadcrumbs
|
||||
items={[{ title: fileName, icon: FileTypeIcon, iconProps: { name: fileName }, iconMargin: '0 0.5rem 0 0' }]}
|
||||
currentOnly
|
||||
/>
|
||||
<svelte:fragment slot="actions">
|
||||
<DownloadFileButton name={fileName} {file} />
|
||||
<Button
|
||||
icon={view.icon.Open}
|
||||
kind="icon"
|
||||
showTooltip={{ label: attachment.string.OpenInWindow }}
|
||||
on:click={() => {
|
||||
closeTooltip()
|
||||
showPopup(FilePreviewPopup, { file, name: fileName, contentType, metadata }, 'centered')
|
||||
}}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</Header>
|
||||
|
||||
{#if file}
|
||||
<FilePreview {file} {contentType} name={fileName} {metadata} />
|
||||
{/if}
|
@ -40,6 +40,8 @@ import AttachmentsUpdatedMessage from './components/activity/AttachmentsUpdatedM
|
||||
import IconAttachment from './components/icons/Attachment.svelte'
|
||||
import FileDownload from './components/icons/FileDownload.svelte'
|
||||
import IconUploadDuo from './components/icons/UploadDuo.svelte'
|
||||
import PreviewWidget from './components/PreviewWidget.svelte'
|
||||
import PreviewPopupActions from './components/PreviewPopupActions.svelte'
|
||||
|
||||
export * from './types'
|
||||
|
||||
@ -256,7 +258,9 @@ export default async (): Promise<Resources> => ({
|
||||
Attachments,
|
||||
FileBrowser,
|
||||
Photos,
|
||||
PDFViewer
|
||||
PDFViewer,
|
||||
PreviewWidget,
|
||||
PreviewPopupActions
|
||||
},
|
||||
activity: {
|
||||
AttachmentsUpdatedMessage
|
||||
|
@ -14,7 +14,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { type Attachment } from '@hcengineering/attachment'
|
||||
import { type BlobMetadata, type Attachment } from '@hcengineering/attachment'
|
||||
import {
|
||||
type Blob,
|
||||
type Class,
|
||||
@ -24,8 +24,10 @@ import {
|
||||
type Ref,
|
||||
type Space
|
||||
} from '@hcengineering/core'
|
||||
import { setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import { type FileOrBlob, getFileMetadata, uploadFile } from '@hcengineering/presentation'
|
||||
import { getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import { type FileOrBlob, getClient, getFileMetadata, uploadFile } from '@hcengineering/presentation'
|
||||
import workbench, { type WidgetTab } from '@hcengineering/workbench'
|
||||
import view from '@hcengineering/view'
|
||||
|
||||
import attachment from './plugin'
|
||||
|
||||
@ -98,3 +100,38 @@ export function getType (type: string): 'image' | 'text' | 'json' | 'video' | 'a
|
||||
|
||||
return 'other'
|
||||
}
|
||||
|
||||
export async function openAttachmentInSidebar (value: Attachment): Promise<void> {
|
||||
await openFilePreviewInSidebar(value.file, value.name, value.type, value.metadata)
|
||||
}
|
||||
|
||||
export async function openFilePreviewInSidebar (
|
||||
file: Ref<Blob>,
|
||||
name: string,
|
||||
contentType: string,
|
||||
metadata?: BlobMetadata
|
||||
): Promise<void> {
|
||||
const client = getClient()
|
||||
const widget = client.getModel().findAllSync(workbench.class.Widget, { _id: attachment.ids.PreviewWidget })[0]
|
||||
const createFn = await getResource(workbench.function.CreateWidgetTab)
|
||||
let icon = attachment.icon.Attachment
|
||||
|
||||
if (contentType.startsWith('image/')) {
|
||||
icon = view.icon.Image
|
||||
} else if (contentType.startsWith('video/')) {
|
||||
icon = view.icon.Video
|
||||
} else if (contentType.startsWith('audio/')) {
|
||||
icon = view.icon.Audio
|
||||
} else {
|
||||
icon = view.icon.File
|
||||
}
|
||||
|
||||
const tab: WidgetTab = {
|
||||
id: file,
|
||||
icon,
|
||||
name,
|
||||
widget: attachment.ids.PreviewWidget,
|
||||
data: { file, name, contentType, metadata }
|
||||
}
|
||||
await createFn(widget, tab, true)
|
||||
}
|
||||
|
@ -40,6 +40,7 @@
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/ui": "^0.6.15",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/workbench": "^0.6.16",
|
||||
"@hcengineering/preference": "^0.6.13"
|
||||
},
|
||||
"repository": "https://github.com/hcengineering/platform",
|
||||
|
@ -19,6 +19,7 @@ import type { Asset, Plugin } from '@hcengineering/platform'
|
||||
import { IntlString, plugin, Resource } from '@hcengineering/platform'
|
||||
import type { Preference } from '@hcengineering/preference'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
import { Widget } from '@hcengineering/workbench'
|
||||
|
||||
export * from './analytics'
|
||||
|
||||
@ -87,6 +88,9 @@ export default plugin(attachmentId, {
|
||||
UploadFile: '' as Resource<(file: File) => Promise<Ref<Blob>>>,
|
||||
DeleteFile: '' as Resource<(id: string) => Promise<void>>
|
||||
},
|
||||
ids: {
|
||||
PreviewWidget: '' as Ref<Widget>
|
||||
},
|
||||
string: {
|
||||
Files: '' as IntlString,
|
||||
NoFiles: '' as IntlString,
|
||||
@ -106,6 +110,7 @@ export default plugin(attachmentId, {
|
||||
FileBrowserTypeFilterPDFs: '' as IntlString,
|
||||
DeleteFile: '' as IntlString,
|
||||
Attachments: '' as IntlString,
|
||||
FileBrowser: '' as IntlString
|
||||
FileBrowser: '' as IntlString,
|
||||
OpenInWindow: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
@ -95,10 +95,12 @@
|
||||
}
|
||||
|
||||
let objectChatPanel: ObjectChatPanel | undefined
|
||||
let prevObjectId: Ref<Doc> | undefined = undefined
|
||||
|
||||
$: if (object._id) {
|
||||
$: if (prevObjectId !== object._id) {
|
||||
prevObjectId = object._id
|
||||
objectChatPanel = hierarchy.classHierarchyMixin(object._class, chunter.mixin.ObjectChatPanel)
|
||||
isAsideShown = objectChatPanel?.openByDefault === true
|
||||
isAsideShown = isAsideShown ?? objectChatPanel?.openByDefault === true
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -259,7 +259,7 @@ export async function getWorkspaces (): Promise<Workspace[]> {
|
||||
if (adays === bdays) {
|
||||
return getWorkspaceSize(b) - getWorkspaceSize(a)
|
||||
}
|
||||
return bdays - adays
|
||||
return b.lastVisit - a.lastVisit
|
||||
})
|
||||
|
||||
return workspaces
|
||||
|
@ -354,6 +354,8 @@
|
||||
|
||||
.notification {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
.embeddedMarker {
|
||||
position: absolute;
|
||||
|
@ -196,4 +196,23 @@
|
||||
<symbol id="undo" viewBox="0 0 32 32">
|
||||
<path d="M20 10H7.8149L11.4023 6.4141L10 5L4 11L10 17L11.4023 15.5854L7.8179 12H20C21.5913 12 23.1174 12.6321 24.2426 13.7574C25.3679 14.8826 26 16.4087 26 18C26 19.5913 25.3679 21.1174 24.2426 22.2426C23.1174 23.3679 21.5913 24 20 24H13C12.4477 24 12 24.4477 12 25C12 25.5523 12.4477 26 13 26H20C22.1217 26 24.1566 25.1571 25.6569 23.6569C27.1571 22.1566 28 20.1217 28 18C28 15.8783 27.1571 13.8434 25.6569 12.3431C24.1566 10.8429 22.1217 10 20 10Z"/>
|
||||
</symbol>
|
||||
<symbol id="video" viewBox="0 0 32 32">
|
||||
<path d="M13 15V23L20 19L13 15Z" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M9 4C7.89543 4 7 4.89543 7 6V26C7 27.1046 7.89543 28 9 28H23C24.1046 28 25 27.1046 25 26V12H21C18.7909 12 17 10.2091 17 8V4H9ZM19 4.41421V8C19 9.10457 19.8954 10 21 10H24.5858L19 4.41421ZM5 6C5 3.79086 6.79086 2 9 2H18.5858C19.1162 2 19.6249 2.21071 20 2.58579L26.4142 9C26.7893 9.37507 27 9.88378 27 10.4142V26C27 28.2091 25.2091 30 23 30H9C6.79086 30 5 28.2091 5 26V6Z"
|
||||
/>
|
||||
</symbol>
|
||||
<symbol id="audio" viewBox="0 0 32 32">
|
||||
<path d="M13 15V23L20 19L13 15Z" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M9 4C7.89543 4 7 4.89543 7 6V26C7 27.1046 7.89543 28 9 28H23C24.1046 28 25 27.1046 25 26V12H21C18.7909 12 17 10.2091 17 8V4H9ZM19 4.41421V8C19 9.10457 19.8954 10 21 10H24.5858L19 4.41421ZM5 6C5 3.79086 6.79086 2 9 2H18.5858C19.1162 2 19.6249 2.21071 20 2.58579L26.4142 9C26.7893 9.37507 27 9.88378 27 10.4142V26C27 28.2091 25.2091 30 23 30H9C6.79086 30 5 28.2091 5 26V6Z"
|
||||
/>
|
||||
</symbol>
|
||||
<symbol id="file" viewBox="0 0 32 32">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 4C7.89543 4 7 4.89543 7 6V26C7 27.1046 7.89543 28 9 28H23C24.1046 28 25 27.1046 25 26V12H21C18.7909 12 17 10.2091 17 8V4H9ZM19 4.41421V8C19 9.10457 19.8954 10 21 10H24.5858L19 4.41421ZM5 6C5 3.79086 6.79086 2 9 2H18.5858C19.1162 2 19.6249 2.21071 20 2.58579L26.4142 9C26.7893 9.37507 27 9.88378 27 10.4142V26C27 28.2091 25.2091 30 23 30H9C6.79086 30 5 28.2091 5 26V6ZM10 17C10 16.4477 10.4477 16 11 16H21C21.5523 16 22 16.4477 22 17C22 17.5523 21.5523 18 21 18H11C10.4477 18 10 17.5523 10 17ZM10 23C10 22.4477 10.4477 22 11 22H21C21.5523 22 22 22.4477 22 23C22 23.5523 21.5523 24 21 24H11C10.4477 24 10 23.5523 10 23Z" />
|
||||
</symbol>
|
||||
</svg>
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 46 KiB |
@ -58,5 +58,8 @@ loadMetadata(view.icon, {
|
||||
Copy: `${icons}#copy`,
|
||||
DetailsFilled: `${icons}#details-filled`,
|
||||
Translate: `${icons}#translate`,
|
||||
Undo: `${icons}#undo`
|
||||
Undo: `${icons}#undo`,
|
||||
Video: `${icons}#video`,
|
||||
Audio: `${icons}#audio`,
|
||||
File: `${icons}#file`
|
||||
})
|
||||
|
@ -254,7 +254,10 @@ const view = plugin(viewId, {
|
||||
TodoList: '' as Asset,
|
||||
DetailsFilled: '' as Asset,
|
||||
Translate: '' as Asset,
|
||||
Undo: '' as Asset
|
||||
Undo: '' as Asset,
|
||||
Video: '' as Asset,
|
||||
Audio: '' as Asset,
|
||||
File: '' as Asset
|
||||
},
|
||||
category: {
|
||||
General: '' as Ref<ActionCategory>,
|
||||
|
@ -251,6 +251,7 @@
|
||||
|
||||
const doSyncLoc = reduceCalls(async (loc: Location): Promise<void> => {
|
||||
if (workspaceId !== $location.path[1]) {
|
||||
tabs = []
|
||||
// Switch of workspace
|
||||
return
|
||||
}
|
||||
|
@ -64,7 +64,7 @@
|
||||
}
|
||||
|
||||
async function updateTabData (tab: WorkbenchTab): Promise<void> {
|
||||
const tabLoc = getTabLocation(tab)
|
||||
const tabLoc = $tabIdStore === tab._id ? getCurrentLocation() : getTabLocation(tab)
|
||||
const alias = tabLoc.path[2]
|
||||
const application = client.getModel().findAllSync<Application>(workbench.class.Application, { alias })[0]
|
||||
|
||||
|
@ -26,6 +26,7 @@ import ServerManager from './components/ServerManager.svelte'
|
||||
import WorkbenchTabs from './components/WorkbenchTabs.svelte'
|
||||
import { isAdminUser } from '@hcengineering/presentation'
|
||||
import { canCloseTab, closeTab, pinTab, unpinTab } from './workbench'
|
||||
import { closeWidgetTab, createWidgetTab } from './sidebar'
|
||||
|
||||
async function hasArchiveSpaces (spaces: Space[]): Promise<boolean> {
|
||||
return spaces.find((sp) => sp.archived) !== undefined
|
||||
@ -54,7 +55,9 @@ export default async (): Promise<Resources> => ({
|
||||
function: {
|
||||
HasArchiveSpaces: hasArchiveSpaces,
|
||||
IsOwner: async (docs: Space[]) => getCurrentAccount().role === AccountRole.Owner || isAdminUser(),
|
||||
CanCloseTab: canCloseTab
|
||||
CanCloseTab: canCloseTab,
|
||||
CreateWidgetTab: createWidgetTab,
|
||||
CloseWidgetTab: closeWidgetTab
|
||||
},
|
||||
actionImpl: {
|
||||
Navigate: doNavigate,
|
||||
|
@ -23,7 +23,8 @@ import {
|
||||
navigate,
|
||||
getCurrentLocation
|
||||
} from '@hcengineering/ui'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import presentation, { getClient } from '@hcengineering/presentation'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
|
||||
import { workspaceStore } from './utils'
|
||||
|
||||
@ -44,7 +45,12 @@ locationStore.subscribe((loc) => {
|
||||
if (tab == null) return
|
||||
const tabId = tab._id
|
||||
if (tabId == null || tab._id !== tabId) return
|
||||
|
||||
const tabLoc = getTabLocation(tab)
|
||||
const tabWs = tabLoc.path[1]
|
||||
if (workspace !== tabWs) {
|
||||
return
|
||||
}
|
||||
if (loc.path[2] === '' || loc.path[2] == null) return
|
||||
void getClient().update(tab, { location: locationToUrl(loc) })
|
||||
})
|
||||
|
||||
@ -86,7 +92,8 @@ export function selectTab (_id: Ref<WorkbenchTab>): void {
|
||||
|
||||
export function getTabLocation (tab: WorkbenchTab): Location {
|
||||
const base = `${window.location.protocol}//${window.location.host}`
|
||||
const url = new URL(concatLink(base, tab.location))
|
||||
const front = getMetadata(presentation.metadata.FrontUrl) ?? base
|
||||
const url = new URL(concatLink(front, tab.location))
|
||||
|
||||
return parseLocation(url)
|
||||
}
|
||||
|
@ -257,6 +257,10 @@ export default plugin(workbenchId, {
|
||||
WorkbenchExtensions: '' as ComponentExtensionId,
|
||||
WorkbenchTabExtensions: '' as ComponentExtensionId
|
||||
},
|
||||
function: {
|
||||
CreateWidgetTab: '' as Resource<(widget: Widget, tab: WidgetTab, newTab: boolean) => Promise<void>>,
|
||||
CloseWidgetTab: '' as Resource<(widget: Widget, tab: string) => Promise<void>>
|
||||
},
|
||||
actionImpl: {
|
||||
Navigate: '' as ViewAction<{
|
||||
mode: 'app' | 'special' | 'space'
|
||||
|
@ -59,6 +59,10 @@ export abstract class PostgresDbCollection<T extends Record<string, any>> implem
|
||||
}
|
||||
|
||||
protected buildWhereClause (query: Query<T>, lastRefIdx: number = 0): [string, any[]] {
|
||||
if (Object.keys(query).length === 0) {
|
||||
return ['', []]
|
||||
}
|
||||
|
||||
const whereChunks: string[] = []
|
||||
const values: any[] = []
|
||||
let currIdx: number = lastRefIdx
|
||||
@ -131,7 +135,9 @@ export abstract class PostgresDbCollection<T extends Record<string, any>> implem
|
||||
const sqlChunks: string[] = [this.buildSelectClause()]
|
||||
const [whereClause, whereValues] = this.buildWhereClause(query)
|
||||
|
||||
sqlChunks.push(whereClause)
|
||||
if (whereClause !== '') {
|
||||
sqlChunks.push(whereClause)
|
||||
}
|
||||
|
||||
if (sort !== undefined) {
|
||||
sqlChunks.push(this.buildSortClause(sort))
|
||||
@ -200,7 +206,9 @@ export abstract class PostgresDbCollection<T extends Record<string, any>> implem
|
||||
const [whereClause, whereValues] = this.buildWhereClause(query, updateValues.length)
|
||||
|
||||
sqlChunks.push(updateClause)
|
||||
sqlChunks.push(whereClause)
|
||||
if (whereClause !== '') {
|
||||
sqlChunks.push(whereClause)
|
||||
}
|
||||
|
||||
const finalSql = sqlChunks.join(' ')
|
||||
await this.client.query(finalSql, [...updateValues, ...whereValues])
|
||||
@ -210,7 +218,9 @@ export abstract class PostgresDbCollection<T extends Record<string, any>> implem
|
||||
const sqlChunks: string[] = [`DELETE FROM ${this.name}`]
|
||||
const [whereClause, whereValues] = this.buildWhereClause(query)
|
||||
|
||||
sqlChunks.push(whereClause)
|
||||
if (whereClause !== '') {
|
||||
sqlChunks.push(whereClause)
|
||||
}
|
||||
|
||||
const finalSql = sqlChunks.join(' ')
|
||||
await this.client.query(finalSql, whereValues)
|
||||
|
@ -8,7 +8,7 @@ export class LeadsPage {
|
||||
}
|
||||
|
||||
readonly leadApplication = (): Locator => this.page.locator('[id="app-lead\\:string\\:LeadApplication"]')
|
||||
readonly customersNavElement = (): Locator => this.page.locator('text=Customers')
|
||||
readonly customersNavElement = (): Locator => this.page.locator('.antiPanel-navigator').locator('text=Customers')
|
||||
readonly newCustomerButton = (): Locator => this.page.locator('button:has-text("New Customer")')
|
||||
readonly personButton = (): Locator => this.page.locator('button:has-text("Person")')
|
||||
readonly companyButton = (): Locator => this.page.locator('button:has-text("Company")')
|
||||
|
@ -11,7 +11,7 @@ export class CompanyDetailsPage extends CommonRecruitingPage {
|
||||
}
|
||||
|
||||
readonly inputName = (): Locator => this.page.locator('div.antiEditBox input')
|
||||
readonly buttonCompanyDetails = (): Locator => this.page.locator('div.flex-row-center > span', { hasText: 'Company' })
|
||||
readonly buttonCompanyDetails = (): Locator => this.page.locator('.popupPanel-body__aside').locator('text=Company')
|
||||
readonly buttonLocation = (): Locator =>
|
||||
this.page.locator('//span[text()="Location"]/following-sibling::div[1]/button/span')
|
||||
|
||||
|
@ -12,9 +12,11 @@ export class NavigationMenuPage {
|
||||
readonly buttonMyApplications = (): Locator =>
|
||||
this.page.locator('a[href$="my-applications"]', { hasText: 'My applications' })
|
||||
|
||||
readonly buttonTalents = (): Locator => this.page.locator('a[href$="talents"]', { hasText: 'Talents' })
|
||||
readonly buttonVacancies = (): Locator => this.page.locator('a[href$="vacancies"]', { hasText: 'Vacancies' })
|
||||
readonly buttonCompanies = (): Locator => this.page.locator('a[href$="organizations"]', { hasText: 'Companies' })
|
||||
readonly navigator = (): Locator => this.page.locator('.antiPanel-navigator')
|
||||
readonly buttonTalents = (): Locator => this.navigator().locator('a[href$="talents"]', { hasText: 'Talents' })
|
||||
readonly buttonVacancies = (): Locator => this.navigator().locator('a[href$="vacancies"]', { hasText: 'Vacancies' })
|
||||
readonly buttonCompanies = (): Locator =>
|
||||
this.navigator().locator('a[href$="organizations"]', { hasText: 'Companies' })
|
||||
|
||||
// Action methods to click on each button
|
||||
async clickButtonApplications (): Promise<void> {
|
||||
|
@ -8,8 +8,8 @@ export class RecruitingPage {
|
||||
}
|
||||
|
||||
recruitApplication = (): Locator => this.page.locator('[id="app-recruit\\:string\\:RecruitApplication"]')
|
||||
talentsNavElement = (): Locator => this.page.locator('text=Talents')
|
||||
reviews = (): Locator => this.page.locator('text=Reviews')
|
||||
talentsNavElement = (): Locator => this.page.locator('.antiPanel-navigator').locator('text=Talents')
|
||||
reviews = (): Locator => this.page.locator('.antiPanel-navigator').locator('text=Reviews')
|
||||
reviewButton = (): Locator => this.page.getByRole('button', { name: 'Review', exact: true })
|
||||
|
||||
frontendEngineerOption = (): Locator => this.page.locator('td:has-text("Frontend Engineer")')
|
||||
@ -27,13 +27,13 @@ export class RecruitingPage {
|
||||
newTalentModalPath = (): Locator => this.page.getByText('Person New Talent')
|
||||
recruitApplicationButton = (): Locator => this.page.locator('[id="app-recruit\\:string\\:RecruitApplication"]')
|
||||
applicationsLink = (): Locator => this.page.locator('text=/^Applications/')
|
||||
talentsLink = (): Locator => this.page.locator('text=Talents')
|
||||
vacanciesLink = (): Locator => this.page.locator('text=Vacancies')
|
||||
talentsLink = (): Locator => this.page.locator('.antiPanel-navigator').locator('text=Talents')
|
||||
vacanciesLink = (): Locator => this.page.locator('.antiPanel-navigator').locator('text=Vacancies')
|
||||
softwareEngineerLink = (): Locator => this.page.locator('text=Software Engineer')
|
||||
applicationLabelChunterButton = (): Locator =>
|
||||
this.page.locator('[id="app-chunter\\:string\\:ApplicationLabelChunter"]')
|
||||
|
||||
generalChatLink = (): Locator => this.page.locator('text=general')
|
||||
generalChatLink = (): Locator => this.page.locator('.antiPanel-navigator').locator('text=general')
|
||||
contactsButton = (): Locator => this.page.locator('[id="app-contact\\:string\\:Contacts"]')
|
||||
employeeSection = (): Locator => this.page.getByRole('button', { name: 'Employee' })
|
||||
johnAppleseed = (): Locator => this.page.locator('text=Appleseed John')
|
||||
|
@ -41,14 +41,14 @@ export class TalentsPage extends CommonRecruitingPage {
|
||||
|
||||
recruitApplicationButton = (): Locator => this.page.locator('[id="app-recruit\\:string\\:RecruitApplication"]')
|
||||
|
||||
talentsTab = (): Locator => this.page.locator('text=Talents')
|
||||
talentsTab = (): Locator => this.page.locator('.antiPanel-navigator').locator('text=Talents')
|
||||
newTalentButton = (): Locator => this.page.locator('button:has-text("New Talent")')
|
||||
addSocialLinksButton = (): Locator => this.page.locator('[id="presentation\\:string\\:AddSocialLinks"]')
|
||||
emailSelectorButton = (): Locator => this.page.locator('.antiPopup').locator('text=Email')
|
||||
confirmEmailButton = (): Locator => this.page.locator('#channel-ok.antiButton')
|
||||
createTalentButton = (): Locator => this.page.locator('.antiCard button:has-text("Create")')
|
||||
popupPanel = (): Locator => this.page.locator('.popupPanel')
|
||||
talentsLink = (): Locator => this.page.locator('text=Talents')
|
||||
talentsLink = (): Locator => this.page.locator('.antiPanel-navigator').locator('text=Talents')
|
||||
firstNameInput = (): Locator => this.page.locator('[placeholder="First name"]')
|
||||
lastNameInput = (): Locator => this.page.locator('[placeholder="Last name"]')
|
||||
skillsButton = (): Locator =>
|
||||
@ -62,7 +62,7 @@ export class TalentsPage extends CommonRecruitingPage {
|
||||
selectSkillButton = (skillName: string): Locator => this.page.locator(`button:has-text("${skillName}") .check`)
|
||||
createCandidateButton = (): Locator => this.page.locator('button:has-text("Create")')
|
||||
openOtherSkills = (): Locator => this.page.getByText('Other')
|
||||
skillsLink = (): Locator => this.page.locator('text=Skills')
|
||||
skillsLink = (): Locator => this.page.locator('.antiPanel-navigator').locator('text=Skills')
|
||||
newSkillButton = (): Locator => this.page.getByRole('button', { name: 'Skill', exact: true })
|
||||
emailContact = (): Locator =>
|
||||
this.page.locator('div[class^="popupPanel-body__header"] button[id="gmail:string:Email"]')
|
||||
|
@ -44,7 +44,7 @@ export class VacanciesPage extends CommonRecruitingPage {
|
||||
readonly recruitApplicationButton = (): Locator =>
|
||||
this.page.locator('[id="app-recruit\\:string\\:RecruitApplication"]')
|
||||
|
||||
readonly vacanciesMenuLink = (): Locator => this.page.locator('text=Vacancies')
|
||||
readonly vacanciesMenuLink = (): Locator => this.page.locator('.antiPanel-navigator').locator('text=Vacancies')
|
||||
readonly createVacancyButton = (): Locator => this.page.locator('button:has-text("Vacancy")')
|
||||
readonly vacancyInputField = (): Locator => this.page.locator('form [placeholder="Software\\ Engineer"]')
|
||||
readonly createButton = (): Locator => this.page.locator('form button:has-text("Create")')
|
||||
|
@ -53,7 +53,7 @@ export class CommonTrackerPage extends CalendarPage {
|
||||
submitDatePopup = (): Locator => this.page.locator('div.date-popup-container button[type="submit"]')
|
||||
|
||||
trackerApplicationButton = (): Locator => this.page.locator('[id="app-tracker\\:string\\:TrackerApplication"]')
|
||||
componentsLink = (): Locator => this.page.locator('text=Components')
|
||||
componentsLink = (): Locator => this.page.locator('.antiPanel-navigator').locator('text=Components')
|
||||
createComponentButton = (): Locator => this.page.getByRole('button', { name: 'Component', exact: true })
|
||||
componentNameInput = (): Locator => this.page.locator('[placeholder="Component\\ name"]')
|
||||
createComponentConfirmButton = (): Locator => this.page.locator('button:has-text("Create component")')
|
||||
|
@ -8,7 +8,7 @@ import { NewIssue } from './types'
|
||||
const retryOptions = { intervals: [1000, 1500, 2500], timeout: 60000 }
|
||||
export class IssuesPage extends CommonTrackerPage {
|
||||
modelSelectorAll = (): Locator => this.page.locator('label[data-id="tab-all"]')
|
||||
issues = (): Locator => this.page.locator('text="Issues"')
|
||||
issues = (): Locator => this.page.locator('.antiPanel-navigator').locator('text="Issues"')
|
||||
subIssues = (): Locator => this.page.locator('button:has-text("Add sub-issue")')
|
||||
newIssue = (): Locator => this.page.locator('#new-issue')
|
||||
modelSelectorActive = (): Locator => this.page.locator('label[data-id="tab-active"]')
|
||||
@ -109,7 +109,7 @@ export class IssuesPage extends CommonTrackerPage {
|
||||
doneHeaderKanban = (): Locator => this.page.locator('.header :text-is("Done")').first()
|
||||
canceledHeaderKanban = (): Locator => this.page.locator('.header :text-is("Canceled")').first()
|
||||
|
||||
myIssuesButton = (): Locator => this.page.locator('text="My issues"')
|
||||
myIssuesButton = (): Locator => this.page.locator('.antiPanel-navigator').locator('text="My issues"')
|
||||
assignedTab = (): Locator => this.page.locator('[data-id="tab-assigned"]')
|
||||
createdTab = (): Locator => this.page.locator('[data-id="tab-created"]')
|
||||
subscribedTab = (): Locator => this.page.locator('[data-id="tab-subscribed"]')
|
||||
@ -140,7 +140,7 @@ export class IssuesPage extends CommonTrackerPage {
|
||||
timeSpentReports = (): Locator => this.page.getByText('Time spent reports', { exact: true })
|
||||
addTimeReport = (): Locator => this.page.locator('text="Add time report"')
|
||||
issueName = (name: string): Locator => this.page.locator(`text="${name}"`)
|
||||
issuesButton = (): Locator => this.page.locator('text="Issues"')
|
||||
issuesButton = (): Locator => this.page.locator('.antiPanel-navigator').locator('text="Issues"')
|
||||
viewButton = (): Locator => this.page.locator('button[data-id="btn-viewOptions"]')
|
||||
orderingButton = (): Locator => this.page.locator('.ordering button')
|
||||
modifiedDateMenuItem = (): Locator => this.page.locator('button.menu-item', { hasText: 'Modified date' })
|
||||
|
@ -344,7 +344,7 @@ test.describe('Planning ToDo tests', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test('Change ToDo start and end times by dragging', async ({ page }) => {
|
||||
test.skip('Change ToDo start and end times by dragging', async ({ page }) => {
|
||||
const planningPage = new PlanningPage(page)
|
||||
const planningNavigationMenuPage = new PlanningNavigationMenuPage(page)
|
||||
const dateEnd = new Date()
|
||||
|
Loading…
Reference in New Issue
Block a user