Cards fixes (#8186)

This commit is contained in:
Denis Bykhov 2025-03-11 09:00:29 +05:00 committed by GitHub
parent 92273f548b
commit a89f75ae9f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 153 additions and 76 deletions

View File

@ -16,13 +16,12 @@ import {
CardEvents,
cardId,
DOMAIN_CARD,
type ParentInfo,
type Card,
type MasterTag,
type ParentInfo,
type Tag
} from '@hcengineering/card'
import chunter from '@hcengineering/chunter'
import contact from '@hcengineering/contact'
import core, {
AccountRole,
DOMAIN_MODEL,
@ -368,8 +367,7 @@ export function createModel (builder: Builder): void {
name: 'tagrelation',
label: card.string.TagRelations,
icon: setting.icon.Relations,
props: { _classes: [card.class.Card, contact.class.Contact], exclude: [] },
component: setting.component.RelationSetting,
component: card.component.RelationSetting,
group: 'settings-editor',
role: AccountRole.Maintainer,
order: 4501
@ -381,7 +379,7 @@ export function createModel (builder: Builder): void {
setting.class.SettingsCategory,
core.space.Model,
{
name: 'masterTags',
name: 'types',
label: card.string.MasterTags,
icon: card.icon.Card,
component: card.component.ManageMasterTagsContent,

View File

@ -77,12 +77,11 @@ export function createModel (builder: Builder): void {
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverCard.trigger.OnCardParentChange,
trigger: serverCard.trigger.OnCardUpdate,
isAsync: true,
txMatch: {
_class: core.class.TxUpdateDoc,
objectClass: card.class.Card,
operations: { parent: { $exists: true } }
objectClass: card.class.Card
}
})

View File

@ -5,10 +5,10 @@
"Cards": "Karten",
"Content": "Inhalt",
"CreateCard": "Karte erstellen",
"CreateMasterTag": "Master-Tag erstellen",
"CreateMasterTag": "Typ erstellen",
"CreateTag": "Tag erstellen",
"MasterTag": "Master-Tag",
"MasterTags": "Master-Tags",
"MasterTag": "Typ",
"MasterTags": "Typen",
"Parent": "Eltern",
"Tags": "Tags",
"Tag": "Tag",
@ -18,11 +18,11 @@
"TagRelations": "Tag-Beziehungen",
"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",
"DeleteMasterTag": "Typ löschen",
"DeleteMasterTagConfirm": "Möchten Sie diesen Typ wirklich löschen? Alle mit diesem Typ verbundenen Objekte werden gelöscht",
"UnsetParent": "Elternteil entfernen",
"SetParent": "Elternteil setzen",
"CreateChild": "Kind erstellen",
"Children": "Kinder"
}
}
}

View File

@ -40,6 +40,9 @@
{:else if value}
{#if type === 'link'}
<div class="flex-row-center">
{#if showParent}
<ParentNamesPresenter {value} />
{/if}
<DocNavLink
object={value}
{onClick}
@ -58,9 +61,6 @@
{/if}
<span class="overflow-label" class:select-text={!noSelect} title={value?.title}>
{value.title}
{#if showParent}
<ParentNamesPresenter {value} />
{/if}
<slot name="details" />
</span>
</span>

View File

@ -74,7 +74,7 @@
ev.stopPropagation()
const loc = getCurrentResolvedLocation()
loc.path[2] = settingId
loc.path[3] = 'masterTags'
loc.path[3] = 'types'
loc.path[4] = value._class
loc.path.length = 5
loc.fragment = undefined

View File

@ -72,7 +72,7 @@
&:active {
color: var(--theme-content-color);
}
&::before {
&::after {
content: '';
padding: 0 0.25rem;
}

View File

@ -14,7 +14,7 @@
-->
<script lang="ts">
import { Card } from '@hcengineering/card'
import { FindOptions, SortingOrder } from '@hcengineering/core'
import { Doc, FindOptions, SortingOrder } from '@hcengineering/core'
import { ObjectPopup, getClient } from '@hcengineering/presentation'
import { createEventDispatcher } from 'svelte'
import card from '../plugin'
@ -48,6 +48,13 @@
$: selected = !Array.isArray(value) ? value.parent ?? undefined : undefined
$: ignoreObjects = !Array.isArray(value) ? [value._id] : undefined
$: cards = new Set(Array.isArray(value) ? value.map((p) => p._id) : [value._id])
const filter = (it: Doc): boolean => {
const card = it as Card
return !card.parentInfo.some((p) => cards.has(p._id))
}
</script>
<ObjectPopup
@ -59,6 +66,7 @@
allowDeselect={true}
placeholder={card.string.SetParent}
create={undefined}
{filter}
{ignoreObjects}
shadows={true}
{width}

View File

@ -81,7 +81,7 @@
ev.stopPropagation()
const loc = getCurrentResolvedLocation()
loc.path[2] = settingId
loc.path[3] = 'masterTags'
loc.path[3] = 'types'
loc.path[4] = tag._id
loc.path.length = 5
loc.fragment = undefined

View File

@ -17,7 +17,7 @@
import { Class, Doc, Ref } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { NavItem } from '@hcengineering/ui'
import { NavLink, showMenu } from '@hcengineering/view-resources'
import { NavLink } from '@hcengineering/view-resources'
import { createEventDispatcher } from 'svelte'
import card from '../plugin'
@ -67,9 +67,6 @@
on:click={() => {
dispatch('select', clazz._id)
}}
on:contextmenu={(evt) => {
showMenu(evt, { object: clazz })
}}
/>
</NavLink>
{#if (descendants.get(clazz._id)?.length ?? 0) > 0}

View File

@ -0,0 +1,17 @@
<script lang="ts">
import contact from '@hcengineering/contact'
import { Class, Doc, Ref } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { CreateRelation } from '@hcengineering/setting-resources'
import card from '../../plugin'
export let aClass: Ref<Class<Doc>> | undefined = undefined
const client = getClient()
const hierarchy = client.getHierarchy()
const _classes = [...hierarchy.getDescendants(card.class.Card), contact.class.Contact].filter(
(c) => c !== card.class.Card
)
</script>
<CreateRelation {aClass} {_classes} exclude={[]} on:close />

View File

@ -35,7 +35,7 @@
onDestroy(resolvedLocationStore.subscribe(handleLocationChanged))
function handleLocationChanged ({ path }: Location): void {
if (path[3] !== 'masterTags' || path[4] === undefined) {
if (path[3] !== 'types' || path[4] === undefined) {
selectedTagId = undefined
} else {
selectedTagId = path[4] as Ref<MasterTag>

View File

@ -0,0 +1,14 @@
<script lang="ts">
import { getClient } from '@hcengineering/presentation'
import { RelationSetting } from '@hcengineering/setting-resources'
import contact from '@hcengineering/contact'
import card from '../../plugin'
const client = getClient()
const hierarchy = client.getHierarchy()
const _classes = [...hierarchy.getDescendants(card.class.Card), contact.class.Contact].filter(
(c) => c !== card.class.Card
)
</script>
<RelationSetting {_classes} exclude={[]} />

View File

@ -13,8 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import card, { MasterTag } from '@hcengineering/card'
import contact from '@hcengineering/contact'
import { MasterTag } from '@hcengineering/card'
import core, { Association, Class, Doc, Ref } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
@ -22,6 +21,7 @@
import { clearSettingsStore, settingsStore } from '@hcengineering/setting-resources'
import { ButtonIcon, Icon, IconAdd, Label, showPopup } from '@hcengineering/ui'
import { onDestroy } from 'svelte'
import CreateRelation from './CreateRelation.svelte'
export let masterTag: MasterTag
@ -49,10 +49,8 @@
}
function addRelation (): void {
showPopup(setting.component.CreateRelation, {
aClass: masterTag._id,
exclude: [],
_classes: [card.class.Card, contact.class.Contact]
showPopup(CreateRelation, {
aClass: masterTag._id
})
}

View File

@ -35,6 +35,7 @@ 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'
import RelationSetting from './components/settings/RelationSetting.svelte'
export default async (): Promise<Resources> => ({
component: {
@ -51,7 +52,8 @@ export default async (): Promise<Resources> => ({
TagsSection,
RelationsSection,
ChildsSection,
SetParentActionPopup
SetParentActionPopup,
RelationSetting
},
completion: {
CardQuery: queryCard

View File

@ -35,7 +35,8 @@ export default mergeIds(cardId, card, {
TagsSection: '' as AnyComponent,
ChildsSection: '' as AnyComponent,
RelationsSection: '' as AnyComponent,
SetParentActionPopup: '' as AnyComponent
SetParentActionPopup: '' as AnyComponent,
RelationSetting: '' as AnyComponent
},
completion: {
CardQuery: '' as Resource<ObjectSearchFactory>,

View File

@ -82,7 +82,9 @@
const clazz = hierarchy.getClass(key)
result.push([
{ id: key, label: clazz.label, icon: clazz.icon },
value.map((it) => ({ id: it._id, label: it.label, icon: it.icon }))
value
.map((it) => ({ id: it._id, label: it.label, icon: it.icon }))
.sort((a, b) => a.label.localeCompare(b.label))
])
} catch {}
}

View File

@ -75,7 +75,9 @@ export {
ClassSetting,
filterDescendants,
SpaceTypeGeneralSectionEditor,
ClassHierarchy
ClassHierarchy,
RelationSetting,
CreateRelation
}
async function DeleteMixin (object: Mixin<Class<Doc>>): Promise<void> {

View File

@ -48,7 +48,7 @@
$: query.query(
view.class.Viewlet,
{
attachTo: _class
attachTo: client.getHierarchy().getBaseClass(_class)
},
(res) => {
viewlet = res[0]

View File

@ -200,54 +200,93 @@ async function OnCardRemove (ctx: TxRemoveDoc<Card>[], control: TriggerControl):
return res
}
async function OnCardParentChange (ctx: TxUpdateDoc<Card>[], control: TriggerControl): Promise<Tx[]> {
async function OnCardUpdate (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 (updateTx.operations.parent !== undefined) {
const newParent = updateTx.operations.parent
const oldParent = doc.parentInfo[doc.parentInfo.length - 1]?._id
if (newParent != null) {
const parent = (await control.findAll(control.ctx, card.class.Card, { _id: newParent }))[0]
if (parent !== undefined) {
if (parent.parentInfo.findIndex((p) => p._id === doc._id) === -1) {
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
}
]
})
)
} else {
// rollback
return [
control.txFactory.createTxUpdateDoc(doc._class, doc.space, doc._id, {
parent: oldParent ?? null
})
]
}
}
}
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
}
]
})
)
}
if (updateTx.operations.title !== undefined) {
res.push(...(await updateParentInfoName(control, doc._id, updateTx.operations.title, doc._id)))
}
return res
}
async function updateParentInfoName (
control: TriggerControl,
parent: Ref<Card>,
title: string,
originParent: Ref<Card>
): Promise<Tx[]> {
const res: Tx[] = []
const childs = await control.findAll(control.ctx, card.class.Card, { parent })
for (const child of childs) {
if (child._id === originParent) continue
const parentInfo = child.parentInfo
const index = parentInfo.findIndex((p) => p._id === parent)
if (index === -1) {
continue
}
parentInfo[index].title = title
res.push(
control.txFactory.createTxUpdateDoc(child._class, child.space, child._id, {
parentInfo
})
)
res.push(...(await updateParentInfoName(control, child._id, title, originParent)))
}
return res
}
async function OnCardCreate (ctx: TxCreateDoc<Card>[], control: TriggerControl): Promise<Tx[]> {
const createTx = ctx[0]
const doc = TxProcessor.createDoc2Doc(createTx)
@ -277,6 +316,6 @@ export default async () => ({
OnMasterTagRemove,
OnCardRemove,
OnCardCreate,
OnCardParentChange
OnCardUpdate
}
})

View File

@ -32,7 +32,7 @@ export default plugin(serverCardId, {
OnMasterTagCreate: '' as Resource<TriggerFunc>,
OnMasterTagRemove: '' as Resource<TriggerFunc>,
OnCardCreate: '' as Resource<TriggerFunc>,
OnCardParentChange: '' as Resource<TriggerFunc>,
OnCardUpdate: '' as Resource<TriggerFunc>,
OnCardRemove: '' as Resource<TriggerFunc>
}
})