diff --git a/plugins/view-resources/src/copyAsMarkdownTable.ts b/plugins/view-resources/src/copyAsMarkdownTable.ts index b92772fdb7..5bdede0749 100644 --- a/plugins/view-resources/src/copyAsMarkdownTable.ts +++ b/plugins/view-resources/src/copyAsMarkdownTable.ts @@ -19,6 +19,7 @@ import core, { type Doc, type Hierarchy, type Ref, + type PersonId, concatLink, getDisplayTime, getObjectValue @@ -28,14 +29,12 @@ import { addNotification, NotificationSeverity, locationToUrl } from '@hcenginee 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 view from './plugin' import SimpleNotification from './components/SimpleNotification.svelte' import { copyText } from './actionImpl' -/** - * Standard document attribute keys - */ enum DocumentAttributeKey { CreatedBy = 'createdBy', CreatedOn = 'createdOn', @@ -45,9 +44,6 @@ enum DocumentAttributeKey { Name = 'name' } -/** - * Date format options for Intl.DateTimeFormat - */ enum DateFormatOption { Numeric = 'numeric', Short = 'short' @@ -124,13 +120,43 @@ export function isIntlString (value: string): boolean { return parts.length >= 3 && parts.every((part) => part.length > 0) } +async function loadPersonName ( + personId: PersonId, + hierarchy: Hierarchy, + userCache?: Map +): Promise { + if (userCache !== undefined) { + const cachedName = userCache.get(personId) + if (cachedName !== undefined) { + return cachedName + } + } + + try { + const client = getClient() + const person = await getPersonByPersonId(client, personId) + if (person !== null) { + const name = getName(hierarchy, person) + if (userCache !== undefined) { + userCache.set(personId, name) + } + return name + } + } catch (error) { + console.warn('Failed to lookup user name for PersonId:', personId, error) + } + + return personId +} + async function formatValue ( attr: AttributeModel, card: Doc, hierarchy: Hierarchy, _class: Ref>, language: string | undefined, - isFirstColumn: boolean = false + isFirstColumn: boolean = false, + userCache?: Map ): Promise { let value: any if (attr.castRequest != null) { @@ -173,6 +199,9 @@ async function formatValue ( if (isIntlString(value)) { return await translate(value as unknown as IntlString, {}, language) } + if (attr.key === DocumentAttributeKey.CreatedBy || attr.key === DocumentAttributeKey.ModifiedBy) { + return await loadPersonName(value as PersonId, hierarchy, userCache) + } return value } @@ -310,6 +339,9 @@ export async function CopyAsMarkdownTable ( const language = getCurrentLanguage() + // Cache for user ID (PersonId) -> name mappings to reduce database calls + const userCache = new Map() + const headers: string[] = [] for (const attr of displayableModel) { let label: string @@ -329,7 +361,7 @@ export async function CopyAsMarkdownTable ( for (let i = 0; i < displayableModel.length; i++) { const attr = displayableModel[i] const isFirstColumn = i === 0 - const value = await formatValue(attr, card, hierarchy, props.cardClass, language, isFirstColumn) + const value = await formatValue(attr, card, hierarchy, props.cardClass, language, isFirstColumn, userCache) // If this is the first column with empty key (title attribute), create a markdown link if (isFirstColumn && attr.key === '') {