diff --git a/plugins/view-resources/src/components/RelationshipTable.svelte b/plugins/view-resources/src/components/RelationshipTable.svelte index 54ace5243e..398877a95f 100644 --- a/plugins/view-resources/src/components/RelationshipTable.svelte +++ b/plugins/view-resources/src/components/RelationshipTable.svelte @@ -49,7 +49,7 @@ import { showMenu } from '../actions' import { canChangeAttribute } from '../permissions' import view from '../plugin' - import { buildConfigAssociation, buildConfigLookup, buildModel, restrictionStore } from '../utils' + import { buildConfigAssociation, buildConfigLookup, buildModel, getAttributeValue, restrictionStore } from '../utils' import { getResultOptions, getResultQuery } from '../viewOptions' import { CopyRelationshipTableAsMarkdown } from '../copyAsMarkdownTable' import IconUpDown from './icons/UpDown.svelte' @@ -229,16 +229,7 @@ } function getValue (attribute: AttributeModel, object: Doc): any { - if (attribute.castRequest) { - return getObjectValue( - attribute.key.substring(attribute.castRequest.length + 1), - client.getHierarchy().as(object, attribute.castRequest) - ) - } - if (attribute.key.startsWith(assoc)) { - return object - } - return getObjectValue(attribute.key, object) + return getAttributeValue(attribute, object, client.getHierarchy()) } function showContextMenu (ev: MouseEvent, object: Doc | undefined): void { diff --git a/plugins/view-resources/src/copyAsMarkdownTable.ts b/plugins/view-resources/src/copyAsMarkdownTable.ts index 61ef80bb7b..f57eecefaa 100644 --- a/plugins/view-resources/src/copyAsMarkdownTable.ts +++ b/plugins/view-resources/src/copyAsMarkdownTable.ts @@ -30,7 +30,7 @@ import { getCurrentLanguage } from '@hcengineering/theme' import viewPlugin, { type Viewlet, type AttributeModel, type BuildModelKey } from '@hcengineering/view' import presentation, { getClient } from '@hcengineering/presentation' import { getName, getPersonByPersonId } from '@hcengineering/contact' -import { buildModel, buildConfigLookup, getObjectLinkFragment } from './utils' +import { buildModel, buildConfigLookup, getAttributeValue, getObjectLinkFragment } from './utils' import view from './plugin' import SimpleNotification from './components/SimpleNotification.svelte' import { copyText } from './actionImpl' @@ -672,70 +672,73 @@ export async function CopyRelationshipTableAsMarkdown ( const attrIndex = attributeKeyToIndex.get(cell.attribute.key) if (attrIndex === undefined) continue - // Get the document object (prefer cell.object, fall back to parentObject) - const doc = cell.object ?? cell.parentObject + // Determine if this is an association column + const isAssociationKey = cell.attribute.key.startsWith('$associations') + + let doc: Doc | undefined + if (isAssociationKey) { + doc = cell.object + } else { + doc = cell.object ?? cell.parentObject + } + if (doc === undefined) { // Empty cell row[attrIndex] = '' continue } - // Handle association keys - they need special treatment - // For association keys, the cell.object is the related document - // and we need to extract the attribute key after the association path - const assoc = '$associations' - let attributeToUse = cell.attribute + // Use the same getValue logic as RelationshipTable + // For association keys, this returns the child document object itself + const rawValue = getAttributeValue(cell.attribute, doc, hierarchy) + + // Determine which document and class to use for formatting let docToUse = doc - let isAssociationKey = false + let docClass = props.cardClass + let attributeToUse = cell.attribute - if (cell.attribute.key.startsWith(assoc)) { - isAssociationKey = true - // For association keys, the object is the related document - docToUse = cell.object ?? doc - - // Extract the part after the association path - // e.g., "$associations.relationName.attributeName" -> "attributeName" - // or "$associations.relationName" -> "" (for the document itself) - const parts = cell.attribute.key.split(assoc) - if (parts.length > 1) { - const afterAssoc = parts[1].substring(1) // Remove leading dot - if (afterAssoc.length > 0) { - // Create a modified attribute with the extracted key - attributeToUse = { - ...cell.attribute, - key: afterAssoc - } - } else { - // Association key points to the document itself (use empty key for title) - attributeToUse = { - ...cell.attribute, - key: '' + if (isAssociationKey) { + // For association keys, the value IS the child document object + if (rawValue !== undefined && rawValue !== null && typeof rawValue === 'object' && '_class' in rawValue) { + docToUse = rawValue as Doc + docClass = docToUse._class + const parts = cell.attribute.key.split('$associations.') + if (parts.length > 1) { + const afterAssoc = parts[1].substring(1) // Remove leading dot + const dotIndex = afterAssoc.indexOf('.') + if (dotIndex > 0) { + const attributeName = afterAssoc.substring(dotIndex + 1) + attributeToUse = { + ...cell.attribute, + key: attributeName + } + } else { + attributeToUse = { + ...cell.attribute, + key: '' + } } } } } - // Format the value + // Format the value using the same logic as regular tables const isFirstColumn = attrIndex === 0 - const docClass = isAssociationKey && docToUse !== undefined ? docToUse._class : props.cardClass + const allowEmptyKey = isFirstColumn || isAssociationKey let value = await formatValue( attributeToUse, docToUse, hierarchy, docClass, language, - isFirstColumn, + allowEmptyKey, // Pass true for association keys so empty key works userCache, props.valueFormatter ) - // If this is the first column with empty key (title attribute), create a markdown link - if ( - isFirstColumn && - (cell.attribute.key === '' || (isAssociationKey && attributeToUse.key === '')) && - cell.object !== undefined - ) { - value = await createMarkdownLink(hierarchy, cell.object, value) + const isDocumentTitle = attributeToUse.key === '' && docToUse !== undefined + if (isDocumentTitle) { + value = await createMarkdownLink(hierarchy, docToUse, value) } else { value = escapeMarkdownLinkText(value) } diff --git a/plugins/view-resources/src/utils.ts b/plugins/view-resources/src/utils.ts index 9a4cc25916..430f24b972 100644 --- a/plugins/view-resources/src/utils.ts +++ b/plugins/view-resources/src/utils.ts @@ -1082,6 +1082,24 @@ export function getCategorySpaces (categories: CategoryType[]): Array ) } +/** + * Get value from a document for a given attribute model + * @public + */ +export function getAttributeValue (attribute: AttributeModel, object: Doc, hierarchy: Hierarchy): any { + const assoc = '$associations' + if (attribute.castRequest !== undefined && attribute.castRequest !== null) { + return getObjectValue( + attribute.key.substring(attribute.castRequest.length + 1), + hierarchy.as(object, attribute.castRequest) + ) + } + if (attribute.key.startsWith(assoc)) { + return object + } + return getObjectValue(attribute.key, object) +} + export function concatCategories (arr1: CategoryType[], arr2: CategoryType[]): CategoryType[] { const uniqueValues = new Set() const uniqueObjects = new Map()