mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-02 05:09:26 +00:00
Child cards (#8154)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
380c33e4f4
commit
e8a91c2c03
@ -12,13 +12,22 @@
|
||||
// limitations under the License.
|
||||
|
||||
import activity from '@hcengineering/activity'
|
||||
import { CardEvents, cardId, DOMAIN_CARD, type Card, type MasterTag, type Tag } from '@hcengineering/card'
|
||||
import {
|
||||
CardEvents,
|
||||
cardId,
|
||||
DOMAIN_CARD,
|
||||
type ParentInfo,
|
||||
type Card,
|
||||
type MasterTag,
|
||||
type Tag
|
||||
} from '@hcengineering/card'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import contact from '@hcengineering/contact'
|
||||
import core, {
|
||||
AccountRole,
|
||||
DOMAIN_MODEL,
|
||||
IndexKind,
|
||||
SortingOrder,
|
||||
type CollectionSize,
|
||||
type MarkupBlobRef,
|
||||
type Rank,
|
||||
@ -32,6 +41,7 @@ import {
|
||||
TypeCollaborativeDoc,
|
||||
TypeRef,
|
||||
TypeString,
|
||||
UX,
|
||||
type Builder
|
||||
} from '@hcengineering/model'
|
||||
import attachment from '@hcengineering/model-attachment'
|
||||
@ -54,6 +64,7 @@ export class TMasterTag extends TClass implements MasterTag {}
|
||||
export class TTag extends TMixin implements Tag {}
|
||||
|
||||
@Model(card.class.Card, core.class.Doc, DOMAIN_CARD)
|
||||
@UX(card.string.Card, card.icon.Card)
|
||||
export class TCard extends TDoc implements Card {
|
||||
@Prop(TypeRef(card.class.MasterTag), card.string.MasterTag)
|
||||
declare _class: Ref<MasterTag>
|
||||
@ -65,7 +76,8 @@ export class TCard extends TDoc implements Card {
|
||||
@Prop(TypeCollaborativeDoc(), card.string.Content)
|
||||
content!: MarkupBlobRef
|
||||
|
||||
parent?: Ref<Card> | null
|
||||
@Prop(TypeRef(card.class.Card), card.string.Parent)
|
||||
parent?: Ref<Card> | null
|
||||
|
||||
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
|
||||
attachments?: number
|
||||
@ -74,6 +86,10 @@ export class TCard extends TDoc implements Card {
|
||||
|
||||
@Prop(Collection(time.class.ToDo), getEmbeddedLabel('Action Items'))
|
||||
todos?: CollectionSize<ToDo>
|
||||
|
||||
children?: number
|
||||
|
||||
parentInfo!: ParentInfo[]
|
||||
}
|
||||
|
||||
@Model(card.class.MasterTagEditorSection, core.class.Doc, DOMAIN_MODEL)
|
||||
@ -102,6 +118,56 @@ export function createModel (builder: Builder): void {
|
||||
card.app.Card
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.ShowPopup,
|
||||
actionProps: {
|
||||
component: card.component.SetParentActionPopup,
|
||||
element: 'top',
|
||||
fillProps: {
|
||||
_objects: 'value'
|
||||
}
|
||||
},
|
||||
label: card.string.SetParent,
|
||||
icon: card.icon.MasterTag,
|
||||
input: 'none',
|
||||
category: card.category.Card,
|
||||
target: card.class.Card,
|
||||
context: {
|
||||
mode: ['context'],
|
||||
application: card.app.Card,
|
||||
group: 'associate'
|
||||
}
|
||||
},
|
||||
card.action.SetParent
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.UpdateDocument,
|
||||
actionProps: {
|
||||
key: 'parent',
|
||||
value: null
|
||||
},
|
||||
query: {
|
||||
parent: { $ne: null, $exists: true }
|
||||
},
|
||||
label: card.string.UnsetParent,
|
||||
icon: card.icon.MasterTag,
|
||||
input: 'none',
|
||||
category: card.category.Card,
|
||||
target: card.class.Card,
|
||||
context: {
|
||||
mode: ['context'],
|
||||
application: card.app.Card,
|
||||
group: 'associate'
|
||||
}
|
||||
},
|
||||
card.action.UnsetParent
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
@ -109,7 +175,7 @@ export function createModel (builder: Builder): void {
|
||||
attachTo: card.class.Card,
|
||||
descriptor: view.viewlet.Table,
|
||||
configOptions: {
|
||||
hiddenKeys: ['description', 'title']
|
||||
hiddenKeys: ['content', 'title']
|
||||
},
|
||||
config: [
|
||||
'',
|
||||
@ -121,6 +187,37 @@ export function createModel (builder: Builder): void {
|
||||
card.viewlet.CardTable
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
attachTo: card.class.Card,
|
||||
descriptor: view.viewlet.List,
|
||||
viewOptions: {
|
||||
groupBy: ['_class', 'createdBy', 'modifiedBy'],
|
||||
orderBy: [
|
||||
['modifiedOn', SortingOrder.Descending],
|
||||
['rank', SortingOrder.Ascending]
|
||||
],
|
||||
other: []
|
||||
},
|
||||
configOptions: {
|
||||
hiddenKeys: ['content', 'title']
|
||||
},
|
||||
config: [
|
||||
{ key: '', props: { showParent: true } },
|
||||
'_class',
|
||||
{ key: '', presenter: view.component.RolePresenter, label: card.string.Tags, props: { fullSize: true } },
|
||||
{ key: '', displayProps: { grow: true } },
|
||||
{
|
||||
key: 'modifiedOn',
|
||||
displayProps: { fixed: 'right', dividerBefore: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
card.viewlet.CardList
|
||||
)
|
||||
|
||||
builder.mixin(card.class.Card, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: card.component.CardPresenter
|
||||
})
|
||||
|
@ -18,10 +18,9 @@ import card from '@hcengineering/card-resources/src/plugin'
|
||||
import type { Client, Doc, Ref } from '@hcengineering/core'
|
||||
import {} from '@hcengineering/core'
|
||||
import { mergeIds, type Resource } from '@hcengineering/platform'
|
||||
import { type ObjectSearchCategory, type ObjectSearchFactory } from '@hcengineering/model-presentation'
|
||||
import { type Location, type ResolvedLocation } from '@hcengineering/ui/src/types'
|
||||
import { type LocationData } from '@hcengineering/workbench'
|
||||
import { type Action, type ViewAction } from '@hcengineering/view'
|
||||
import { type ActionCategory, type Action, type ViewAction } from '@hcengineering/view'
|
||||
|
||||
export default mergeIds(cardId, card, {
|
||||
app: {
|
||||
@ -31,17 +30,18 @@ export default mergeIds(cardId, card, {
|
||||
DeleteMasterTag: '' as ViewAction
|
||||
},
|
||||
action: {
|
||||
DeleteMasterTag: '' as Ref<Action>
|
||||
DeleteMasterTag: '' as Ref<Action>,
|
||||
SetParent: '' as Ref<Action<Doc, any>>,
|
||||
UnsetParent: '' as Ref<Action<Doc, any>>
|
||||
},
|
||||
category: {
|
||||
Card: '' as Ref<ActionCategory>
|
||||
},
|
||||
ids: {
|
||||
MasterTags: '' as Ref<Doc>,
|
||||
ManageMasterTags: '' as Ref<Doc>,
|
||||
TagRelations: '' as Ref<Doc>
|
||||
},
|
||||
completion: {
|
||||
CardQuery: '' as Resource<ObjectSearchFactory>,
|
||||
CardCategory: '' as Ref<ObjectSearchCategory>
|
||||
},
|
||||
resolver: {
|
||||
Location: '' as Resource<(loc: Location) => Promise<ResolvedLocation | undefined>>,
|
||||
LocationData: '' as Resource<(loc: Location) => Promise<LocationData>>
|
||||
|
@ -50,6 +50,42 @@ export function createModel (builder: Builder): void {
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverCard.trigger.OnMasterTagCreate,
|
||||
txMatch: {
|
||||
_class: core.class.TxCreateDoc,
|
||||
objectClass: { $in: [card.class.MasterTag, card.class.Tag] }
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverCard.trigger.OnCardRemove,
|
||||
isAsync: true,
|
||||
txMatch: {
|
||||
_class: core.class.TxRemoveDoc,
|
||||
objectClass: card.class.Card
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverCard.trigger.OnCardCreate,
|
||||
isAsync: true,
|
||||
txMatch: {
|
||||
_class: core.class.TxCreateDoc,
|
||||
objectClass: card.class.Card
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverCard.trigger.OnCardParentChange,
|
||||
isAsync: true,
|
||||
txMatch: {
|
||||
_class: core.class.TxUpdateDoc,
|
||||
objectClass: card.class.Card,
|
||||
operations: { parent: { $exists: true } }
|
||||
}
|
||||
})
|
||||
|
||||
builder.mixin(card.class.Card, core.class.Class, serverCore.mixin.SearchPresenter, {
|
||||
searchIcon: card.icon.Card,
|
||||
title: [['title']]
|
||||
|
@ -19,6 +19,10 @@
|
||||
"TagRelations": "Vztahy štítků",
|
||||
"DeleteTagConfirm": "Opravdu chcete smazat tento štítek? Všechny související vlastnosti budou smazány",
|
||||
"DeleteMasterTag": "Smazat typ",
|
||||
"DeleteMasterTagConfirm": "Opravdu chcete smazat tento typ? Všechny objekty související s tímto typem budou smazány"
|
||||
"DeleteMasterTagConfirm": "Opravdu chcete smazat tento typ? Všechny objekty související s tímto typem budou smazány",
|
||||
"UnsetParent": "Odebrat rodiče",
|
||||
"SetParent": "Nastavit rodiče",
|
||||
"CreateChild": "Vytvořit dítě",
|
||||
"Children": "Děti"
|
||||
}
|
||||
}
|
@ -19,6 +19,10 @@
|
||||
"DeleteTag": "Tag löschen",
|
||||
"DeleteTagConfirm": "Möchten Sie diesen Tag wirklich löschen? Alle zugehörigen Eigenschaften werden gelöscht",
|
||||
"DeleteMasterTag": "Master-Tag löschen",
|
||||
"DeleteMasterTagConfirm": "Möchten Sie diesen Master-Tag wirklich löschen? Alle mit diesem Master-Tag verbundenen Objekte werden gelöscht"
|
||||
"DeleteMasterTagConfirm": "Möchten Sie diesen Master-Tag wirklich löschen? Alle mit diesem Master-Tag verbundenen Objekte werden gelöscht",
|
||||
"UnsetParent": "Elternteil entfernen",
|
||||
"SetParent": "Elternteil setzen",
|
||||
"CreateChild": "Kind erstellen",
|
||||
"Children": "Kinder"
|
||||
}
|
||||
}
|
@ -19,6 +19,10 @@
|
||||
"TagRelations": "Tag Relations",
|
||||
"DeleteTagConfirm": "Are you sure you want to delete this tag? All related properties will be deleted",
|
||||
"DeleteMasterTag": "Delete Type",
|
||||
"DeleteMasterTagConfirm": "Are you sure you want to delete this type? All objects related to this type will be deleted"
|
||||
"DeleteMasterTagConfirm": "Are you sure you want to delete this type? All objects related to this type will be deleted",
|
||||
"UnsetParent": "Unset parent",
|
||||
"SetParent": "Set parent",
|
||||
"CreateChild": "Create child",
|
||||
"Children": "Children"
|
||||
}
|
||||
}
|
@ -19,6 +19,10 @@
|
||||
"TagRelations": "Relaciones de Etiquetas",
|
||||
"DeleteTagConfirm": "¿Estás seguro de que quieres eliminar esta etiqueta? Todas las propiedades relacionadas serán eliminadas",
|
||||
"DeleteMasterTag": "Eliminar Tipo",
|
||||
"DeleteMasterTagConfirm": "¿Estás seguro de que quieres eliminar este tipo? Todos los objetos relacionados con este tipo serán eliminados"
|
||||
"DeleteMasterTagConfirm": "¿Estás seguro de que quieres eliminar este tipo? Todos los objetos relacionados con este tipo serán eliminados",
|
||||
"UnsetParent": "Desasignar padre",
|
||||
"SetParent": "Asignar padre",
|
||||
"CreateChild": "Crear hijo",
|
||||
"Children": "Hijos"
|
||||
}
|
||||
}
|
@ -19,6 +19,10 @@
|
||||
"TagRelations": "Relations d'étiquettes",
|
||||
"DeleteTagConfirm": "Êtes-vous sûr de vouloir supprimer cette étiquette ? Toutes les propriétés associées seront supprimées",
|
||||
"DeleteMasterTag": "Supprimer le type",
|
||||
"DeleteMasterTagConfirm": "Êtes-vous sûr de vouloir supprimer ce type ? Tous les objets liés à ce type seront supprimés"
|
||||
"DeleteMasterTagConfirm": "Êtes-vous sûr de vouloir supprimer ce type ? Tous les objets liés à ce type seront supprimés",
|
||||
"UnsetParent": "Désassocier le parent",
|
||||
"SetParent": "Associer un parent",
|
||||
"CreateChild": "Créer un enfant",
|
||||
"Children": "Enfants"
|
||||
}
|
||||
}
|
@ -19,6 +19,10 @@
|
||||
"TagRelations": "Relazioni Tag",
|
||||
"DeleteTagConfirm": "Sei sicuro di voler eliminare questo tag? Tutte le proprietà correlate verranno eliminate",
|
||||
"DeleteMasterTag": "Elimina Tipo",
|
||||
"DeleteMasterTagConfirm": "Sei sicuro di voler eliminare questo tipo? Tutti gli oggetti correlati a questo tipo verranno eliminati"
|
||||
"DeleteMasterTagConfirm": "Sei sicuro di voler eliminare questo tipo? Tutti gli oggetti correlati a questo tipo verranno eliminati",
|
||||
"UnsetParent": "Rimuovi genitore",
|
||||
"SetParent": "Imposta genitore",
|
||||
"CreateChild": "Crea figlio",
|
||||
"Children": "Figli"
|
||||
}
|
||||
}
|
@ -19,6 +19,10 @@
|
||||
"TagRelations": "Relações de Tag",
|
||||
"DeleteTagConfirm": "Tem certeza que deseja excluir esta tag? Todas as propriedades relacionadas serão excluídas",
|
||||
"DeleteMasterTag": "Excluir Tipo",
|
||||
"DeleteMasterTagConfirm": "Tem certeza que deseja excluir este tipo? Todos os objetos relacionados a este tipo serão excluídos"
|
||||
"DeleteMasterTagConfirm": "Tem certeza que deseja excluir este tipo? Todos os objetos relacionados a este tipo serão excluídos",
|
||||
"UnsetParent": "Remover pai",
|
||||
"SetParent": "Definir pai",
|
||||
"CreateChild": "Criar filho",
|
||||
"Children": "Filhos"
|
||||
}
|
||||
}
|
@ -19,6 +19,10 @@
|
||||
"TagRelations": "Связи тегов",
|
||||
"DeleteTagConfirm": "Вы действительно хотите удалить этот тег? Все связанные свойства будут удалены",
|
||||
"DeleteMasterTag": "Удалить тип",
|
||||
"DeleteMasterTagConfirm": "Вы действительно хотите удалить этот тип? Все объекты, связанные с этим типом, будут удалены"
|
||||
"DeleteMasterTagConfirm": "Вы действительно хотите удалить этот тип? Все объекты, связанные с этим типом, будут удалены",
|
||||
"UnsetParent": "Отменить выбор родителя",
|
||||
"SetParent": "Выбрать родителя",
|
||||
"CreateChild": "Создать потомка",
|
||||
"Children": "Потомки"
|
||||
}
|
||||
}
|
@ -19,6 +19,10 @@
|
||||
"TagRelations": "标签关系",
|
||||
"DeleteTagConfirm": "您确定要删除此标签吗?所有相关属性都将被删除",
|
||||
"DeleteMasterTag": "删除类型",
|
||||
"DeleteMasterTagConfirm": "您确定要删除此类型吗?所有与此类型相关的对象都将被删除"
|
||||
"DeleteMasterTagConfirm": "您确定要删除此类型吗?所有与此类型相关的对象都将被删除",
|
||||
"UnsetParent": "取消父级",
|
||||
"SetParent": "设置父级",
|
||||
"CreateChild": "创建子级",
|
||||
"Children": "子级"
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@
|
||||
import { AnySvelteComponent, Icon, tooltip } from '@hcengineering/ui'
|
||||
import { ObjectPresenterType } from '@hcengineering/view'
|
||||
import { DocNavLink, ObjectMention } from '@hcengineering/view-resources'
|
||||
import ParentNamesPresenter from './ParentNamesPresenter.svelte'
|
||||
import card from '../plugin'
|
||||
|
||||
export let value: Card | undefined
|
||||
@ -26,8 +27,9 @@
|
||||
export let shouldShowAvatar: boolean = false
|
||||
export let noUnderline: boolean = disabled
|
||||
export let colorInherit: boolean = false
|
||||
export let noSelect: boolean = false
|
||||
export let noSelect: boolean = true
|
||||
export let inline = false
|
||||
export let showParent: boolean = false
|
||||
export let kind: 'list' | undefined = undefined
|
||||
export let type: ObjectPresenterType = 'link'
|
||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||
@ -48,7 +50,7 @@
|
||||
component={card.component.EditCard}
|
||||
shrink={0}
|
||||
>
|
||||
<span class="issuePresenterRoot" class:list={kind === 'list'} class:cursor-pointer={!disabled}>
|
||||
<span class="presenterRoot" class:cursor-pointer={!disabled}>
|
||||
{#if shouldShowAvatar}
|
||||
<div class="icon" use:tooltip={{ label: card.string.Card }}>
|
||||
<Icon icon={icon ?? card.icon.Card} size={'small'} />
|
||||
@ -56,6 +58,9 @@
|
||||
{/if}
|
||||
<span class="overflow-label" class:select-text={!noSelect} title={value?.title}>
|
||||
{value.title}
|
||||
{#if showParent}
|
||||
<ParentNamesPresenter {value} />
|
||||
{/if}
|
||||
<slot name="details" />
|
||||
</span>
|
||||
</span>
|
||||
@ -69,19 +74,11 @@
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.issuePresenterRoot {
|
||||
.presenterRoot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:not(.list) {
|
||||
color: var(--theme-content-color);
|
||||
}
|
||||
|
||||
&.list {
|
||||
color: var(--theme-halfcontent-color);
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-right: 0.5rem;
|
||||
color: var(--theme-dark-color);
|
||||
|
191
plugins/card-resources/src/components/Childs.svelte
Normal file
191
plugins/card-resources/src/components/Childs.svelte
Normal file
@ -0,0 +1,191 @@
|
||||
<script lang="ts">
|
||||
import { Card, CardEvents } from '@hcengineering/card'
|
||||
import core, { Data, Doc, fillDefaults, MarkupBlobRef, SortingOrder, WithLookup } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
Label,
|
||||
Scroller,
|
||||
Button,
|
||||
getCurrentLocation,
|
||||
IconAdd,
|
||||
navigate,
|
||||
resizeObserver,
|
||||
Section
|
||||
} from '@hcengineering/ui'
|
||||
import view, { Viewlet, ViewletPreference, ViewOptions } from '@hcengineering/view'
|
||||
import {
|
||||
List,
|
||||
ListSelectionProvider,
|
||||
restrictionStore,
|
||||
SelectDirection,
|
||||
ViewletsSettingButton
|
||||
} from '@hcengineering/view-resources'
|
||||
import card from '../plugin'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import { makeRank } from '@hcengineering/rank'
|
||||
|
||||
export let object: Card
|
||||
export let readonly: boolean = false
|
||||
|
||||
let viewlet: WithLookup<Viewlet> | undefined
|
||||
let viewOptions: ViewOptions | undefined
|
||||
let preference: ViewletPreference | undefined = undefined
|
||||
|
||||
const query = createQuery()
|
||||
|
||||
const viewletId = card.viewlet.CardList
|
||||
|
||||
let list: List
|
||||
const listProvider = new ListSelectionProvider(
|
||||
(offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection, noScroll?: boolean) => {
|
||||
if (dir === 'vertical') {
|
||||
// Select next
|
||||
list?.select(offset, of, noScroll)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
let docs: Doc[] = []
|
||||
|
||||
$: query.query(
|
||||
view.class.Viewlet,
|
||||
{
|
||||
_id: viewletId
|
||||
},
|
||||
(res) => {
|
||||
viewlet = res[0]
|
||||
},
|
||||
{
|
||||
lookup: {
|
||||
descriptor: view.class.ViewletDescriptor
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const preferenceQuery = createQuery()
|
||||
|
||||
$: if (viewlet != null) {
|
||||
preferenceQuery.query(
|
||||
view.class.ViewletPreference,
|
||||
{
|
||||
space: core.space.Workspace,
|
||||
attachedTo: viewletId
|
||||
},
|
||||
(res) => {
|
||||
preference = res[0]
|
||||
},
|
||||
{ limit: 1 }
|
||||
)
|
||||
} else {
|
||||
preferenceQuery.unsubscribe()
|
||||
preference = undefined
|
||||
}
|
||||
|
||||
$: selectedConfig = preference?.config ?? viewlet?.config ?? []
|
||||
$: config = selectedConfig?.filter((p) =>
|
||||
typeof p === 'string'
|
||||
? !p.includes('$lookup') && !p.startsWith('@')
|
||||
: !p.key.includes('$lookup') && !p.key.startsWith('@')
|
||||
)
|
||||
|
||||
let listWidth: number
|
||||
|
||||
async function createCard (): Promise<void> {
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
const lastOne = await client.findOne(card.class.Card, {}, { sort: { rank: SortingOrder.Descending } })
|
||||
const title = await translate(card.string.Card, {})
|
||||
|
||||
const data: Data<Card> = {
|
||||
parent: object._id,
|
||||
title,
|
||||
rank: makeRank(lastOne?.rank, undefined),
|
||||
content: '' as MarkupBlobRef,
|
||||
parentInfo: [
|
||||
...(object.parentInfo ?? []),
|
||||
{
|
||||
_id: object._id,
|
||||
_class: object._class,
|
||||
title: object.title
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const filledData = fillDefaults(hierarchy, data, object._class)
|
||||
|
||||
const _id = await client.createDoc(object._class, core.space.Workspace, filledData)
|
||||
|
||||
Analytics.handleEvent(CardEvents.CardCreated)
|
||||
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[3] = _id
|
||||
loc.path.length = 4
|
||||
navigate(loc)
|
||||
}
|
||||
|
||||
const selection = listProvider.selection
|
||||
</script>
|
||||
|
||||
<Section label={card.string.Children} icon={card.icon.Card}>
|
||||
<svelte:fragment slot="header">
|
||||
<ViewletsSettingButton bind:viewOptions viewletQuery={{ _id: viewletId }} kind={'tertiary'} bind:viewlet />
|
||||
{#if !$restrictionStore.readonly && !readonly}
|
||||
<Button
|
||||
id="add-child-card"
|
||||
icon={IconAdd}
|
||||
label={card.string.CreateChild}
|
||||
kind={'ghost'}
|
||||
showTooltip={{ label: card.string.CreateChild, direction: 'bottom' }}
|
||||
on:click={() => {
|
||||
void createCard()
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content">
|
||||
{#if (object?.children ?? 0) > 0 && viewOptions !== undefined && viewlet}
|
||||
<Scroller horizontal>
|
||||
<div
|
||||
class="list"
|
||||
use:resizeObserver={(evt) => {
|
||||
listWidth = evt.clientWidth
|
||||
}}
|
||||
>
|
||||
<List
|
||||
bind:this={list}
|
||||
{listProvider}
|
||||
_class={card.class.Card}
|
||||
query={{
|
||||
parent: object._id
|
||||
}}
|
||||
selectedObjectIds={$selection ?? []}
|
||||
configurations={undefined}
|
||||
{config}
|
||||
{viewOptions}
|
||||
compactMode={listWidth <= 600}
|
||||
on:docs
|
||||
on:row-focus={(event) => {
|
||||
listProvider.updateFocus(event.detail ?? undefined)
|
||||
}}
|
||||
on:check={(event) => {
|
||||
listProvider.updateSelection(event.detail.docs, event.detail.value)
|
||||
}}
|
||||
on:content={(evt) => {
|
||||
docs = evt.detail
|
||||
listProvider.update(evt.detail)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Scroller>
|
||||
{:else if !readonly}
|
||||
<div class="antiSection-empty solid clear-mins mt-3">
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<span class="over-underline content-color" on:click={createCard}>
|
||||
<Label label={card.string.CreateChild} />
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Section>
|
@ -39,32 +39,11 @@
|
||||
kind: isMasterTag ? ClassifierKind.CLASS : ClassifierKind.MIXIN,
|
||||
icon: isMasterTag ? card.icon.MasterTag : card.icon.Tag
|
||||
}
|
||||
const baseViewlet = client.getModel().getObject(card.viewlet.CardTable)
|
||||
const base = extractObjectProps(baseViewlet)
|
||||
const id = generateId<Class<MasterTag>>()
|
||||
const ops = client.apply('tag')
|
||||
await ops.createDoc(_class, core.space.Model, data, id)
|
||||
await ops.createMixin(id, core.class.Mixin, core.space.Model, setting.mixin.Editable, {
|
||||
value: true
|
||||
})
|
||||
await ops.createMixin(id, core.class.Mixin, core.space.Model, setting.mixin.UserMixin, {})
|
||||
await ops.createDoc(view.class.Viewlet, core.space.Model, {
|
||||
...base,
|
||||
attachTo: id
|
||||
})
|
||||
await ops.commit()
|
||||
dispatch('close')
|
||||
}
|
||||
|
||||
function extractObjectProps<T extends Doc> (doc: T): Data<T> {
|
||||
const data: any = {}
|
||||
for (const key in doc) {
|
||||
if (key === '_id') {
|
||||
continue
|
||||
}
|
||||
data[key] = doc[key]
|
||||
}
|
||||
return data as Data<T>
|
||||
const id = generateId<Class<MasterTag>>()
|
||||
await client.createDoc(_class, core.space.Model, data, id)
|
||||
|
||||
dispatch('close')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -40,6 +40,7 @@
|
||||
import CardPresenter from './CardPresenter.svelte'
|
||||
import ContentEditor from './ContentEditor.svelte'
|
||||
import TagsEditor from './TagsEditor.svelte'
|
||||
import Childs from './Childs.svelte'
|
||||
|
||||
export let _id: Ref<Card>
|
||||
export let readonly: boolean = false
|
||||
@ -254,6 +255,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Childs object={doc} {readonly} />
|
||||
<RelationsEditor object={doc} {readonly} />
|
||||
|
||||
<svelte:fragment slot="utils">
|
||||
|
@ -22,7 +22,7 @@
|
||||
import { onDestroy } from 'svelte'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
|
||||
export let currentSpace: Ref<Class<Doc>> = card.class.Card
|
||||
export let currentSpace: Ref<Class<Doc>>
|
||||
|
||||
$: _class = currentSpace
|
||||
|
||||
|
@ -69,7 +69,7 @@
|
||||
icon={setting.icon.Setting}
|
||||
kind={'link'}
|
||||
size={'medium'}
|
||||
showTooltip={{ label: setting.string.ClassSetting }}
|
||||
showTooltip={{ label: setting.string.Setting }}
|
||||
on:click={(ev) => {
|
||||
ev.stopPropagation()
|
||||
const loc = getCurrentResolvedLocation()
|
||||
|
@ -56,7 +56,8 @@
|
||||
const data: Data<Card> = {
|
||||
title,
|
||||
rank: makeRank(lastOne?.rank, undefined),
|
||||
content: '' as MarkupBlobRef
|
||||
content: '' as MarkupBlobRef,
|
||||
parentInfo: []
|
||||
}
|
||||
|
||||
const filledData = fillDefaults(hierarchy, data, _class)
|
||||
@ -85,7 +86,7 @@
|
||||
<Button
|
||||
icon={IconAdd}
|
||||
label={card.string.CreateCard}
|
||||
disabled={allClasses.length === 0 || _class === undefined}
|
||||
disabled={allClasses.length === 0 || _class === undefined || _class === card.class.Card}
|
||||
justify={'left'}
|
||||
width={'100%'}
|
||||
kind={'primary'}
|
||||
|
@ -0,0 +1,81 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 { concatLink } from '@hcengineering/core'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import presentation, { NavLink } from '@hcengineering/presentation'
|
||||
import { getCurrentLocation, locationToUrl } from '@hcengineering/ui'
|
||||
import { cardId, Card, ParentInfo } from '@hcengineering/card'
|
||||
|
||||
export let value: Card | undefined
|
||||
|
||||
export let maxWidth = ''
|
||||
|
||||
function getHref (parentInfo: ParentInfo) {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[2] = cardId
|
||||
loc.path[3] = parentInfo._id
|
||||
loc.path.length = 4
|
||||
const frontUrl = getMetadata(presentation.metadata.FrontUrl) ?? window.location.origin
|
||||
return concatLink(frontUrl, locationToUrl(loc))
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if value && Array.isArray(value.parentInfo)}
|
||||
<div class="root" style:max-width={maxWidth}>
|
||||
<span class="names">
|
||||
{#each value.parentInfo as parentInfo}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<NavLink href={getHref(parentInfo)}>
|
||||
<span class="parent-label overflow-label cursor-pointer" title={parentInfo.title}>
|
||||
{parentInfo.title}
|
||||
</span>
|
||||
</NavLink>
|
||||
{/each}
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.root {
|
||||
display: inline-flex;
|
||||
margin-left: 0;
|
||||
min-width: 0;
|
||||
|
||||
.names {
|
||||
display: inline-flex;
|
||||
min-width: 0;
|
||||
color: var(--theme-dark-color);
|
||||
}
|
||||
|
||||
.parent-label {
|
||||
flex-shrink: 5;
|
||||
color: var(--theme-dark-color);
|
||||
|
||||
&:hover {
|
||||
color: var(--theme-caption-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
&:active {
|
||||
color: var(--theme-content-color);
|
||||
}
|
||||
&::before {
|
||||
content: '›';
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,74 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 { Card } from '@hcengineering/card'
|
||||
import { FindOptions, SortingOrder } from '@hcengineering/core'
|
||||
import { ObjectPopup, getClient } from '@hcengineering/presentation'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import card from '../plugin'
|
||||
|
||||
export let value: Card | Card[]
|
||||
export let width: 'medium' | 'large' | 'full' = 'large'
|
||||
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
const options: FindOptions<Card> = {
|
||||
sort: { modifiedOn: SortingOrder.Descending }
|
||||
}
|
||||
|
||||
async function onClose ({ detail: parent }: CustomEvent<Card | undefined | null>): Promise<void> {
|
||||
const vv = Array.isArray(value) ? value : [value]
|
||||
for (const docValue of vv) {
|
||||
if (
|
||||
'_class' in docValue &&
|
||||
parent !== undefined &&
|
||||
parent?._id !== docValue.parent &&
|
||||
parent?._id !== docValue._id
|
||||
) {
|
||||
await client.update(docValue, {
|
||||
parent: parent === null ? null : parent._id
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
dispatch('close', parent)
|
||||
}
|
||||
|
||||
$: selected = !Array.isArray(value) ? value.parent ?? undefined : undefined
|
||||
$: ignoreObjects = !Array.isArray(value) ? [value._id] : undefined
|
||||
</script>
|
||||
|
||||
<ObjectPopup
|
||||
_class={card.class.Card}
|
||||
{options}
|
||||
{selected}
|
||||
category={card.completion.CardCategory}
|
||||
multiSelect={false}
|
||||
allowDeselect={true}
|
||||
placeholder={card.string.SetParent}
|
||||
create={undefined}
|
||||
{ignoreObjects}
|
||||
shadows={true}
|
||||
{width}
|
||||
searchMode={'spotlight'}
|
||||
on:update
|
||||
on:close={onClose}
|
||||
>
|
||||
<svelte:fragment slot="item" let:item>
|
||||
<div class="flex-center clear-mins w-full h-9">
|
||||
<span class="overflow-label w-full content-color">{item.title}</span>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</ObjectPopup>
|
@ -76,7 +76,7 @@
|
||||
icon={setting.icon.Setting}
|
||||
kind={'link'}
|
||||
size={'medium'}
|
||||
showTooltip={{ label: setting.string.ClassSetting }}
|
||||
showTooltip={{ label: setting.string.Setting }}
|
||||
on:click={(ev) => {
|
||||
ev.stopPropagation()
|
||||
const loc = getCurrentResolvedLocation()
|
||||
|
@ -34,6 +34,7 @@ import ProperitiesSection from './components/settings/ProperitiesSection.svelte'
|
||||
import TagsSection from './components/settings/TagsSection.svelte'
|
||||
import RelationsSection from './components/settings/RelationsSection.svelte'
|
||||
import ChildsSection from './components/settings/ChildsSection.svelte'
|
||||
import SetParentActionPopup from './components/SetParentActionPopup.svelte'
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
@ -49,7 +50,8 @@ export default async (): Promise<Resources> => ({
|
||||
ProperitiesSection,
|
||||
TagsSection,
|
||||
RelationsSection,
|
||||
ChildsSection
|
||||
ChildsSection,
|
||||
SetParentActionPopup
|
||||
},
|
||||
completion: {
|
||||
CardQuery: queryCard
|
||||
|
@ -15,7 +15,8 @@
|
||||
|
||||
import card, { cardId } from '@hcengineering/card'
|
||||
import { type Ref } from '@hcengineering/core'
|
||||
import { type IntlString, mergeIds } from '@hcengineering/platform'
|
||||
import { type IntlString, mergeIds, type Resource } from '@hcengineering/platform'
|
||||
import { type ObjectSearchCategory, type ObjectSearchFactory } from '@hcengineering/presentation'
|
||||
import { type AnyComponent } from '@hcengineering/ui/src/types'
|
||||
import { type Viewlet } from '@hcengineering/view'
|
||||
|
||||
@ -33,10 +34,16 @@ export default mergeIds(cardId, card, {
|
||||
ProperitiesSection: '' as AnyComponent,
|
||||
TagsSection: '' as AnyComponent,
|
||||
ChildsSection: '' as AnyComponent,
|
||||
RelationsSection: '' as AnyComponent
|
||||
RelationsSection: '' as AnyComponent,
|
||||
SetParentActionPopup: '' as AnyComponent
|
||||
},
|
||||
completion: {
|
||||
CardQuery: '' as Resource<ObjectSearchFactory>,
|
||||
CardCategory: '' as Ref<ObjectSearchCategory>
|
||||
},
|
||||
viewlet: {
|
||||
CardTable: '' as Ref<Viewlet>
|
||||
CardTable: '' as Ref<Viewlet>,
|
||||
CardList: '' as Ref<Viewlet>
|
||||
},
|
||||
string: {
|
||||
CreateMasterTag: '' as IntlString,
|
||||
@ -53,6 +60,10 @@ export default mergeIds(cardId, card, {
|
||||
DeleteTagConfirm: '' as IntlString,
|
||||
DeleteMasterTag: '' as IntlString,
|
||||
DeleteMasterTagConfirm: '' as IntlString,
|
||||
TagRelations: '' as IntlString
|
||||
TagRelations: '' as IntlString,
|
||||
UnsetParent: '' as IntlString,
|
||||
SetParent: '' as IntlString,
|
||||
CreateChild: '' as IntlString,
|
||||
Children: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
@ -26,10 +26,18 @@ export interface Card extends Doc {
|
||||
_class: Ref<MasterTag>
|
||||
title: string
|
||||
content: MarkupBlobRef
|
||||
children?: number
|
||||
parentInfo: ParentInfo[]
|
||||
parent?: Ref<Card> | null
|
||||
rank: Rank
|
||||
}
|
||||
|
||||
export interface ParentInfo {
|
||||
_id: Ref<Card>
|
||||
_class: Ref<MasterTag>
|
||||
title: string
|
||||
}
|
||||
|
||||
export interface MasterTagEditorSection extends Doc {
|
||||
id: string
|
||||
label: IntlString
|
||||
|
@ -784,9 +784,6 @@ class Connection implements ClientConnection {
|
||||
// We need to revert deleted query simple values.
|
||||
// We need to get rid of simple query parameters matched in documents
|
||||
for (const doc of result) {
|
||||
if (doc._class == null) {
|
||||
doc._class = _class
|
||||
}
|
||||
for (const [k, v] of Object.entries(query)) {
|
||||
if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') {
|
||||
if (doc[k] == null) {
|
||||
@ -794,6 +791,9 @@ class Connection implements ClientConnection {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (doc._class == null) {
|
||||
doc._class = _class
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
|
@ -24,5 +24,7 @@
|
||||
</script>
|
||||
|
||||
{#if _class}
|
||||
<Label label={_class.label} />
|
||||
<div>
|
||||
<Label label={_class.label} />
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -72,7 +72,7 @@
|
||||
$: configOptions = options
|
||||
$: resultOptions = {
|
||||
...configOptions,
|
||||
lookup,
|
||||
...(Object.keys(lookup).length > 0 ? lookup : {}),
|
||||
...(orderBy !== undefined ? { sort: { [orderBy[0]]: orderBy[1] } } : {})
|
||||
}
|
||||
|
||||
@ -109,7 +109,7 @@
|
||||
queryNoLookup,
|
||||
(res) => {
|
||||
fastDocs = res
|
||||
// console.log('query, res', queryNoLookup, res)
|
||||
// console.log('query, res', res)
|
||||
fastQueryIds = new Set(res.map((it) => it._id))
|
||||
},
|
||||
{ ...categoryQueryOptions, limit: 1000 }
|
||||
|
@ -38,6 +38,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/view": "^0.6.13",
|
||||
"@hcengineering/setting": "^0.6.17",
|
||||
"@hcengineering/card": "^0.6.0",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
|
@ -13,10 +13,22 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, { AnyAttribute, Class, Doc, Ref, Tx, TxCreateDoc, TxProcessor, TxRemoveDoc } from '@hcengineering/core'
|
||||
import card, { DOMAIN_CARD, MasterTag } from '@hcengineering/card'
|
||||
import view from '@hcengineering/view'
|
||||
import card, { Card, DOMAIN_CARD, MasterTag, Tag } from '@hcengineering/card'
|
||||
import core, {
|
||||
AnyAttribute,
|
||||
Class,
|
||||
Data,
|
||||
Doc,
|
||||
Ref,
|
||||
Tx,
|
||||
TxCreateDoc,
|
||||
TxProcessor,
|
||||
TxRemoveDoc,
|
||||
TxUpdateDoc
|
||||
} from '@hcengineering/core'
|
||||
import { TriggerControl } from '@hcengineering/server-core'
|
||||
import view from '@hcengineering/view'
|
||||
import setting from '@hcengineering/setting'
|
||||
|
||||
async function OnAttribute (ctx: TxCreateDoc<AnyAttribute>[], control: TriggerControl): Promise<Tx[]> {
|
||||
const attr = TxProcessor.createDoc2Doc(ctx[0])
|
||||
@ -79,7 +91,7 @@ async function OnAttributeRemove (ctx: TxRemoveDoc<AnyAttribute>[], control: Tri
|
||||
return []
|
||||
}
|
||||
|
||||
async function OnMasterTagRemove (ctx: TxRemoveDoc<MasterTag>[], control: TriggerControl): Promise<Tx[]> {
|
||||
async function OnMasterTagRemove (ctx: TxRemoveDoc<MasterTag | Tag>[], control: TriggerControl): Promise<Tx[]> {
|
||||
const removeTx = ctx[0]
|
||||
const removedTag = control.removedMap.get(removeTx.objectId)
|
||||
if (removedTag === undefined) return []
|
||||
@ -91,6 +103,18 @@ async function OnMasterTagRemove (ctx: TxRemoveDoc<MasterTag>[], control: Trigge
|
||||
res.push(control.txFactory.createTxRemoveDoc(card._class, card.space, card._id))
|
||||
}
|
||||
}
|
||||
const viewlets = await control.findAll(control.ctx, view.class.Viewlet, {
|
||||
attachTo: removeTx.objectId
|
||||
})
|
||||
for (const viewlet of viewlets) {
|
||||
res.push(control.txFactory.createTxRemoveDoc(viewlet._class, viewlet.space, viewlet._id))
|
||||
}
|
||||
const attributes = control.modelDb.findAllSync(core.class.Attribute, {
|
||||
attributeOf: removeTx.objectId
|
||||
})
|
||||
for (const attribute of attributes) {
|
||||
res.push(control.txFactory.createTxRemoveDoc(attribute._class, attribute.space, attribute._id))
|
||||
}
|
||||
const desc = control.hierarchy.getDescendants(removeTx.objectId)
|
||||
for (const des of desc) {
|
||||
if (des === removeTx.objectId) continue
|
||||
@ -115,11 +139,142 @@ async function OnMasterTagRemove (ctx: TxRemoveDoc<MasterTag>[], control: Trigge
|
||||
return res
|
||||
}
|
||||
|
||||
function extractObjectProps<T extends Doc> (doc: T): Data<T> {
|
||||
const data: any = {}
|
||||
for (const key in doc) {
|
||||
if (key === '_id') {
|
||||
continue
|
||||
}
|
||||
data[key] = doc[key]
|
||||
}
|
||||
return data as Data<T>
|
||||
}
|
||||
|
||||
async function OnMasterTagCreate (ctx: TxCreateDoc<MasterTag | Tag>[], control: TriggerControl): Promise<Tx[]> {
|
||||
const createTx = ctx[0]
|
||||
const tag = TxProcessor.createDoc2Doc(createTx)
|
||||
const res: Tx[] = []
|
||||
res.push(
|
||||
control.txFactory.createTxMixin(createTx.objectId, core.class.Mixin, core.space.Model, setting.mixin.Editable, {
|
||||
value: true
|
||||
})
|
||||
)
|
||||
res.push(
|
||||
control.txFactory.createTxMixin(createTx.objectId, core.class.Mixin, core.space.Model, setting.mixin.UserMixin, {})
|
||||
)
|
||||
const viewlets = await control.findAll(control.ctx, view.class.Viewlet, { attachTo: tag.extends })
|
||||
for (const viewlet of viewlets) {
|
||||
const base = extractObjectProps(viewlet)
|
||||
res.push(
|
||||
control.txFactory.createTxCreateDoc(view.class.Viewlet, core.space.Model, {
|
||||
...base,
|
||||
attachTo: createTx.objectId
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
async function OnCardRemove (ctx: TxRemoveDoc<Card>[], control: TriggerControl): Promise<Tx[]> {
|
||||
const removeTx = ctx[0]
|
||||
const removedCard = control.removedMap.get(removeTx.objectId) as Card
|
||||
if (removedCard === undefined) return []
|
||||
const res: Tx[] = []
|
||||
const cards = await control.findAll(control.ctx, card.class.Card, { parent: removedCard._id })
|
||||
for (const card of cards) {
|
||||
res.push(control.txFactory.createTxRemoveDoc(card._class, card.space, card._id))
|
||||
}
|
||||
if (removedCard.parent != null) {
|
||||
res.push(
|
||||
control.txFactory.createTxUpdateDoc(card.class.Card, core.space.Workspace, removedCard.parent, {
|
||||
$inc: {
|
||||
children: -1
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
async function OnCardParentChange (ctx: TxUpdateDoc<Card>[], control: TriggerControl): Promise<Tx[]> {
|
||||
const updateTx = ctx[0]
|
||||
if (updateTx.operations.parent === undefined) return []
|
||||
const newParent = updateTx.operations.parent
|
||||
const doc = (await control.findAll(control.ctx, card.class.Card, { _id: updateTx.objectId }))[0]
|
||||
if (doc === undefined) return []
|
||||
const oldParent = doc.parentInfo[doc.parentInfo.length - 1]?._id
|
||||
const res: Tx[] = []
|
||||
if (oldParent != null) {
|
||||
const parent = (await control.findAll(control.ctx, card.class.Card, { _id: oldParent }))[0]
|
||||
if (parent !== undefined) {
|
||||
res.push(
|
||||
control.txFactory.createTxUpdateDoc(parent._class, parent.space, parent._id, {
|
||||
$inc: {
|
||||
children: -1
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
if (newParent != null) {
|
||||
const parent = (await control.findAll(control.ctx, card.class.Card, { _id: newParent }))[0]
|
||||
if (parent !== undefined) {
|
||||
res.push(
|
||||
control.txFactory.createTxUpdateDoc(parent._class, parent.space, parent._id, {
|
||||
$inc: {
|
||||
children: 1
|
||||
}
|
||||
})
|
||||
)
|
||||
res.push(
|
||||
control.txFactory.createTxUpdateDoc(doc._class, doc.space, doc._id, {
|
||||
parentInfo: [
|
||||
...parent.parentInfo,
|
||||
{
|
||||
_id: parent._id,
|
||||
_class: parent._class,
|
||||
title: parent.title
|
||||
}
|
||||
]
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
async function OnCardCreate (ctx: TxCreateDoc<Card>[], control: TriggerControl): Promise<Tx[]> {
|
||||
const createTx = ctx[0]
|
||||
const doc = TxProcessor.createDoc2Doc(createTx)
|
||||
const res: Tx[] = []
|
||||
if (doc.parent != null) {
|
||||
const parent = (await control.findAll(control.ctx, card.class.Card, { _id: doc.parent }))[0]
|
||||
if (parent !== undefined) {
|
||||
res.push(
|
||||
control.txFactory.createTxUpdateDoc(parent._class, parent.space, parent._id, {
|
||||
$inc: {
|
||||
children: 1
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export default async () => ({
|
||||
trigger: {
|
||||
OnAttribute,
|
||||
OnAttributeRemove,
|
||||
OnMasterTagRemove
|
||||
OnMasterTagCreate,
|
||||
OnMasterTagRemove,
|
||||
OnCardRemove,
|
||||
OnCardCreate,
|
||||
OnCardParentChange
|
||||
}
|
||||
})
|
||||
|
@ -29,6 +29,10 @@ export default plugin(serverCardId, {
|
||||
trigger: {
|
||||
OnAttribute: '' as Resource<TriggerFunc>,
|
||||
OnAttributeRemove: '' as Resource<TriggerFunc>,
|
||||
OnMasterTagRemove: '' as Resource<TriggerFunc>
|
||||
OnMasterTagCreate: '' as Resource<TriggerFunc>,
|
||||
OnMasterTagRemove: '' as Resource<TriggerFunc>,
|
||||
OnCardCreate: '' as Resource<TriggerFunc>,
|
||||
OnCardParentChange: '' as Resource<TriggerFunc>,
|
||||
OnCardRemove: '' as Resource<TriggerFunc>
|
||||
}
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user