Prepare for something (#5013)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2024-03-19 15:48:03 +06:00 committed by GitHub
parent ece856465d
commit 87a4896bef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 169 additions and 122 deletions

View File

@ -166,8 +166,6 @@ export async function configurePlatform() {
setMetadata(textEditor.metadata.CollaboratorUrl, config.COLLABORATOR_URL ?? 'ws://locahost:3078')
setMetadata(uiPlugin.metadata.DefaultApplication, login.component.LoginApp)
setMetadata(uiPlugin.metadata.SearchPopup, view.component.ActionsPopup)
setMetadata(contactPlugin.metadata.LastNameFirst, config.LAST_NAME_FIRST === 'true' ?? false)
const languages = config.LANGUAGES ? (config.LANGUAGES as string).split(',').map((l) => l.trim()) : ['en', 'ru', 'es', 'pt']

View File

@ -13,12 +13,13 @@
// limitations under the License.
//
import { DOMAIN_TRANSIENT, type UserStatus } from '@hcengineering/core'
import { DOMAIN_TRANSIENT, type Account, type Ref, type UserStatus } from '@hcengineering/core'
import { Model } from '@hcengineering/model'
import core from './component'
import { TDoc } from './core'
@Model(core.class.UserStatus, core.class.Doc, DOMAIN_TRANSIENT)
export class TUserStatus extends TDoc implements UserStatus {
user!: Ref<Account>
online!: boolean
}

View File

@ -388,6 +388,7 @@ export const roleOrder: Record<AccountRole, number> = {
*/
export interface UserStatus extends Doc {
online: boolean
user: Ref<Account>
}
/**

View File

@ -1780,7 +1780,11 @@
flex-wrap: nowrap;
min-width: 0;
.ap-icon { color: var(--theme-dark-color); }
.ap-icon {
color: var(--theme-dark-color);
height: 100%;
aspect-ratio: 1;
}
.ap-label {
min-width: 0;
text-align: left;

View File

@ -1,21 +1,21 @@
<script lang="ts">
import platform, { addEventListener, getMetadata, OK, PlatformEvent, Status, Severity } from '@hcengineering/platform'
import platform, { OK, PlatformEvent, Severity, Status, addEventListener, getMetadata } from '@hcengineering/platform'
import { onDestroy } from 'svelte'
import type { AnyComponent, WidthType } from '../../types'
import { deviceSizes, deviceWidths } from '../../types'
// import { applicationShortcutKey } from '../../utils'
import { getCurrentLocation, location, navigate, locationStorageKeyId } from '../../location'
import { Theme } from '@hcengineering/theme'
import { IconArrowLeft, IconArrowRight, checkMobile, deviceOptionsStore as deviceInfo } from '../../'
import { embeddedPlatform, getCurrentLocation, location, locationStorageKeyId, navigate } from '../../location'
import uiPlugin from '../../plugin'
import Component from '../Component.svelte'
import Label from '../Label.svelte'
import StatusComponent from '../Status.svelte'
import Clock from './Clock.svelte'
import { IconArrowLeft, IconArrowRight, checkMobile, deviceOptionsStore as deviceInfo } from '../../'
import uiPlugin from '../../plugin'
import Label from '../Label.svelte'
import FontSizeSelector from './FontSizeSelector.svelte'
import LangSelector from './LangSelector.svelte'
import RootBarExtension from './RootBarExtension.svelte'
import ThemeSelector from './ThemeSelector.svelte'
import SearchSelector from './SearchSelector.svelte'
let application: AnyComponent | undefined
@ -136,8 +136,8 @@
<Theme>
<div id="ui-root">
<div class="antiStatusBar">
<div class="flex-row-center h-full content-color">
<div class="history-box flex-row-center gap-3">
<div class="flex-row-center h-full content-color gap-3">
<div class="history-box flex-row-center gap-3" class:embedded={embeddedPlatform}>
<button
class="antiButton ghost jf-center bs-none no-focus resetIconSize statusButton square"
style:color={'var(--theme-dark-color)'}
@ -157,6 +157,7 @@
<IconArrowRight size={'small'} />
</button>
</div>
<RootBarExtension position="left" />
<div
class="flex-row-center justify-center status-info"
style:margin-left={(isPortrait && docWidth <= 480) || (!isPortrait && docHeight <= 480) ? '1.5rem' : '0'}
@ -174,7 +175,7 @@
<Clock />
</div>
<div class="flex-row-center gap-statusbar">
<SearchSelector />
<RootBarExtension position="right" />
<FontSizeSelector />
<ThemeSelector />
<LangSelector />
@ -216,7 +217,11 @@
.history-box {
-webkit-app-region: no-drag;
margin-left: 5.625rem;
margin-left: 1rem;
&.embedded {
margin-left: 5.625rem;
}
}
.maintenanceScheduled {
padding: 0 0.5rem;

View File

@ -0,0 +1,20 @@
<script lang="ts">
import { location as locationStore } from '../../location'
import { rootBarExtensions } from '../../utils'
import Component from '../Component.svelte'
export let position: 'left' | 'right'
let oldLoc: string | undefined = undefined
locationStore.subscribe((newLocation) => {
if (oldLoc !== undefined && oldLoc !== newLocation.path[0]) {
rootBarExtensions.set([])
}
oldLoc = newLocation.path[0]
})
</script>
{#each $rootBarExtensions as ext}
{#if ext[0] === position}
<Component is={ext[1]} />
{/if}
{/each}

View File

@ -1,25 +0,0 @@
<script lang="ts">
import Search from './icons/Search.svelte'
import { showPopup } from '../../popups'
import { getMetadata } from '@hcengineering/platform'
import uiPlugin from '../../plugin'
import { location as locationStore } from '../../location'
const popup = getMetadata(uiPlugin.metadata.SearchPopup)
let isLoggedIn = false
locationStore.subscribe((newLocation) => {
isLoggedIn = newLocation.path[0] === 'workbench'
})
function openPopup () {
if (popup !== undefined) showPopup(popup, {}, 'top')
}
</script>
{#if isLoggedIn}
<button
class="antiButton ghost jf-center bs-none no-focus resetIconSize statusButton square"
on:click={openPopup}
style:color={'var(--theme-dark-color)'}
>
<Search size="32" />
</button>
{/if}

View File

@ -268,3 +268,5 @@ export class DelayedCaller {
}
export const testing = (localStorage.getItem('#platform.testing.enabled') ?? 'false') === 'true'
export const rootBarExtensions = writable<Array<['left' | 'right', AnyComponent]>>([])

View File

@ -128,14 +128,7 @@
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
{id}
bind:this={container}
class="min-w-0"
class:w-full={width === '100%'}
class:h-full={$$slots.content}
style:flex-shrink={shrink}
>
<div {id} bind:this={container} class="min-w-0 h-full" class:w-full={width === '100%'} style:flex-shrink={shrink}>
{#if $$slots.content}
<div
class="w-full h-full flex-streatch"

View File

@ -34,12 +34,12 @@
import { getBlobURL, getClient } from '@hcengineering/presentation'
import {
AnySvelteComponent,
ColorDefinition,
Icon,
IconSize,
getPlatformAvatarColorForTextDef,
getPlatformAvatarColorByName,
themeStore,
ColorDefinition
getPlatformAvatarColorForTextDef,
themeStore
} from '@hcengineering/ui'
import { getAvatarProviderId } from '../utils'
import AvatarIcon from './icons/Avatar.svelte'
@ -293,6 +293,20 @@
}
}
.ava-full {
width: 100%;
height: 100%;
.ava-text {
font-weight: 500;
font-size: 2rem;
}
&.roundedRect {
border-radius: 0px;
}
}
.ava-blur {
position: absolute;
filter: blur(32px);
@ -319,6 +333,10 @@
&.ava-2x-large {
border-radius: 1rem;
}
&.ava-full {
border-radius: 0px;
}
}
}

View File

@ -15,6 +15,7 @@
<script lang="ts">
import { Employee, getName, Person } from '@hcengineering/contact'
import { IntlString } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import {
getPlatformAvatarColorDef,
getPlatformAvatarColorForTextDef,
@ -26,7 +27,6 @@
} from '@hcengineering/ui'
import { createEventDispatcher, onMount } from 'svelte'
import Avatar from './Avatar.svelte'
import { getClient } from '@hcengineering/presentation'
import PersonElement from './PersonElement.svelte'
export let value: Person | Employee | undefined | null
@ -115,7 +115,7 @@
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<span
class="antiPresenter"
class="antiPresenter h-full"
class:text-base={enlargedText}
use:tooltip={disabled ? undefined : showTooltip}
on:click={onEditClick}

View File

@ -41,7 +41,7 @@
<DocNavLink object={value} onClick={onEdit} {disabled} {noUnderline} {colorInherit} {accent} noOverflow>
<span
use:tooltip={disabled ? undefined : showTooltip}
class="antiPresenter"
class="antiPresenter h-full"
class:text-base={enlargedText}
style:max-width={maxWidth}
>

View File

@ -0,0 +1,17 @@
<script lang="ts">
import { IconSearch } from '@hcengineering/ui'
import { showPopup } from '@hcengineering/ui/src/popups'
import ActionsPopup from './ActionsPopup.svelte'
function openPopup () {
showPopup(ActionsPopup, {}, 'top')
}
</script>
<button
class="antiButton ghost jf-center bs-none no-focus resetIconSize statusButton square"
on:click={openPopup}
style:color={'var(--theme-dark-color)'}
>
<IconSearch size={'small'} />
</button>

View File

@ -19,12 +19,15 @@ import { getEventPopupPositionElement, type PopupAlignment } from '@hcengineerin
import { actionImpl } from './actionImpl'
import ActionsPopup from './components/ActionsPopup.svelte'
import ArrayEditor from './components/ArrayEditor.svelte'
import AttachedDocPanel from './components/AttachedDocPanel.svelte'
import BooleanEditor from './components/BooleanEditor.svelte'
import BooleanPresenter from './components/BooleanPresenter.svelte'
import BooleanTruePresenter from './components/BooleanTruePresenter.svelte'
import ClassAttributeBar from './components/ClassAttributeBar.svelte'
import ClassPresenter from './components/ClassPresenter.svelte'
import ClassRefPresenter from './components/ClassRefPresenter.svelte'
import CollaborativeDocEditor from './components/CollaborativeDocEditor.svelte'
import CollaborativeHTMLEditor from './components/CollaborativeHTMLEditor.svelte'
import ColorsPopup from './components/ColorsPopup.svelte'
import DateEditor from './components/DateEditor.svelte'
import DatePresenter from './components/DatePresenter.svelte'
@ -35,7 +38,9 @@ import EditDoc from './components/EditDoc.svelte'
import EnumArrayEditor from './components/EnumArrayEditor.svelte'
import EnumEditor from './components/EnumEditor.svelte'
import EnumPresenter from './components/EnumPresenter.svelte'
import ArrayFilter from './components/filter/ArrayFilter.svelte'
import DateFilter from './components/filter/DateFilter.svelte'
import DateFilterPresenter from './components/filter/DateFilterPresenter.svelte'
import FilterBar from './components/filter/FilterBar.svelte'
import FilterTypePopup from './components/filter/FilterTypePopup.svelte'
import ObjectFilter from './components/filter/ObjectFilter.svelte'
@ -43,13 +48,11 @@ import StringFilter from './components/filter/StringFilter.svelte'
import StringFilterPresenter from './components/filter/StringFilterPresenter.svelte'
import TimestampFilter from './components/filter/TimestampFilter.svelte'
import ValueFilter from './components/filter/ValueFilter.svelte'
import CollaborativeDocEditor from './components/CollaborativeDocEditor.svelte'
import CollaborativeHTMLEditor from './components/CollaborativeHTMLEditor.svelte'
import HTMLEditor from './components/HTMLEditor.svelte'
import HTMLPresenter from './components/HTMLPresenter.svelte'
import HyperlinkPresenter from './components/HyperlinkPresenter.svelte'
import HyperlinkEditor from './components/HyperlinkEditor.svelte'
import HyperlinkEditorPopup from './components/HyperlinkEditorPopup.svelte'
import HyperlinkPresenter from './components/HyperlinkPresenter.svelte'
import IconPicker from './components/IconPicker.svelte'
import IntlStringPresenter from './components/IntlStringPresenter.svelte'
import GithubPresenter from './components/linkPresenters/GithubPresenter.svelte'
@ -64,13 +67,16 @@ import MarkupEditor from './components/MarkupEditor.svelte'
import MarkupEditorPopup from './components/MarkupEditorPopup.svelte'
import MarkupPresenter from './components/MarkupPresenter.svelte'
import Menu from './components/Menu.svelte'
import TreeElement from './components/navigator/TreeElement.svelte'
import TreeItem from './components/navigator/TreeItem.svelte'
import TreeNode from './components/navigator/TreeNode.svelte'
import TreeElement from './components/navigator/TreeElement.svelte'
import NumberEditor from './components/NumberEditor.svelte'
import NumberPresenter from './components/NumberPresenter.svelte'
import ObjectMention from './components/ObjectMention.svelte'
import ObjectPresenter from './components/ObjectPresenter.svelte'
import RolePresenter from './components/RolePresenter.svelte'
import SearchSelector from './components/SearchSelector.svelte'
import SpaceHeader from './components/SpaceHeader.svelte'
import SpacePresenter from './components/SpacePresenter.svelte'
import SpaceRefPresenter from './components/SpaceRefPresenter.svelte'
import StatusPresenter from './components/status/StatusPresenter.svelte'
@ -82,13 +88,8 @@ import TableBrowser from './components/TableBrowser.svelte'
import TimestampPresenter from './components/TimestampPresenter.svelte'
import UpDownNavigator from './components/UpDownNavigator.svelte'
import ValueSelector from './components/ValueSelector.svelte'
import ViewletSettingButton from './components/ViewletSettingButton.svelte'
import DateFilterPresenter from './components/filter/DateFilterPresenter.svelte'
import ArrayFilter from './components/filter/ArrayFilter.svelte'
import SpaceHeader from './components/SpaceHeader.svelte'
import ViewletContentView from './components/ViewletContentView.svelte'
import AttachedDocPanel from './components/AttachedDocPanel.svelte'
import ObjectMention from './components/ObjectMention.svelte'
import ViewletSettingButton from './components/ViewletSettingButton.svelte'
import {
afterResult,
@ -114,12 +115,14 @@ import {
} from './filter'
import { IndexedDocumentPreview } from '@hcengineering/presentation'
import { showEmptyGroups } from './viewOptions'
import { AggregationMiddleware, AnalyticsMiddleware } from './middleware'
export { getActions, invokeAction, getContextActions, showMenu } from './actions'
import { showEmptyGroups } from './viewOptions'
export { getActions, getContextActions, invokeAction, showMenu } from './actions'
export { default as ActionButton } from './components/ActionButton.svelte'
export { default as ActionHandler } from './components/ActionHandler.svelte'
export { default as BaseDocPresenter } from './components/BaseDocPresenter.svelte'
export { default as FilterButton } from './components/filter/FilterButton.svelte'
export { default as FilterRemovedNotification } from './components/filter/FilterRemovedNotification.svelte'
export { default as FixedColumn } from './components/FixedColumn.svelte'
export { default as SourcePresenter } from './components/inference/SourcePresenter.svelte'
export { default as LinkPresenter } from './components/LinkPresenter.svelte'
@ -132,21 +135,19 @@ export { default as NavLink } from './components/navigator/NavLink.svelte'
export { default as ObjectBox } from './components/ObjectBox.svelte'
export { default as ObjectPresenter } from './components/ObjectPresenter.svelte'
export { default as ObjectSearchBox } from './components/ObjectSearchBox.svelte'
export { default as ParentsNavigator } from './components/ParentsNavigator.svelte'
export { default as StatusPresenter } from './components/status/StatusPresenter.svelte'
export { default as StatusRefPresenter } from './components/status/StatusRefPresenter.svelte'
export { default as TableBrowser } from './components/TableBrowser.svelte'
export { default as ValueSelector } from './components/ValueSelector.svelte'
export { default as FilterRemovedNotification } from './components/filter/FilterRemovedNotification.svelte'
export { default as ParentsNavigator } from './components/ParentsNavigator.svelte'
export { default as ViewletSelector } from './components/ViewletSelector.svelte'
export { default as ViewletsSettingButton } from './components/ViewletsSettingButton.svelte'
export { default as BaseDocPresenter } from './components/BaseDocPresenter.svelte'
export * from './filter'
export * from './selection'
export * from './utils'
export * from './status'
export * from './middleware'
export * from './selection'
export * from './status'
export * from './utils'
export {
buildModel,
getActiveViewletId,
@ -158,45 +159,45 @@ export {
getObjectPreview,
groupBy,
isCollectionAttr,
type LoadingProps,
setActiveViewletId
setActiveViewletId,
type LoadingProps
} from './utils'
export * from './viewOptions'
export {
HTMLPresenter,
Table,
DateEditor,
DocAttributeBar,
EditDoc,
ColorsPopup,
Menu,
SpacePresenter,
UpDownNavigator,
ViewletSettingButton,
FilterBar,
ClassAttributeBar,
ClassPresenter,
BooleanEditor,
BooleanPresenter,
NumberEditor,
NumberPresenter,
TimestampPresenter,
SortableList,
SortableListItem,
MarkupEditor,
TreeNode,
TreeItem,
TreeElement,
StringEditor,
ClassAttributeBar,
ClassPresenter,
ColorsPopup,
DateEditor,
DocAttributeBar,
DocNavLink,
EnumEditor,
StringPresenter,
EditBoxPopup,
SpaceHeader,
ViewletContentView,
EditDoc,
EnumEditor,
FilterBar,
HTMLPresenter,
HyperlinkEditor,
IconPicker,
ObjectMention
MarkupEditor,
Menu,
NumberEditor,
NumberPresenter,
ObjectMention,
SortableList,
SortableListItem,
SpaceHeader,
SpacePresenter,
StringEditor,
StringPresenter,
Table,
TimestampPresenter,
TreeElement,
TreeItem,
TreeNode,
UpDownNavigator,
ViewletContentView,
ViewletSettingButton
}
function PositionElementAlignment (e?: Event): PopupAlignment | undefined {
@ -261,7 +262,8 @@ export default async (): Promise<Resources> => ({
DateFilterPresenter,
StringFilterPresenter,
AttachedDocPanel,
ObjectMention
ObjectMention,
SearchSelector
},
popup: {
PositionElementAlignment

View File

@ -32,8 +32,8 @@ import {
ClassSortFuncs,
CollectionEditor,
CollectionPresenter,
FilteredView,
FilterMode,
FilteredView,
Groupping,
IgnoreActions,
InlineAttributEditor,
@ -156,7 +156,8 @@ const view = plugin(viewId, {
DividerPresenter: '' as AnyComponent,
IconWithEmoji: '' as AnyComponent,
AttachedDocPanel: '' as AnyComponent,
ObjectMention: '' as AnyComponent
ObjectMention: '' as AnyComponent,
SearchSelector: '' as AnyComponent
},
ids: {
IconWithEmoji: '' as Asset

View File

@ -20,7 +20,7 @@
import notification, { DocNotifyContext, InboxNotification, notificationId } from '@hcengineering/notification'
import { BrowserNotificatator, InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
import { IntlString, broadcastEvent, getMetadata, getResource } from '@hcengineering/platform'
import { ActionContext, createQuery, getClient, isAdminUser } from '@hcengineering/presentation'
import { ActionContext, ComponentExtensions, createQuery, getClient, isAdminUser } from '@hcengineering/presentation'
import setting from '@hcengineering/setting'
import support, { SupportStatus, supportLink } from '@hcengineering/support'
import {
@ -53,6 +53,7 @@
openPanel,
popupstore,
resolvedLocationStore,
rootBarExtensions,
setResolvedLocation,
showPopup,
workbenchSeparators
@ -126,6 +127,12 @@
}
onMount(() => {
rootBarExtensions.update((cur) => {
if (!cur.find((p) => p[1] === view.component.SearchSelector)) {
cur.push(['right', view.component.SearchSelector])
}
return cur
})
void getResource(login.function.GetWorkspaces).then(async (getWorkspaceFn) => {
$workspacesStore = await getWorkspaceFn()
await updateWindowTitle(getLocation())
@ -562,14 +569,6 @@
)
}
function getApps (apps: Application[] | Promise<Application[]>): Application[] {
if (apps instanceof Promise) {
return []
} else {
return apps
}
}
function checkInbox (popups: CompAndProps[]) {
if (inboxPopup !== undefined) {
const exists = popups.find((p) => p.id === inboxPopup?.id)
@ -706,14 +705,14 @@
notify={hasInboxNotifications}
/>
</NavLink>
<Applications apps={getApps(apps)} active={currentApplication?._id} direction={appsDirection} />
<Applications {apps} active={currentApplication?._id} direction={appsDirection} />
</div>
<div class="info-box {appsDirection}" class:vertical-mobile={appsDirection === 'vertical'} class:mini={appsMini}>
<AppItem
icon={IconSettings}
label={setting.string.Settings}
size={appsMini ? 'small' : 'large'}
on:click={() => showPopup(AppSwitcher, { apps: getApps(apps) }, popupPosition)}
on:click={() => showPopup(AppSwitcher, { apps }, popupPosition)}
/>
<a href={supportLink}>
<AppItem
@ -876,6 +875,7 @@
<ActionContext context={{ mode: 'popup' }} />
</svelte:fragment>
</Popup>
<ComponentExtensions extension={workbench.extensions.WorkbenchExtensions} />
<BrowserNotificatator />
{/if}

View File

@ -14,12 +14,12 @@
//
import type { Class, Doc, Mixin, Obj, Ref, Space } from '@hcengineering/core'
import { DocNotifyContext, InboxNotification } from '@hcengineering/notification'
import type { Asset, IntlString, Metadata, Plugin, Resource } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform'
import type { Preference } from '@hcengineering/preference'
import { AnyComponent, Location, ResolvedLocation } from '@hcengineering/ui'
import { AnyComponent, ComponentExtensionId, Location, ResolvedLocation } from '@hcengineering/ui'
import { ViewAction } from '@hcengineering/view'
import { DocNotifyContext, InboxNotification } from '@hcengineering/notification'
/**
* @public
@ -174,6 +174,9 @@ export default plugin(workbenchId, {
// Default for navigation expanded state
NavigationExpandedDefault: '' as Metadata<boolean>
},
extensions: {
WorkbenchExtensions: '' as ComponentExtensionId
},
actionImpl: {
Navigate: '' as ViewAction<{
mode: 'app' | 'special' | 'space'

View File

@ -22,6 +22,7 @@ import core, {
Collection,
DOMAIN_DOC_INDEX_STATE,
DOMAIN_MODEL,
DOMAIN_TRANSIENT,
DOMAIN_TX,
Doc,
DocumentQuery,
@ -745,7 +746,13 @@ class TServerStorage implements ServerStorage {
for (const tx of txes) {
if (!this.hierarchy.isDerived(tx._class, core.class.TxApplyIf)) {
if (tx.space !== core.space.DerivedTx) {
txToStore.push(tx)
if (this.hierarchy.isDerived(tx._class, core.class.TxCUD)) {
if (this.hierarchy.findDomain((tx as TxCUD<Doc>).objectClass) !== DOMAIN_TRANSIENT) {
txToStore.push(tx)
}
} else {
txToStore.push(tx)
}
}
if (tx.objectSpace === core.space.Model) {
modelTx.push(tx)

View File

@ -378,8 +378,7 @@ class TSessionManager implements SessionManager {
private async setStatus (ctx: MeasureContext, session: Session, online: boolean): Promise<void> {
try {
const user = (
await session.findAll(
ctx,
await session.pipeline().modelDb.findAll(
core.class.Account,
{
email: session.getUser()
@ -388,11 +387,12 @@ class TSessionManager implements SessionManager {
)
)[0]
if (user === undefined) return
const status = (await session.findAll(ctx, core.class.UserStatus, { modifiedBy: user._id }, { limit: 1 }))[0]
const status = (await session.findAll(ctx, core.class.UserStatus, { user: user._id }, { limit: 1 }))[0]
const txFactory = new TxFactory(user._id, true)
if (status === undefined) {
const tx = txFactory.createTxCreateDoc(core.class.UserStatus, user._id as string as Ref<Space>, {
online
online,
user: user._id
})
await session.tx(ctx, tx)
} else if (status.online !== online) {