mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-23 03:22:19 +03:00
Board: Update actions (#1859)
Signed-off-by: Anna No <anna.no@xored.com>
This commit is contained in:
parent
c11ece2a64
commit
8d051a1aab
@ -280,17 +280,34 @@ export function createModel (builder: Builder): void {
|
||||
)
|
||||
|
||||
// card actions
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.ShowPanel,
|
||||
actionProps: {
|
||||
component: board.component.EditCard
|
||||
},
|
||||
label: view.string.Open,
|
||||
icon: board.icon.Card,
|
||||
input: 'any',
|
||||
category: board.category.Card,
|
||||
target: board.class.Card,
|
||||
context: { mode: 'context', application: board.app.Board, group: 'top' }
|
||||
},
|
||||
board.action.Open
|
||||
)
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.ShowPopup,
|
||||
actionProps: {
|
||||
component: board.component.LabelsActionPopup,
|
||||
element: 'top'
|
||||
element: view.popup.PositionElementAlignment
|
||||
},
|
||||
label: board.string.Labels,
|
||||
icon: board.icon.Card,
|
||||
input: 'any',
|
||||
inline: true,
|
||||
category: board.category.Card,
|
||||
target: board.class.Card,
|
||||
context: { mode: 'context', application: board.app.Board, group: 'top' }
|
||||
@ -303,11 +320,12 @@ export function createModel (builder: Builder): void {
|
||||
action: view.actionImpl.ShowPopup,
|
||||
actionProps: {
|
||||
component: board.component.DatesActionPopup,
|
||||
element: 'top'
|
||||
element: view.popup.PositionElementAlignment
|
||||
},
|
||||
label: board.string.Dates,
|
||||
icon: board.icon.Card,
|
||||
input: 'any',
|
||||
inline: true,
|
||||
category: board.category.Card,
|
||||
target: board.class.Card,
|
||||
context: { mode: 'context', application: board.app.Board, group: 'top' }
|
||||
@ -320,12 +338,13 @@ export function createModel (builder: Builder): void {
|
||||
action: view.actionImpl.ShowPopup,
|
||||
actionProps: {
|
||||
component: board.component.CoverActionPopup,
|
||||
element: 'top',
|
||||
element: view.popup.PositionElementAlignment,
|
||||
value: 'object'
|
||||
},
|
||||
label: board.string.Cover,
|
||||
icon: board.icon.Card,
|
||||
input: 'any',
|
||||
inline: true,
|
||||
category: board.category.Card,
|
||||
target: board.class.Card,
|
||||
context: { mode: 'context', application: board.app.Board, group: 'top' }
|
||||
@ -339,12 +358,13 @@ export function createModel (builder: Builder): void {
|
||||
action: view.actionImpl.ShowPopup,
|
||||
actionProps: {
|
||||
component: board.component.MoveActionPopup,
|
||||
element: 'top'
|
||||
element: view.popup.PositionElementAlignment
|
||||
},
|
||||
input: 'any',
|
||||
inline: true,
|
||||
category: board.category.Card,
|
||||
target: board.class.Card,
|
||||
context: { mode: 'context', application: board.app.Board, group: 'tools' }
|
||||
context: { mode: ['context', 'editor'], application: board.app.Board, group: 'tools' }
|
||||
},
|
||||
board.action.Move
|
||||
)
|
||||
@ -354,14 +374,15 @@ export function createModel (builder: Builder): void {
|
||||
action: view.actionImpl.ShowPopup,
|
||||
actionProps: {
|
||||
component: board.component.CopyActionPopup,
|
||||
element: 'top'
|
||||
element: view.popup.PositionElementAlignment
|
||||
},
|
||||
label: board.string.Copy,
|
||||
icon: board.icon.Card,
|
||||
input: 'any',
|
||||
inline: true,
|
||||
category: board.category.Card,
|
||||
target: board.class.Card,
|
||||
context: { mode: 'context', application: board.app.Board, group: 'tools' }
|
||||
context: { mode: ['context', 'editor'], application: board.app.Board, group: 'tools' }
|
||||
},
|
||||
board.action.Copy
|
||||
)
|
||||
@ -381,11 +402,11 @@ export function createModel (builder: Builder): void {
|
||||
isArchived: { $nin: [true] }
|
||||
},
|
||||
label: board.string.Archive,
|
||||
icon: board.icon.Card,
|
||||
icon: view.icon.Archive,
|
||||
input: 'any',
|
||||
category: board.category.Card,
|
||||
target: board.class.Card,
|
||||
context: { mode: 'context', application: board.app.Board, group: 'tools' }
|
||||
context: { mode: ['context', 'editor'], application: board.app.Board, group: 'tools' }
|
||||
},
|
||||
board.action.Archive
|
||||
)
|
||||
@ -405,7 +426,7 @@ export function createModel (builder: Builder): void {
|
||||
input: 'any',
|
||||
category: board.category.Card,
|
||||
target: board.class.Card,
|
||||
context: { mode: 'context', application: board.app.Board, group: 'tools' }
|
||||
context: { mode: ['context', 'editor'], application: board.app.Board, group: 'tools' }
|
||||
},
|
||||
board.action.SendToBoard
|
||||
)
|
||||
@ -423,7 +444,7 @@ export function createModel (builder: Builder): void {
|
||||
category: board.category.Card,
|
||||
input: 'any',
|
||||
target: board.class.Card,
|
||||
context: { mode: 'context', application: board.app.Board, group: 'tools' }
|
||||
context: { mode: ['context', 'editor'], application: board.app.Board, group: 'tools' }
|
||||
},
|
||||
board.action.Delete
|
||||
)
|
||||
|
@ -532,7 +532,7 @@ export function createModel (builder: Builder): void {
|
||||
isArchived: { $nin: [true] }
|
||||
},
|
||||
label: task.string.Archive,
|
||||
icon: task.icon.TaskState,
|
||||
icon: view.icon.Archive,
|
||||
input: 'any',
|
||||
category: task.category.Task,
|
||||
target: task.class.State,
|
||||
|
@ -68,7 +68,9 @@
|
||||
evt.currentTarget.focus()
|
||||
}}
|
||||
on:click={(evt) => {
|
||||
dispatch('close')
|
||||
if (!action.inline) {
|
||||
dispatch('close')
|
||||
}
|
||||
action.action(ctx, evt)
|
||||
}}
|
||||
>
|
||||
|
@ -1,4 +1,12 @@
|
||||
import type { AnySvelteComponent, AnyComponent, PopupAlignment, PopupPositionElement, PopupOptions } from './types'
|
||||
import type {
|
||||
AnySvelteComponent,
|
||||
AnyComponent,
|
||||
HorizontalAlignment,
|
||||
PopupAlignment,
|
||||
PopupPositionElement,
|
||||
PopupOptions,
|
||||
VerticalAlignment
|
||||
} from './types'
|
||||
import { getResource } from '@anticrm/platform'
|
||||
import { writable } from 'svelte/store'
|
||||
|
||||
@ -262,3 +270,29 @@ export function fitPopupElement (
|
||||
export function eventToHTMLElement (evt: MouseEvent): HTMLElement {
|
||||
return evt.target as HTMLElement
|
||||
}
|
||||
|
||||
export function getEventPopupPositionElement (
|
||||
e?: Event,
|
||||
position?: { v: VerticalAlignment, h: HorizontalAlignment }
|
||||
): PopupAlignment | undefined {
|
||||
if (e == null || e.target == null) {
|
||||
return undefined
|
||||
}
|
||||
const target = e.target as HTMLElement
|
||||
return getPopupPositionElement(target, position)
|
||||
}
|
||||
|
||||
export function getPopupPositionElement (
|
||||
el: HTMLElement | undefined,
|
||||
position?: { v: VerticalAlignment, h: HorizontalAlignment }
|
||||
): PopupAlignment | undefined {
|
||||
if (el?.getBoundingClientRect != null) {
|
||||
const result = el.getBoundingClientRect()
|
||||
return {
|
||||
getBoundingClientRect: () => result,
|
||||
position
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ export interface Action {
|
||||
label: IntlString
|
||||
icon: Asset | AnySvelteComponent
|
||||
action: (props: any, ev: Event) => Promise<void>
|
||||
inline?: boolean
|
||||
}
|
||||
|
||||
export interface IPopupItem {
|
||||
@ -74,7 +75,22 @@ export interface PopupPositionElement {
|
||||
h: HorizontalAlignment
|
||||
}
|
||||
}
|
||||
|
||||
export type PopupPosAlignment = 'right' | 'top' | 'float' | 'account' | 'full' | 'content' | 'middle'
|
||||
|
||||
export function isPopupPosAlignment (x: any): x is PopupPosAlignment {
|
||||
return (
|
||||
typeof x === 'string' &&
|
||||
(x === 'right' ||
|
||||
x === 'top' ||
|
||||
x === 'float' ||
|
||||
x === 'account' ||
|
||||
x === 'full' ||
|
||||
x === 'content' ||
|
||||
x === 'middle')
|
||||
)
|
||||
}
|
||||
|
||||
export type PopupAlignment = PopupPosAlignment | PopupPositionElement | null
|
||||
|
||||
export type TooltipAlignment = 'top' | 'bottom' | 'left' | 'right'
|
||||
|
@ -22,13 +22,21 @@
|
||||
import type { State, TodoItem } from '@anticrm/task'
|
||||
import task from '@anticrm/task'
|
||||
import { StyledTextBox } from '@anticrm/text-editor'
|
||||
import { Button, CircleButton, EditBox, IconAdd, IconMoreH, Label, showPopup } from '@anticrm/ui'
|
||||
import {
|
||||
Button,
|
||||
CircleButton,
|
||||
EditBox,
|
||||
getEventPopupPositionElement,
|
||||
IconAdd,
|
||||
IconMoreH,
|
||||
Label,
|
||||
showPopup
|
||||
} from '@anticrm/ui'
|
||||
import { ContextMenu, DocAttributeBar, invokeAction, UpDownNavigator } from '@anticrm/view-resources'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import board from '../plugin'
|
||||
import { getCardActions } from '../utils/CardActionUtils'
|
||||
import { updateCard } from '../utils/CardUtils'
|
||||
import { getPopupAlignment } from '../utils/PopupUtils'
|
||||
import CardActions from './editor/CardActions.svelte'
|
||||
import CardChecklist from './editor/CardChecklist.svelte'
|
||||
import AddChecklist from './popups/AddChecklist.svelte'
|
||||
@ -57,7 +65,7 @@
|
||||
}
|
||||
|
||||
function addChecklist (e: Event) {
|
||||
showPopup(AddChecklist, { value: object }, getPopupAlignment(e))
|
||||
showPopup(AddChecklist, { value: object }, getEventPopupPositionElement(e))
|
||||
}
|
||||
|
||||
$: cardQuery.query(_class, { _id }, (result) => {
|
||||
@ -124,7 +132,11 @@
|
||||
kind="transparent"
|
||||
size="medium"
|
||||
on:click={(e) => {
|
||||
showPopup(ContextMenu, { object }, getPopupAlignment(e))
|
||||
showPopup(
|
||||
ContextMenu,
|
||||
{ object, baseMenuClass: board.class.Card, mode: 'editor' },
|
||||
getEventPopupPositionElement(e)
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
|
@ -22,13 +22,22 @@
|
||||
import notification from '@anticrm/notification'
|
||||
import view from '@anticrm/view'
|
||||
import { getClient, UserBoxList } from '@anticrm/presentation'
|
||||
import { Button, Component, EditBox, Icon, IconEdit, Label, numberToHexColor, showPopup } from '@anticrm/ui'
|
||||
import {
|
||||
Button,
|
||||
Component,
|
||||
EditBox,
|
||||
getPopupPositionElement,
|
||||
Icon,
|
||||
IconEdit,
|
||||
Label,
|
||||
numberToHexColor,
|
||||
showPopup
|
||||
} from '@anticrm/ui'
|
||||
import { ContextMenu } from '@anticrm/view-resources'
|
||||
import board from '../plugin'
|
||||
import CardLabels from './editor/CardLabels.svelte'
|
||||
import DatePresenter from './presenters/DatePresenter.svelte'
|
||||
import { hasDate, openCardPanel, updateCard, updateCardMembers } from '../utils/CardUtils'
|
||||
import { getElementPopupAlignment } from '../utils/PopupUtils'
|
||||
import CheckListsPresenter from './presenters/ChecklistsPresenter.svelte'
|
||||
import NotificationPresenter from './presenters/NotificationPresenter.svelte'
|
||||
|
||||
@ -47,7 +56,7 @@
|
||||
|
||||
function enterEditMode (): void {
|
||||
isEditMode = true
|
||||
showPopup(ContextMenu, { object }, getElementPopupAlignment(ref, { h: 'right', v: 'top' }), exitEditMode)
|
||||
showPopup(ContextMenu, { object }, getPopupPositionElement(ref, { h: 'right', v: 'top' }), exitEditMode)
|
||||
}
|
||||
|
||||
function showCard () {
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Button, Component, getPlatformColor, IconEdit, showPopup } from '@anticrm/ui'
|
||||
import { Button, Component, getPlatformColor, IconMoreV, showPopup } from '@anticrm/ui'
|
||||
import { State } from '@anticrm/task'
|
||||
import notification from '@anticrm/notification'
|
||||
import { ContextMenu } from '@anticrm/view-resources'
|
||||
@ -23,7 +23,7 @@
|
||||
<span class="lines-limit-2">{state.title}</span>
|
||||
<div class="flex">
|
||||
<Component is={notification.component.LastViewEditor} props={{ value: state }} />
|
||||
<Button icon={IconEdit} kind="transparent" on:click={showMenu} />
|
||||
<Button icon={IconMoreV} kind="transparent" on:click={showMenu} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -21,6 +21,7 @@
|
||||
Button,
|
||||
CheckBox,
|
||||
TextAreaEditor,
|
||||
getEventPopupPositionElement,
|
||||
IconAdd,
|
||||
IconDelete,
|
||||
IconMoreH,
|
||||
@ -32,7 +33,6 @@
|
||||
import contact, { Employee } from '@anticrm/contact'
|
||||
|
||||
import board from '../../plugin'
|
||||
import { getPopupAlignment } from '../../utils/PopupUtils'
|
||||
import { getDateIcon } from '../../utils/BoardUtils'
|
||||
|
||||
export let value: TodoItem
|
||||
@ -118,7 +118,7 @@
|
||||
}
|
||||
|
||||
function showItemMenu (item: TodoItem, e?: Event) {
|
||||
showPopup(ContextMenu, { object: item }, getPopupAlignment(e))
|
||||
showPopup(ContextMenu, { object: item }, getEventPopupPositionElement(e))
|
||||
}
|
||||
|
||||
$: checklistItemsQuery.query(task.class.TodoItem, { space: value.space, attachedTo: value._id }, (result) => {
|
||||
|
@ -1,28 +0,0 @@
|
||||
import { PopupAlignment } from '@anticrm/ui'
|
||||
import { HorizontalAlignment, VerticalAlignment } from '@anticrm/ui/src/types'
|
||||
|
||||
export function getPopupAlignment (
|
||||
e?: Event,
|
||||
position?: { v: VerticalAlignment, h: HorizontalAlignment }
|
||||
): PopupAlignment | undefined {
|
||||
if (e == null || e.target == null) {
|
||||
return undefined
|
||||
}
|
||||
const target = e.target as HTMLElement
|
||||
return getElementPopupAlignment(target, position)
|
||||
}
|
||||
|
||||
export function getElementPopupAlignment (
|
||||
el: HTMLElement | undefined,
|
||||
position?: { v: VerticalAlignment, h: HorizontalAlignment }
|
||||
): PopupAlignment | undefined {
|
||||
if (el?.getBoundingClientRect != null) {
|
||||
const result = el.getBoundingClientRect()
|
||||
return {
|
||||
getBoundingClientRect: () => result,
|
||||
position
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
@ -120,6 +120,7 @@ const boards = plugin(boardId, {
|
||||
Completed: '' as Ref<DoneState>
|
||||
},
|
||||
action: {
|
||||
Open: '' as Ref<Action>,
|
||||
Cover: '' as Ref<Action>,
|
||||
Dates: '' as Ref<Action>,
|
||||
Labels: '' as Ref<Action>,
|
||||
|
@ -1,6 +1,15 @@
|
||||
import { Doc, Hierarchy } from '@anticrm/core'
|
||||
import { getResource, Resource } from '@anticrm/platform'
|
||||
import { getClient, MessageBox } from '@anticrm/presentation'
|
||||
import { AnyComponent, closeTooltip, PopupPositionElement, showPanel, showPopup } from '@anticrm/ui'
|
||||
import {
|
||||
AnyComponent,
|
||||
closeTooltip,
|
||||
isPopupPosAlignment,
|
||||
PopupAlignment,
|
||||
PopupPosAlignment,
|
||||
showPanel,
|
||||
showPopup
|
||||
} from '@anticrm/ui'
|
||||
import { ViewContext } from '@anticrm/view'
|
||||
import MoveView from './components/Move.svelte'
|
||||
import { contextStore } from './context'
|
||||
@ -119,7 +128,7 @@ function ShowPanel (
|
||||
evt: Event,
|
||||
props: {
|
||||
component?: AnyComponent
|
||||
element: PopupPositionElement
|
||||
element: PopupPosAlignment
|
||||
rightSection?: AnyComponent
|
||||
}
|
||||
): void {
|
||||
@ -146,12 +155,12 @@ function ShowPanel (
|
||||
* - values - all docs will be placed into
|
||||
* - props - some basic props, will be merged with key, _class, value, values
|
||||
*/
|
||||
function ShowPopup (
|
||||
async function ShowPopup (
|
||||
doc: Doc | Doc[],
|
||||
evt: Event,
|
||||
props: {
|
||||
component: AnyComponent
|
||||
element: PopupPositionElement
|
||||
element?: PopupPosAlignment | Resource<(e?: Event) => PopupAlignment | undefined>
|
||||
_id?: string
|
||||
_class?: string
|
||||
_space?: string
|
||||
@ -159,8 +168,9 @@ function ShowPopup (
|
||||
values?: string
|
||||
props?: Record<string, any>
|
||||
}
|
||||
): void {
|
||||
): Promise<void> {
|
||||
const docs = Array.isArray(doc) ? doc : doc !== undefined ? [doc] : []
|
||||
const element = await getPopupAlignment(props.element, evt)
|
||||
evt.preventDefault()
|
||||
let cprops = {
|
||||
...(props?.props ?? {})
|
||||
@ -178,7 +188,7 @@ function ShowPopup (
|
||||
}
|
||||
}
|
||||
|
||||
showPopup(props.component, cprops, props.element)
|
||||
showPopup(props.component, cprops, element)
|
||||
}
|
||||
|
||||
function UpdateDocument (doc: Doc | Doc[], evt: Event, props: Record<string, any>): void {
|
||||
@ -212,6 +222,24 @@ function UpdateDocument (doc: Doc | Doc[], evt: Event, props: Record<string, any
|
||||
}
|
||||
}
|
||||
|
||||
async function getPopupAlignment (
|
||||
element?: PopupPosAlignment | Resource<(e?: Event) => PopupAlignment | undefined>,
|
||||
evt?: Event
|
||||
): Promise<PopupAlignment | undefined> {
|
||||
if (element === undefined) {
|
||||
return undefined
|
||||
}
|
||||
if (isPopupPosAlignment(element)) {
|
||||
return element
|
||||
}
|
||||
try {
|
||||
const alignmentGetter: (e?: Event) => PopupAlignment | undefined = await getResource(element)
|
||||
return alignmentGetter(evt)
|
||||
} catch (e) {
|
||||
return element as PopupAlignment
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
@ -17,20 +17,23 @@
|
||||
import type { Asset } from '@anticrm/platform'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { Action, Menu } from '@anticrm/ui'
|
||||
import type { ViewContextType } from '@anticrm/view'
|
||||
import { getActions, invokeAction } from '../actions'
|
||||
|
||||
export let object: Doc | Doc[]
|
||||
export let baseMenuClass: Ref<Class<Doc>> | undefined = undefined
|
||||
export let actions: Action[] = []
|
||||
export let mode: ViewContextType | undefined = undefined
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let loaded = 0
|
||||
|
||||
getActions(client, object, baseMenuClass).then((result) => {
|
||||
getActions(client, object, baseMenuClass, mode).then((result) => {
|
||||
actions = result.map((a) => ({
|
||||
label: a.label,
|
||||
icon: a.icon as Asset,
|
||||
inline: a.inline,
|
||||
action: async (_: any, evt: Event) => {
|
||||
invokeAction(object, evt, a.action, a.actionProps)
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
//
|
||||
|
||||
import { Resources } from '@anticrm/platform'
|
||||
import { getEventPopupPositionElement, PopupAlignment } from '@anticrm/ui'
|
||||
import { actionImpl } from './actionImpl'
|
||||
import BooleanEditor from './components/BooleanEditor.svelte'
|
||||
import BooleanPresenter from './components/BooleanPresenter.svelte'
|
||||
@ -51,6 +52,10 @@ import ObjectFilter from './components/filter/ObjectFilter.svelte'
|
||||
import TimestampFilter from './components/filter/TimestampFilter.svelte'
|
||||
import ClassPresenter from './components/ClassPresenter.svelte'
|
||||
|
||||
function PositionElementAlignment (e?: Event): PopupAlignment | undefined {
|
||||
return getEventPopupPositionElement(e)
|
||||
}
|
||||
|
||||
export { getActions, invokeAction } from './actions'
|
||||
export { default as ActionContext } from './components/ActionContext.svelte'
|
||||
export { default as ActionHandler } from './components/ActionHandler.svelte'
|
||||
@ -107,5 +112,8 @@ export default async (): Promise<Resources> => ({
|
||||
GithubPresenter,
|
||||
YoutubePresenter,
|
||||
ActionsPopup
|
||||
},
|
||||
popup: {
|
||||
PositionElementAlignment
|
||||
}
|
||||
})
|
||||
|
@ -33,7 +33,7 @@ import type {
|
||||
import type { Asset, IntlString, Plugin, Resource, Status } from '@anticrm/platform'
|
||||
import { plugin } from '@anticrm/platform'
|
||||
import type { AnyComponent, AnySvelteComponent } from '@anticrm/ui'
|
||||
import { PopupPosAlignment } from '@anticrm/ui/src/types'
|
||||
import { PopupAlignment, PopupPosAlignment } from '@anticrm/ui/src/types'
|
||||
import type { Preference } from '@anticrm/preference'
|
||||
|
||||
/**
|
||||
@ -212,6 +212,7 @@ export interface Action<T extends Doc = Doc, P = Record<string, any>> extends Do
|
||||
// any - one or multiple objects are required
|
||||
// any - any input is suitable.
|
||||
input: ViewActionInput
|
||||
inline?: boolean
|
||||
|
||||
// Focus and/or all selection document should match target class.
|
||||
target: Ref<Class<Doc>>
|
||||
@ -415,6 +416,9 @@ const view = plugin(viewId, {
|
||||
Editor: '' as Ref<ActionCategory>,
|
||||
MarkdownFormatting: '' as Ref<ActionCategory>
|
||||
},
|
||||
popup: {
|
||||
PositionElementAlignment: '' as Resource<(e?: Event) => PopupAlignment | undefined>
|
||||
},
|
||||
actionImpl: {
|
||||
UpdateDocument: '' as ViewAction<{
|
||||
key: string
|
||||
@ -430,7 +434,7 @@ const view = plugin(viewId, {
|
||||
}>,
|
||||
ShowPopup: '' as ViewAction<{
|
||||
component: AnyComponent
|
||||
element?: PopupPosAlignment
|
||||
element?: PopupPosAlignment | Resource<(e?: Event) => PopupAlignment | undefined>
|
||||
_id?: string
|
||||
_class?: string
|
||||
_space?: string
|
||||
|
Loading…
Reference in New Issue
Block a user