mirror of
https://github.com/hcengineering/platform.git
synced 2025-01-03 00:43:59 +03:00
UBERF-8993: Part2 (#7532)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
0db7adccd6
commit
35981be705
1
.gitignore
vendored
1
.gitignore
vendored
@ -106,3 +106,4 @@ tests/profiles
|
||||
dump
|
||||
**/logs/**
|
||||
dev/tool/history.json
|
||||
.aider*
|
||||
|
@ -28,22 +28,23 @@ function $push (document: Doc, keyval: Record<string, PropertyType>): void {
|
||||
if (doc[key] === undefined) {
|
||||
doc[key] = []
|
||||
}
|
||||
const val = keyval[key]
|
||||
if (typeof val === 'object') {
|
||||
const kvk = keyval[key]
|
||||
if (typeof kvk === 'object' && kvk != null) {
|
||||
const arr = doc[key] as Array<any>
|
||||
const desc = val as Position<PropertyType>
|
||||
const desc = kvk as Position<PropertyType>
|
||||
if ('$each' in desc) {
|
||||
if (arr != null) {
|
||||
if (arr != null && Array.isArray(arr)) {
|
||||
arr.splice(desc.$position ?? 0, 0, ...desc.$each)
|
||||
}
|
||||
} else {
|
||||
arr.push(val)
|
||||
arr.push(kvk)
|
||||
}
|
||||
} else {
|
||||
if (doc[key] == null) {
|
||||
doc[key] = []
|
||||
if (doc[key] === null || doc[key] === undefined) {
|
||||
doc[key] = [kvk]
|
||||
} else {
|
||||
doc[key].push(kvk)
|
||||
}
|
||||
doc[key].push(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -55,15 +56,16 @@ function $pull (document: Doc, keyval: Record<string, PropertyType>): void {
|
||||
doc[key] = []
|
||||
}
|
||||
const arr = doc[key] as Array<any>
|
||||
if (typeof keyval[key] === 'object' && keyval[key] !== null) {
|
||||
const { $in } = keyval[key] as PullArray<PropertyType>
|
||||
const kvk = keyval[key]
|
||||
if (typeof kvk === 'object' && kvk !== null) {
|
||||
const { $in } = kvk as PullArray<PropertyType>
|
||||
|
||||
doc[key] = (arr ?? []).filter((val) => {
|
||||
if ($in !== undefined) {
|
||||
return !$in.includes(val)
|
||||
} else {
|
||||
// We need to match all fields
|
||||
for (const [kk, kv] of Object.entries(keyval[key])) {
|
||||
for (const [kk, kv] of Object.entries(kvk)) {
|
||||
if (val[kk] !== kv) {
|
||||
return true
|
||||
}
|
||||
@ -72,7 +74,7 @@ function $pull (document: Doc, keyval: Record<string, PropertyType>): void {
|
||||
}
|
||||
})
|
||||
} else {
|
||||
doc[key] = (arr ?? []).filter((val) => val !== keyval[key])
|
||||
doc[key] = (arr ?? []).filter((val) => val !== kvk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -93,13 +93,14 @@
|
||||
dispatch('update', selectedObjects)
|
||||
}
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let selection = 0
|
||||
let list: ListView
|
||||
|
||||
async function handleSelection (evt: Event | undefined, objects: Doc[], selection: number): Promise<void> {
|
||||
const item = objects[selection]
|
||||
if (item === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!multiSelect) {
|
||||
if (allowDeselect) {
|
||||
@ -140,7 +141,7 @@
|
||||
showPopup(c.component, c.props ?? {}, 'top', async (res) => {
|
||||
if (res != null) {
|
||||
// We expect reference to new object.
|
||||
const newPerson = await client.findOne(_class, { _id: res })
|
||||
const newPerson = await getClient().findOne(_class, { _id: res })
|
||||
if (newPerson !== undefined) {
|
||||
search = c.update?.(newPerson) ?? ''
|
||||
dispatch('created', newPerson)
|
||||
@ -163,7 +164,7 @@
|
||||
}
|
||||
|
||||
function findObjectPresenter (_class: Ref<Class<Doc>>): void {
|
||||
const presenterMixin = client.getHierarchy().classHierarchyMixin(_class, view.mixin.ObjectPresenter)
|
||||
const presenterMixin = getClient().getHierarchy().classHierarchyMixin(_class, view.mixin.ObjectPresenter)
|
||||
if (presenterMixin?.presenter !== undefined) {
|
||||
getResource(presenterMixin.presenter)
|
||||
.then((result) => {
|
||||
|
@ -46,7 +46,6 @@
|
||||
|
||||
let categories: ObjectSearchCategory[] = []
|
||||
let categoryStatus: Record<Ref<ObjectSearchCategory>, number> = {}
|
||||
const client = getClient()
|
||||
|
||||
let category: ObjectSearchCategory | undefined
|
||||
const categoryQuery: DocumentQuery<ObjectSearchCategory> = {
|
||||
@ -56,10 +55,12 @@
|
||||
categoryQuery._id = { $in: allowCategory }
|
||||
}
|
||||
|
||||
client.findAll(presentation.class.ObjectSearchCategory, categoryQuery).then((r) => {
|
||||
categories = r.filter((it) => hasResource(it.query))
|
||||
category = categories[0]
|
||||
})
|
||||
void getClient()
|
||||
.findAll(presentation.class.ObjectSearchCategory, categoryQuery)
|
||||
.then((r) => {
|
||||
categories = r.filter((it) => hasResource(it.query))
|
||||
category = categories[0]
|
||||
})
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@ -96,7 +97,7 @@
|
||||
key.preventDefault()
|
||||
key.stopPropagation()
|
||||
const item = items[selection]
|
||||
if (item) {
|
||||
if (item != null) {
|
||||
dispatchItem(item)
|
||||
return true
|
||||
} else {
|
||||
@ -106,7 +107,7 @@
|
||||
return false
|
||||
}
|
||||
|
||||
export function done () {}
|
||||
export function done (): void {}
|
||||
|
||||
const updateItems = reduceCalls(async function updateItems (
|
||||
cat: ObjectSearchCategory | undefined,
|
||||
@ -120,6 +121,7 @@
|
||||
const newCategoryStatus: Record<Ref<ObjectSearchCategory>, number> = {}
|
||||
const f = await getResource(cat.query)
|
||||
|
||||
const client = getClient()
|
||||
const result = await f(client, query, { in: relatedDocuments, nin: ignore })
|
||||
// We need to sure, results we return is for proper category.
|
||||
if (cat._id === category?._id) {
|
||||
|
@ -53,7 +53,7 @@ import { getRawCurrentLocation, workspaceId, type AnyComponent, type AnySvelteCo
|
||||
import view, { type AttributeCategory, type AttributeEditor } from '@hcengineering/view'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { onDestroy } from 'svelte'
|
||||
import { get, writable, type Writable } from 'svelte/store'
|
||||
import { get, writable } from 'svelte/store'
|
||||
|
||||
import { type KeyedAttribute } from '..'
|
||||
import { OptimizeQueryMiddleware, PresentationPipelineImpl, type PresentationPipeline } from './pipeline'
|
||||
@ -63,7 +63,7 @@ export { reduceCalls } from '@hcengineering/core'
|
||||
|
||||
let liveQuery: LQ
|
||||
let rawLiveQuery: LQ
|
||||
let client: TxOperations & Client & OptimisticTxes
|
||||
let client: TxOperations & Client
|
||||
let pipeline: PresentationPipeline
|
||||
|
||||
const txListeners: Array<(...tx: Tx[]) => void> = []
|
||||
@ -89,13 +89,11 @@ export function removeTxListener (l: (tx: Tx) => void): void {
|
||||
}
|
||||
}
|
||||
|
||||
export interface OptimisticTxes {
|
||||
pendingCreatedDocs: Writable<Record<Ref<Doc>, boolean>>
|
||||
}
|
||||
|
||||
export const uiContext = new MeasureMetricsContext('client-ui', {})
|
||||
|
||||
class UIClient extends TxOperations implements Client, OptimisticTxes {
|
||||
export const pendingCreatedDocs = writable<Record<Ref<Doc>, boolean>>({})
|
||||
|
||||
class UIClient extends TxOperations implements Client {
|
||||
hook = getMetadata(plugin.metadata.ClientHook)
|
||||
constructor (
|
||||
client: Client,
|
||||
@ -105,14 +103,9 @@ class UIClient extends TxOperations implements Client, OptimisticTxes {
|
||||
}
|
||||
|
||||
protected pendingTxes = new Set<Ref<Tx>>()
|
||||
protected _pendingCreatedDocs = writable<Record<Ref<Doc>, boolean>>({})
|
||||
|
||||
get pendingCreatedDocs (): typeof this._pendingCreatedDocs {
|
||||
return this._pendingCreatedDocs
|
||||
}
|
||||
|
||||
async doNotify (...tx: Tx[]): Promise<void> {
|
||||
const pending = get(this._pendingCreatedDocs)
|
||||
const pending = get(pendingCreatedDocs)
|
||||
let pendingUpdated = false
|
||||
tx.forEach((t) => {
|
||||
if (this.pendingTxes.has(t._id)) {
|
||||
@ -129,7 +122,7 @@ class UIClient extends TxOperations implements Client, OptimisticTxes {
|
||||
}
|
||||
})
|
||||
if (pendingUpdated) {
|
||||
this._pendingCreatedDocs.set(pending)
|
||||
pendingCreatedDocs.set(pending)
|
||||
}
|
||||
|
||||
// We still want to notify about all transactions because there might be queries created after
|
||||
@ -214,9 +207,9 @@ class UIClient extends TxOperations implements Client, OptimisticTxes {
|
||||
}
|
||||
|
||||
if (innerTx._class === core.class.TxCreateDoc) {
|
||||
const pending = get(this._pendingCreatedDocs)
|
||||
const pending = get(pendingCreatedDocs)
|
||||
pending[innerTx.objectId] = true
|
||||
this._pendingCreatedDocs.set(pending)
|
||||
pendingCreatedDocs.set(pending)
|
||||
}
|
||||
|
||||
this.pendingTxes.add(tx._id)
|
||||
@ -231,11 +224,33 @@ class UIClient extends TxOperations implements Client, OptimisticTxes {
|
||||
}
|
||||
}
|
||||
|
||||
const hierarchyProxy = new Proxy(
|
||||
{},
|
||||
{
|
||||
get (target, p, receiver) {
|
||||
const h = client.getHierarchy()
|
||||
return Reflect.get(h, p)
|
||||
}
|
||||
}
|
||||
) as TxOperations & Client
|
||||
|
||||
// We need a proxy to handle all the calls to the proper client.
|
||||
const clientProxy = new Proxy(
|
||||
{},
|
||||
{
|
||||
get (target, p, receiver) {
|
||||
if (p === 'getHierarchy') {
|
||||
return () => hierarchyProxy
|
||||
}
|
||||
return Reflect.get(client, p)
|
||||
}
|
||||
}
|
||||
) as TxOperations & Client
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function getClient (): TxOperations & Client & OptimisticTxes {
|
||||
return client
|
||||
export function getClient (): TxOperations & Client {
|
||||
return clientProxy
|
||||
}
|
||||
|
||||
let txQueue: Tx[] = []
|
||||
@ -252,6 +267,7 @@ export function addRefreshListener (r: RefreshListener): void {
|
||||
* @public
|
||||
*/
|
||||
export async function setClient (_client: Client): Promise<void> {
|
||||
pendingCreatedDocs.set({})
|
||||
if (liveQuery !== undefined) {
|
||||
await liveQuery.close()
|
||||
}
|
||||
@ -700,7 +716,7 @@ export function isSpace (space: Doc): space is Space {
|
||||
}
|
||||
|
||||
export function isSpaceClass (_class: Ref<Class<Doc>>): boolean {
|
||||
return getClient().getHierarchy().isDerived(_class, core.class.Space)
|
||||
return client.getHierarchy().isDerived(_class, core.class.Space)
|
||||
}
|
||||
|
||||
export function setPresentationCookie (token: string, workspaceId: string): void {
|
||||
|
@ -59,6 +59,9 @@
|
||||
}
|
||||
|
||||
function onKeydown (e: KeyboardEvent): void {
|
||||
if (e.key === undefined) {
|
||||
return
|
||||
}
|
||||
const key = e.key.toLowerCase()
|
||||
const target = e.target as HTMLInputElement
|
||||
if (key !== 'backspace' && key !== 'delete') return
|
||||
|
@ -63,7 +63,7 @@ class FocusManagerImpl implements FocusManager {
|
||||
return
|
||||
}
|
||||
this.current = this.elements.findIndex((it) => it.id === idx) ?? 0
|
||||
this.elements[Math.abs(this.current) % this.elements.length].focus()
|
||||
this.elements[Math.abs(this.current) % this.elements.length]?.focus()
|
||||
}
|
||||
|
||||
setFocusPos (order: number): void {
|
||||
@ -73,7 +73,7 @@ class FocusManagerImpl implements FocusManager {
|
||||
const idx = this.elements.findIndex((it) => it.order === order)
|
||||
if (idx !== undefined) {
|
||||
this.current = idx
|
||||
this.elements[Math.abs(this.current) % this.elements.length].focus()
|
||||
this.elements[Math.abs(this.current) % this.elements.length]?.focus()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -149,16 +149,17 @@ export function closePopup (category?: string): void {
|
||||
} else {
|
||||
for (let i = popups.length - 1; i >= 0; i--) {
|
||||
if (popups[i].type !== 'popup') continue
|
||||
if ((popups[i] as CompAndProps).options.fixed !== true) {
|
||||
const isClosing = (popups[i] as CompAndProps).closing ?? false
|
||||
const popi = popups[i] as CompAndProps
|
||||
if (popi.options.fixed !== true) {
|
||||
const isClosing = popi.closing ?? false
|
||||
if (popups[i].type === 'popup') {
|
||||
;(popups[i] as CompAndProps).closing = true
|
||||
popi.closing = true
|
||||
}
|
||||
if (!isClosing) {
|
||||
// To prevent possible recursion, we need to check if we call some code from popup close, to do close.
|
||||
;(popups[i] as CompAndProps).onClose?.(undefined)
|
||||
popi.onClose?.(undefined)
|
||||
}
|
||||
;(popups[i] as CompAndProps).closing = false
|
||||
popi.closing = false
|
||||
popups.splice(i, 1)
|
||||
break
|
||||
}
|
||||
|
@ -214,8 +214,12 @@ export function replaceURLs (text: string): string {
|
||||
* @returns {string} string with parsed URL
|
||||
*/
|
||||
export function parseURL (text: string): string {
|
||||
const matches = autolinker.parse(text, { urls: true })
|
||||
return matches.length > 0 ? matches[0].getAnchorHref() : ''
|
||||
try {
|
||||
const matches = autolinker.parse(text ?? '', { urls: true })
|
||||
return matches.length > 0 ? matches[0].getAnchorHref() : ''
|
||||
} catch (err: any) {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -16,7 +16,7 @@
|
||||
import contact, { Person, PersonAccount } from '@hcengineering/contact'
|
||||
import { personAccountByIdStore, personByIdStore } from '@hcengineering/contact-resources'
|
||||
import { Class, Doc, getCurrentAccount, Markup, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||
import { getClient, MessageViewer } from '@hcengineering/presentation'
|
||||
import { getClient, MessageViewer, pendingCreatedDocs } from '@hcengineering/presentation'
|
||||
import { AttachmentDocList, AttachmentImageSize } from '@hcengineering/attachment-resources'
|
||||
import { getDocLinkTitle } from '@hcengineering/view-resources'
|
||||
import { Action, Button, IconEdit, ShowMore } from '@hcengineering/ui'
|
||||
@ -58,7 +58,6 @@
|
||||
export let onReply: ((message: ActivityMessage) => void) | undefined = undefined
|
||||
|
||||
const client = getClient()
|
||||
const { pendingCreatedDocs } = client
|
||||
const hierarchy = client.getHierarchy()
|
||||
const STALE_TIMEOUT_MS = 5000
|
||||
const currentAccount = getCurrentAccount()
|
||||
|
@ -82,9 +82,11 @@
|
||||
included = []
|
||||
}
|
||||
|
||||
$: employees = Array.from(
|
||||
(value ?? []).map((it) => $personAccountByIdStore.get(it as Ref<PersonAccount>)?.person)
|
||||
).filter((it) => it !== undefined) as Ref<Employee>[]
|
||||
$: employees = Array.isArray(value)
|
||||
? (Array.from((value ?? []).map((it) => $personAccountByIdStore.get(it as Ref<PersonAccount>)?.person)).filter(
|
||||
(it) => it !== undefined
|
||||
) as Ref<Employee>[])
|
||||
: []
|
||||
|
||||
$: docQuery =
|
||||
excluded.length === 0 && included.length === 0
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact, { Contact, Employee, Person } from '@hcengineering/contact'
|
||||
import contact, { Person } from '@hcengineering/contact'
|
||||
import type { Class, Doc, DocumentQuery, Ref } from '@hcengineering/core'
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import { ObjectCreate, getClient } from '@hcengineering/presentation'
|
||||
@ -43,8 +43,8 @@
|
||||
|
||||
export let sort: ((a: Person, b: Person) => number) | undefined = undefined
|
||||
|
||||
function filter (items: Ref<Person>[]): Ref<Person>[] {
|
||||
return items.filter((it, idx, arr) => arr.indexOf(it) === idx)
|
||||
function filter (items: Ref<Person>[] | undefined): Ref<Person>[] {
|
||||
return (items ?? []).filter((it, idx, arr) => arr.indexOf(it) === idx)
|
||||
}
|
||||
|
||||
let persons: Person[] = filter(items)
|
||||
@ -55,11 +55,10 @@
|
||||
.filter((p) => p !== undefined) as Person[]
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
|
||||
async function addPerson (evt: Event): Promise<void> {
|
||||
const accounts = new Set(
|
||||
client
|
||||
getClient()
|
||||
.getModel()
|
||||
.findAllSync(contact.class.PersonAccount, {})
|
||||
.map((p) => p.person)
|
||||
@ -72,7 +71,7 @@
|
||||
allowDeselect: false,
|
||||
selectedUsers: filter(items),
|
||||
filter: (it: Doc) => {
|
||||
const h = client.getHierarchy()
|
||||
const h = getClient().getHierarchy()
|
||||
if (h.hasMixin(it, contact.mixin.Employee)) {
|
||||
const isActive = h.as(it, contact.mixin.Employee).active
|
||||
const isSelected = items.some((selectedItem) => selectedItem === it._id)
|
||||
|
@ -46,7 +46,7 @@
|
||||
if (space !== value.space) {
|
||||
const children = await findChildren(value)
|
||||
for (const child of children) {
|
||||
await client.updateDoc(document.class.Document, value.space, child, {
|
||||
await ops.updateDoc(document.class.Document, value.space, child, {
|
||||
space
|
||||
})
|
||||
}
|
||||
|
@ -1058,7 +1058,11 @@ export async function restorePassword (token: string, password: string): Promise
|
||||
}
|
||||
|
||||
async function handleStatusError (message: string, err: Status): Promise<void> {
|
||||
if (err.code === platform.status.InvalidPassword || err.code === platform.status.AccountNotFound) {
|
||||
if (
|
||||
err.code === platform.status.InvalidPassword ||
|
||||
err.code === platform.status.AccountNotFound ||
|
||||
err.code === platform.status.InvalidOtp
|
||||
) {
|
||||
// No need to send to analytics
|
||||
return
|
||||
}
|
||||
|
@ -340,7 +340,7 @@
|
||||
console.log(`Error exiting fullscreen mode: ${err.message} (${err.name})`)
|
||||
$isFullScreen = false
|
||||
})
|
||||
} else if (!document.fullscreenElement && needFullScreen) {
|
||||
} else if (!document.fullscreenElement && needFullScreen && roomEl != null) {
|
||||
roomEl
|
||||
.requestFullscreen()
|
||||
.then(() => {
|
||||
@ -355,7 +355,7 @@
|
||||
|
||||
function onFullScreen (): void {
|
||||
const needFullScreen = !$isFullScreen
|
||||
if (!document.fullscreenElement && needFullScreen) {
|
||||
if (!document.fullscreenElement && needFullScreen && roomEl != null) {
|
||||
roomEl
|
||||
.requestFullscreen()
|
||||
.then(() => {
|
||||
|
@ -15,7 +15,15 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { AttachmentStyleBoxCollabEditor } from '@hcengineering/attachment-resources'
|
||||
import core, { ClassifierKind, type CollaborativeDoc, Data, Doc, Mixin, Ref } from '@hcengineering/core'
|
||||
import core, {
|
||||
ClassifierKind,
|
||||
type CollaborativeDoc,
|
||||
Data,
|
||||
Doc,
|
||||
type MarkupBlobRef,
|
||||
Mixin,
|
||||
Ref
|
||||
} from '@hcengineering/core'
|
||||
import notification from '@hcengineering/notification'
|
||||
import { Panel } from '@hcengineering/panel'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
@ -37,7 +45,7 @@
|
||||
let object: Required<Vacancy>
|
||||
let rawName: string = ''
|
||||
let rawDesc: string = ''
|
||||
let rawFullDesc: CollaborativeDoc
|
||||
let rawFullDesc: MarkupBlobRef | null = null
|
||||
let lastId: Ref<Vacancy> | undefined = undefined
|
||||
|
||||
let showAllMixins = false
|
||||
|
@ -35,10 +35,8 @@
|
||||
export let groupByKey: string
|
||||
export let config: (string | BuildModelKey)[]
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
const assigneeAttribute = hierarchy.getAttribute(recruit.class.Applicant, 'assignee')
|
||||
const isTitleHidden = client.getHierarchy().getAttribute(recruit.mixin.Candidate, 'title').hidden
|
||||
const assigneeAttribute = getClient().getHierarchy().getAttribute(recruit.class.Applicant, 'assignee')
|
||||
const isTitleHidden = getClient().getHierarchy().getAttribute(recruit.mixin.Candidate, 'title').hidden
|
||||
|
||||
$: channels = (object.$lookup?.attachedTo as WithLookup<Candidate>)?.$lookup?.channels
|
||||
|
||||
@ -70,7 +68,7 @@
|
||||
<Avatar person={object.$lookup?.attachedTo} size={'medium'} name={object.$lookup?.attachedTo?.name} />
|
||||
<div class="flex-grow flex-col min-w-0 ml-2">
|
||||
<div class="fs-title over-underline lines-limit-2">
|
||||
{object.$lookup?.attachedTo ? getName(client.getHierarchy(), object.$lookup.attachedTo) : ''}
|
||||
{object.$lookup?.attachedTo ? getName(getClient().getHierarchy(), object.$lookup.attachedTo) : ''}
|
||||
</div>
|
||||
{#if !isTitleHidden && enabledConfig(config, 'title')}
|
||||
<div class="text-sm lines-limit-2">{object.$lookup?.attachedTo?.title ?? ''}</div>
|
||||
@ -107,7 +105,7 @@
|
||||
shrink={1}
|
||||
value={object.status}
|
||||
onChange={(status) => {
|
||||
client.update(object, { status })
|
||||
getClient().update(object, { status })
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
@ -120,7 +118,7 @@
|
||||
shouldRender={object.dueDate !== null && object.dueDate !== undefined}
|
||||
shouldIgnoreOverdue={isDone}
|
||||
onChange={async (e) => {
|
||||
await client.update(object, { dueDate: e })
|
||||
await getClient().update(object, { dueDate: e })
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
@ -122,7 +122,7 @@
|
||||
}
|
||||
const handleSelect = async (event: CustomEvent): Promise<void> => {
|
||||
selected = event.detail as AnyAttribute
|
||||
if (selected != null) {
|
||||
if (selected?._id != null) {
|
||||
const exist = (await client.findOne(selected.attributeOf, { [selected.name]: { $exists: true } })) !== undefined
|
||||
$settingsStore = { id: selected._id, component: EditAttribute, props: { attribute: selected, exist, disabled } }
|
||||
}
|
||||
|
@ -55,7 +55,9 @@
|
||||
specials = newSpecials
|
||||
}
|
||||
|
||||
$: updateSpecials(model, space)
|
||||
$: if (model != null) {
|
||||
void updateSpecials(model, space)
|
||||
}
|
||||
$: visible =
|
||||
(!deselect && currentSpace !== undefined && currentSpecial !== undefined && space._id === currentSpace) ||
|
||||
forciblyСollapsed
|
||||
@ -64,7 +66,7 @@
|
||||
{#if specials}
|
||||
<TreeNode
|
||||
_id={space?._id}
|
||||
icon={space?.icon === view.ids.IconWithEmoji ? IconWithEmoji : space?.icon ?? model.icon}
|
||||
icon={space?.icon === view.ids.IconWithEmoji ? IconWithEmoji : space?.icon ?? model?.icon}
|
||||
iconProps={space?.icon === view.ids.IconWithEmoji
|
||||
? { icon: space.color }
|
||||
: {
|
||||
|
@ -108,22 +108,22 @@ export const completionConfig: Partial<CompletionOptions> = {
|
||||
props: {
|
||||
...props,
|
||||
close: () => {
|
||||
component.destroy()
|
||||
component?.destroy()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
onUpdate (props: SuggestionProps) {
|
||||
component.updateProps(props)
|
||||
component?.updateProps(props)
|
||||
},
|
||||
onKeyDown (props: SuggestionKeyDownProps) {
|
||||
if (props.event.key === 'Escape') {
|
||||
props.event.stopPropagation()
|
||||
}
|
||||
return component.onKeyDown(props)
|
||||
return component?.onKeyDown(props)
|
||||
},
|
||||
onExit () {
|
||||
component.destroy()
|
||||
component?.destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -181,22 +181,22 @@ export function inlineCommandsConfig (
|
||||
props: {
|
||||
...props,
|
||||
close: () => {
|
||||
component.destroy()
|
||||
component?.destroy()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
onUpdate (props: SuggestionProps) {
|
||||
component.updateProps(props)
|
||||
component?.updateProps(props)
|
||||
},
|
||||
onKeyDown (props: SuggestionKeyDownProps) {
|
||||
if (props.event.key === 'Escape') {
|
||||
props.event.stopPropagation()
|
||||
}
|
||||
return component.onKeyDown(props)
|
||||
return component?.onKeyDown(props)
|
||||
},
|
||||
onExit () {
|
||||
component.destroy()
|
||||
component?.destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,9 @@
|
||||
specials = newSpecials
|
||||
}
|
||||
|
||||
$: updateSpecials(model, space)
|
||||
$: if (model != null) {
|
||||
void updateSpecials(model, space)
|
||||
}
|
||||
$: visible =
|
||||
(!deselect && currentSpace !== undefined && currentSpecial !== undefined && space._id === currentSpace) ||
|
||||
forciblyСollapsed
|
||||
|
@ -30,8 +30,6 @@
|
||||
|
||||
let resActions = actions
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let loaded = false
|
||||
|
||||
const order: Record<ActionGroup, number> = {
|
||||
@ -44,7 +42,7 @@
|
||||
remove: 7
|
||||
}
|
||||
|
||||
void getActions(client, object, baseMenuClass, mode).then((result) => {
|
||||
void getActions(getClient(), object, baseMenuClass, mode).then((result) => {
|
||||
const filtered = result.filter((a) => {
|
||||
if (excludedActions.includes(a._id)) {
|
||||
return false
|
||||
@ -63,7 +61,7 @@
|
||||
inline: a.inline,
|
||||
group: a.context.group ?? 'other',
|
||||
action: async (_: any, evt: Event) => {
|
||||
if (overrides.has(a._id)) {
|
||||
if (overrides?.has(a._id)) {
|
||||
overrides.get(a._id)?.(object, evt)
|
||||
return
|
||||
}
|
||||
|
@ -178,11 +178,11 @@
|
||||
|
||||
const keyDown = (event: KeyboardEvent, index: number) => {
|
||||
if (event.key === 'ArrowDown') {
|
||||
actionElements[(index + 1) % actionElements.length].focus()
|
||||
actionElements[(index + 1) % actionElements.length]?.focus()
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowUp') {
|
||||
actionElements[(actionElements.length + index - 1) % actionElements.length].focus()
|
||||
actionElements[(actionElements.length + index - 1) % actionElements.length]?.focus()
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowLeft') {
|
||||
|
@ -604,6 +604,7 @@ export class DocumentContentPage extends DocumentCommonPage {
|
||||
await expect(this.editDocumentSpace).not.toBeVisible()
|
||||
await expect(this.createNewTemplateFromSpace).not.toBeVisible()
|
||||
}
|
||||
await this.page.keyboard.press('Escape')
|
||||
}
|
||||
|
||||
async checkSpaceFormIsCreated (spaceName: string): Promise<void> {
|
||||
|
@ -31,6 +31,7 @@ import core, {
|
||||
Data,
|
||||
generateId,
|
||||
getWorkspaceId,
|
||||
isActiveMode,
|
||||
isArchivingMode,
|
||||
isMigrationMode,
|
||||
isWorkspaceCreating,
|
||||
@ -511,7 +512,7 @@ export async function selectWorkspace (
|
||||
}
|
||||
return result
|
||||
}
|
||||
if (workspaceInfo.disabled === true && workspaceInfo.mode === 'active') {
|
||||
if (workspaceInfo.disabled === true && isActiveMode(workspaceInfo.mode)) {
|
||||
ctx.error('workspace disabled', { workspaceUrl, email })
|
||||
throw new PlatformError(
|
||||
new Status(Severity.ERROR, platform.status.WorkspaceNotFound, { workspace: workspaceUrl })
|
||||
@ -1770,14 +1771,14 @@ export async function getWorkspaceInfo (
|
||||
|
||||
const [ws] = await ctx.with('get-workspace', {}, async () =>
|
||||
(await db.workspace.find(query)).filter(
|
||||
(it) => it.disabled !== true || account?.admin === true || it.mode !== 'active'
|
||||
(it) => it.disabled !== true || account?.admin === true || !isActiveMode(it.mode)
|
||||
)
|
||||
)
|
||||
if (ws == null) {
|
||||
ctx.error('no workspace', { workspace: workspace.name, email })
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||
}
|
||||
if (ws.mode !== 'archived' && _updateLastVisit && (isAccount(account) || email === systemAccountEmail)) {
|
||||
if (!isArchivingMode(ws.mode) && _updateLastVisit && (isAccount(account) || email === systemAccountEmail)) {
|
||||
void ctx.with('update-last-visit', {}, () => updateLastVisit(db, ws, account as Account))
|
||||
}
|
||||
|
||||
|
@ -204,12 +204,17 @@ test.describe('Content in the Documents tests', () => {
|
||||
await expect(documentContentPage.imageInPopup()).toBeVisible()
|
||||
await documentContentPage.fullscreenButton().click()
|
||||
await expect(documentContentPage.fullscreenImage()).toBeVisible()
|
||||
await documentContentPage.page.keyboard.press('Escape')
|
||||
await expect(documentContentPage.fullscreenImage()).toBeHidden()
|
||||
await expect(async () => {
|
||||
await documentContentPage.page.keyboard.press('Escape')
|
||||
await expect(documentContentPage.fullscreenImage()).toBeHidden()
|
||||
await expect(documentContentPage.imageInPopup()).toBeHidden()
|
||||
}).toPass(retryOptions)
|
||||
})
|
||||
|
||||
await test.step('User can open image original in the new tab', async () => {
|
||||
const pagePromise = context.waitForEvent('page')
|
||||
const pagePromise = context.waitForEvent('page', {
|
||||
timeout: 15000
|
||||
})
|
||||
await documentContentPage.clickImageOriginalButton()
|
||||
const newPage = await pagePromise
|
||||
|
||||
|
@ -29,6 +29,7 @@ test.describe('Tracker issue tests', () => {
|
||||
const newIssue: NewIssue = {
|
||||
title: `Issue with all parameters and attachments-${generateId()}`,
|
||||
description: 'Created issue with all parameters and attachments description',
|
||||
projectName: 'Default',
|
||||
status: 'In Progress',
|
||||
priority: 'Urgent',
|
||||
assignee: 'Appleseed John',
|
||||
@ -48,7 +49,8 @@ test.describe('Tracker issue tests', () => {
|
||||
test('Edit an issue', async ({ page }) => {
|
||||
const newIssue: NewIssue = {
|
||||
title: `Issue with all parameters and attachments-${generateId()}`,
|
||||
description: 'Created issue with all parameters and attachments description'
|
||||
description: 'Created issue with all parameters and attachments description',
|
||||
projectName: 'Default'
|
||||
}
|
||||
const editIssue: Issue = {
|
||||
status: 'Done',
|
||||
@ -86,7 +88,8 @@ test.describe('Tracker issue tests', () => {
|
||||
test.skip('Set parent issue', async ({ page }) => {
|
||||
const parentIssue: NewIssue = {
|
||||
title: `PARENT ISSUE-${generateId(2)}`,
|
||||
description: 'Created issue to be parent issue'
|
||||
description: 'Created issue to be parent issue',
|
||||
projectName: 'Default'
|
||||
}
|
||||
|
||||
await issuesPage.clickModelSelectorAll()
|
||||
@ -96,7 +99,8 @@ test.describe('Tracker issue tests', () => {
|
||||
const newIssue: NewIssue = {
|
||||
title: `Set parent issue during creation-${generateId(2)}`,
|
||||
description: 'Set parent issue during creation',
|
||||
parentIssue: parentIssue.title
|
||||
parentIssue: parentIssue.title,
|
||||
projectName: 'Default'
|
||||
}
|
||||
|
||||
await issuesPage.clickModelSelectorAll()
|
||||
@ -116,7 +120,8 @@ test.describe('Tracker issue tests', () => {
|
||||
await test.step('Set parent issue from issues page', async () => {
|
||||
const newIssue: NewIssue = {
|
||||
title: `Set parent issue from issues page-${generateId(2)}`,
|
||||
description: 'Set parent issue from issues page'
|
||||
description: 'Set parent issue from issues page',
|
||||
projectName: 'Default'
|
||||
}
|
||||
await issuesPage.clickModelSelectorAll()
|
||||
await issuesPage.createNewIssue(newIssue)
|
||||
@ -139,7 +144,8 @@ test.describe('Tracker issue tests', () => {
|
||||
await test.step('Set parent issue from issue details page', async () => {
|
||||
const newIssue: NewIssue = {
|
||||
title: `Set parent issue from issue details page-${generateId(2)}`,
|
||||
description: 'Set parent issue from issue details page'
|
||||
description: 'Set parent issue from issue details page',
|
||||
projectName: 'Default'
|
||||
}
|
||||
await issuesPage.clickModelSelectorAll()
|
||||
await issuesPage.createNewIssue(newIssue)
|
||||
@ -160,7 +166,8 @@ test.describe('Tracker issue tests', () => {
|
||||
const secondProjectName = 'Second Project'
|
||||
const moveIssue: NewIssue = {
|
||||
title: `Issue to another project-${generateId()}`,
|
||||
description: 'Issue to move to another project'
|
||||
description: 'Issue to move to another project',
|
||||
projectName: 'Default'
|
||||
}
|
||||
await prepareNewIssueWithOpenStep(page, moveIssue)
|
||||
await issuesDetailsPage.moreActionOnIssue('Move to project')
|
||||
@ -181,7 +188,8 @@ test.describe('Tracker issue tests', () => {
|
||||
const commentText = `Comment should be stored after reload-${generateId()}`
|
||||
const commentIssue: NewIssue = {
|
||||
title: `Issue for stored comment-${generateId()}`,
|
||||
description: 'Issue for comment stored after reload the page'
|
||||
description: 'Issue for comment stored after reload the page',
|
||||
projectName: 'Default'
|
||||
}
|
||||
await prepareNewIssueWithOpenStep(page, commentIssue)
|
||||
|
||||
@ -203,7 +211,8 @@ test.describe('Tracker issue tests', () => {
|
||||
priority: 'Medium',
|
||||
estimation: '8',
|
||||
component: 'Default component',
|
||||
milestone: 'Edit Milestone'
|
||||
milestone: 'Edit Milestone',
|
||||
projectName: 'Default'
|
||||
}
|
||||
await issuesPage.clickModelSelectorAll()
|
||||
await issuesPage.buttonCreateNewIssue().click()
|
||||
@ -219,8 +228,9 @@ test.describe('Tracker issue tests', () => {
|
||||
|
||||
test('Delete an issue', async ({ page }) => {
|
||||
const deleteIssue: NewIssue = {
|
||||
title: `Issue-to-delete-${generateId()}`,
|
||||
description: 'Description Issue for deletion'
|
||||
title: `Issue-to-delete-${generateId(4)}`,
|
||||
description: 'Description Issue for deletion',
|
||||
projectName: 'Default'
|
||||
}
|
||||
await prepareNewIssueWithOpenStep(page, deleteIssue)
|
||||
await issuesPage.navigateToIssues()
|
||||
@ -238,7 +248,8 @@ test.describe('Tracker issue tests', () => {
|
||||
const additionalDescription = 'New row for the additional description'
|
||||
const changedDescriptionIssue: NewIssue = {
|
||||
title: `Check the changed description activity-${generateId()}`,
|
||||
description: 'Check the changed description activity description'
|
||||
description: 'Check the changed description activity description',
|
||||
projectName: 'Default'
|
||||
}
|
||||
await prepareNewIssueWithOpenStep(page, changedDescriptionIssue)
|
||||
await issuesDetailsPage.waitDetailsOpened(changedDescriptionIssue.title)
|
||||
@ -251,7 +262,8 @@ test.describe('Tracker issue tests', () => {
|
||||
test('Add comment with image attachment', async ({ page }) => {
|
||||
const commentImageIssue: NewIssue = {
|
||||
title: `Add comment with image attachment-${generateId()}`,
|
||||
description: 'Add comment with image attachment'
|
||||
description: 'Add comment with image attachment',
|
||||
projectName: 'Default'
|
||||
}
|
||||
await prepareNewIssueWithOpenStep(page, commentImageIssue)
|
||||
await issuesDetailsPage.waitDetailsOpened(commentImageIssue.title)
|
||||
@ -264,7 +276,8 @@ test.describe('Tracker issue tests', () => {
|
||||
const commentPopup = `Comment for the popup-${generateId()}`
|
||||
const commentIssue: NewIssue = {
|
||||
title: `Issue for add comment by popup-${generateId()}`,
|
||||
description: 'Issue for add comment by popup'
|
||||
description: 'Issue for add comment by popup',
|
||||
projectName: 'Default'
|
||||
}
|
||||
await prepareNewIssueWithOpenStep(page, commentIssue)
|
||||
await issuesDetailsPage.waitDetailsOpened(commentIssue.title)
|
||||
|
@ -9,7 +9,8 @@ test.describe('Tracker public link issues tests', () => {
|
||||
test('Public link generate', async ({ browser }) => {
|
||||
const publicLinkIssue: NewIssue = {
|
||||
title: `Public link generate issue-${generateId()}`,
|
||||
description: 'Public link generate issue'
|
||||
description: 'Public link generate issue',
|
||||
projectName: 'Default'
|
||||
}
|
||||
|
||||
let link: string
|
||||
@ -60,7 +61,8 @@ test.describe('Tracker public link issues tests', () => {
|
||||
test('Public link Revoke', async ({ browser }) => {
|
||||
const publicLinkIssue: NewIssue = {
|
||||
title: `Public link Revoke issue-${generateId()}`,
|
||||
description: 'Public link Revoke issue'
|
||||
description: 'Public link Revoke issue',
|
||||
projectName: 'Default'
|
||||
}
|
||||
|
||||
const newContext = await browser.newContext({ storageState: PlatformSetting })
|
||||
|
Loading…
Reference in New Issue
Block a user