mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-17 18:05:42 +02:00
Fix tables (#10813)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Generated
+7
-1
@@ -19096,6 +19096,12 @@ importers:
|
||||
'@hcengineering/presentation':
|
||||
specifier: workspace:^0.7.0
|
||||
version: link:../../packages/presentation
|
||||
'@hcengineering/text':
|
||||
specifier: workspace:^0.7.19
|
||||
version: link:../../foundations/core/packages/text
|
||||
'@hcengineering/text-markdown':
|
||||
specifier: workspace:^0.7.21
|
||||
version: link:../../foundations/core/packages/text-markdown
|
||||
'@hcengineering/theme':
|
||||
specifier: workspace:^0.7.0
|
||||
version: link:../../packages/theme
|
||||
@@ -61587,7 +61593,7 @@ snapshots:
|
||||
node-loader@2.0.0(webpack@5.102.1):
|
||||
dependencies:
|
||||
loader-utils: 2.0.4
|
||||
webpack: 5.102.1
|
||||
webpack: 5.102.1(esbuild@0.25.12)(webpack-cli@5.1.4)
|
||||
|
||||
node-localstorage@2.2.1:
|
||||
dependencies:
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
function updateKeys (_class: Ref<Class<Doc>>, to: Ref<Class<Doc>> | undefined): void {
|
||||
console.log('tag', _class, to)
|
||||
const filtredKeys = [...hierarchy.getAllAttributes(_class, to).entries()]
|
||||
.filter(([key, value]) => value.hidden !== true && value.type._class === core.class.TypeMarkup)
|
||||
.map(([key, attr]) => ({ key, attr }))
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
<script lang="ts">
|
||||
import { Card } from '@hcengineering/card'
|
||||
import { Doc, Mixin } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { getDocMixins } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import MarkupProperties from '../MarkupProperties.svelte'
|
||||
@@ -25,6 +26,8 @@
|
||||
export let hidden: boolean = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
const h = client.getHierarchy()
|
||||
|
||||
let mixins: Array<Mixin<Doc>> = []
|
||||
$: mixins = getDocMixins(doc)
|
||||
@@ -35,9 +38,14 @@
|
||||
|
||||
{#if !hidden}
|
||||
<div class="w-full flex flex-col flex-gap-4">
|
||||
<MarkupProperties {doc} {readonly} tag={undefined} on:update />
|
||||
<MarkupProperties
|
||||
{doc}
|
||||
readonly={readonly || h.getAncestors(doc._class).some((p) => doc.readonlySections?.includes(p))}
|
||||
tag={undefined}
|
||||
on:update
|
||||
/>
|
||||
{#each mixins as mixin}
|
||||
<MarkupProperties {doc} {readonly} tag={mixin} on:update />
|
||||
<MarkupProperties {doc} readonly={readonly || doc.readonlySections?.includes(mixin._id)} tag={mixin} on:update />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -50,6 +50,8 @@
|
||||
"@hcengineering/theme": "workspace:^0.7.0",
|
||||
"@hcengineering/contact": "workspace:^0.7.0",
|
||||
"@hcengineering/view-resources": "workspace:^0.7.0",
|
||||
"@hcengineering/text": "workspace:^0.7.19",
|
||||
"@hcengineering/text-markdown": "workspace:^0.7.21",
|
||||
"svelte": "^4.2.20"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,15 @@
|
||||
|
||||
import { isIntlString, extractObjectTitleOrName } from '../formatter/utils'
|
||||
|
||||
jest.mock('@hcengineering/presentation', () => ({
|
||||
getClient: jest.fn(() => ({
|
||||
getModel: jest.fn(() => ({
|
||||
findObject: jest.fn()
|
||||
})),
|
||||
getHierarchy: jest.fn()
|
||||
}))
|
||||
}))
|
||||
|
||||
jest.mock('@hcengineering/platform', () => {
|
||||
const actual = jest.requireActual('@hcengineering/platform')
|
||||
return {
|
||||
|
||||
@@ -16,6 +16,15 @@
|
||||
import type { AttributeModel } from '@hcengineering/view'
|
||||
import { modelToConfig } from '../model/tableModel'
|
||||
|
||||
jest.mock('@hcengineering/presentation', () => ({
|
||||
getClient: jest.fn(() => ({
|
||||
getModel: jest.fn(() => ({
|
||||
findObject: jest.fn()
|
||||
})),
|
||||
getHierarchy: jest.fn()
|
||||
}))
|
||||
}))
|
||||
|
||||
jest.mock('@hcengineering/view-resources', () => ({
|
||||
buildModel: jest.fn(),
|
||||
buildConfigLookup: jest.fn()
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Hierarchy, PersonId } from '@hcengineering/core'
|
||||
import { getName, getPersonByPersonId, getPersonByPersonRef } from '@hcengineering/contact'
|
||||
import { type Doc, type Hierarchy, type PersonId, type Ref } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { getName, getPersonByPersonId } from '@hcengineering/contact'
|
||||
|
||||
/**
|
||||
* Load person display name by PersonId with optional caching
|
||||
@@ -48,3 +48,35 @@ export async function loadPersonName (
|
||||
|
||||
return personId
|
||||
}
|
||||
|
||||
/**
|
||||
* Load person display name by Person Ref (Person document ID)
|
||||
*/
|
||||
export async function loadPersonNameByRef (
|
||||
personRef: Ref<Doc>,
|
||||
hierarchy: Hierarchy,
|
||||
userCache?: Map<string, string>
|
||||
): Promise<string> {
|
||||
if (userCache !== undefined) {
|
||||
const cachedName = userCache.get(personRef)
|
||||
if (cachedName !== undefined) {
|
||||
return cachedName
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const client = getClient()
|
||||
const person = await getPersonByPersonRef(client, personRef as any)
|
||||
if (person !== null) {
|
||||
const name = getName(hierarchy, person)
|
||||
if (userCache !== undefined) {
|
||||
userCache.set(personRef, name)
|
||||
}
|
||||
return name
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to lookup user name for Person Ref:', personRef, error)
|
||||
}
|
||||
|
||||
return personRef
|
||||
}
|
||||
|
||||
@@ -13,8 +13,20 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, { type AnyAttribute, type Class, type Doc, type Ref } from '@hcengineering/core'
|
||||
import contact from '@hcengineering/contact'
|
||||
import core, {
|
||||
getDisplayTime,
|
||||
type AnyAttribute,
|
||||
type Class,
|
||||
type Doc,
|
||||
type Hierarchy,
|
||||
type PersonId,
|
||||
type Ref
|
||||
} from '@hcengineering/core'
|
||||
import { translate, type IntlString } from '@hcengineering/platform'
|
||||
import { markupToJSON } from '@hcengineering/text'
|
||||
import { markupToMarkdown } from '@hcengineering/text-markdown'
|
||||
import { loadPersonNameByRef } from '../data/personLoader'
|
||||
|
||||
export enum DocumentAttributeKey {
|
||||
CreatedBy = 'createdBy',
|
||||
@@ -30,11 +42,114 @@ export enum DateFormatOption {
|
||||
Short = 'short'
|
||||
}
|
||||
|
||||
export function formatDateValue (
|
||||
value: number | string | Date,
|
||||
isDateOnly: boolean,
|
||||
language: string | undefined
|
||||
): string | undefined {
|
||||
if (!isDateOnly && typeof value === 'number') {
|
||||
return getDisplayTime(value)
|
||||
}
|
||||
|
||||
const parsedDate = value instanceof Date ? value : new Date(value)
|
||||
if (Number.isNaN(parsedDate.getTime())) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
year: DateFormatOption.Numeric,
|
||||
month: DateFormatOption.Short,
|
||||
day: DateFormatOption.Numeric
|
||||
}
|
||||
|
||||
return parsedDate.toLocaleDateString(language ?? 'default', options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a value is an IntlString id ({@link Id}: {@code plugin:resourceKind:key}) or
|
||||
* {@link getEmbeddedLabel} output ({@code embedded:embedded:...}).
|
||||
*
|
||||
* Format a single value of any supported type.
|
||||
*/
|
||||
export async function formatSingleValue (
|
||||
value: any,
|
||||
attrType: any,
|
||||
hierarchy: Hierarchy,
|
||||
language: string | undefined,
|
||||
userCache?: Map<PersonId, string>,
|
||||
elementFormatter?: (doc: Doc, title: string) => Promise<string>
|
||||
): Promise<string> {
|
||||
if (value === null || value === undefined) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (
|
||||
typeof value === 'number' &&
|
||||
(attrType?._class === core.class.TypeTimestamp || attrType?._class === core.class.TypeDate)
|
||||
) {
|
||||
return formatDateValue(value, attrType?._class === core.class.TypeDate, language) ?? ''
|
||||
}
|
||||
|
||||
if (value instanceof Date) {
|
||||
return formatDateValue(value, true, language) ?? ''
|
||||
}
|
||||
|
||||
if (
|
||||
typeof value === 'string' &&
|
||||
(attrType?._class === core.class.TypeTimestamp || attrType?._class === core.class.TypeDate)
|
||||
) {
|
||||
return formatDateValue(value, attrType?._class === core.class.TypeDate, language) ?? ''
|
||||
}
|
||||
|
||||
const isMarkup =
|
||||
attrType?._class === core.class.TypeMarkup ||
|
||||
attrType?._class === core.class.TypeCollaborativeDoc ||
|
||||
(typeof value === 'object' && value !== null && (value.type === 'doc' || value._class === 'core:class:Markup'))
|
||||
|
||||
if (isMarkup) {
|
||||
try {
|
||||
return markupToMarkdown(markupToJSON(value))
|
||||
} catch (e) {
|
||||
// fallback
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if ('title' in value || 'name' in value) {
|
||||
const title = value.title ?? value.name ?? ''
|
||||
const text =
|
||||
typeof title === 'string' && isIntlString(title)
|
||||
? await translate(title as unknown as IntlString, {}, language)
|
||||
: String(title)
|
||||
if (elementFormatter !== undefined) {
|
||||
return await elementFormatter(value as Doc, text)
|
||||
}
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof value === 'boolean') {
|
||||
return value ? '✅ Yes' : '❌ No'
|
||||
}
|
||||
|
||||
if (typeof value === 'number') {
|
||||
return String(value)
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
if (isIntlString(value)) {
|
||||
return await translate(value as unknown as IntlString, {}, language)
|
||||
}
|
||||
|
||||
const isRef = attrType?._class === core.class.RefTo
|
||||
if (isRef) {
|
||||
if (attrType.to !== undefined && hierarchy.isDerived(attrType.to, contact.mixin.Employee)) {
|
||||
const name = await loadPersonNameByRef(value as any, hierarchy, userCache as any)
|
||||
return name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return String(value)
|
||||
}
|
||||
|
||||
export function isIntlString (value: unknown): value is string {
|
||||
if (typeof value !== 'string' || value.length === 0) {
|
||||
return false
|
||||
@@ -60,60 +175,46 @@ export function isIntlString (value: unknown): value is string {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an array of values, handling reference lookups if needed
|
||||
*/
|
||||
export async function formatArrayValue (
|
||||
value: any[],
|
||||
attrType: any,
|
||||
attribute: AnyAttribute | undefined,
|
||||
attrKey: string,
|
||||
card: Doc,
|
||||
language: string | undefined
|
||||
hierarchy: Hierarchy,
|
||||
language: string | undefined,
|
||||
userCache?: Map<PersonId, string>,
|
||||
elementFormatter?: (doc: Doc, title: string) => Promise<string>
|
||||
): Promise<string> {
|
||||
const isRefArray =
|
||||
attrType?._class === core.class.ArrOf &&
|
||||
(attrType as { of?: { _class?: Ref<Class<Doc>> } })?.of?._class === core.class.RefTo
|
||||
const isRef =
|
||||
attrType?._class === core.class.RefTo ||
|
||||
(attrType?._class === core.class.ArrOf &&
|
||||
(attrType as { of?: { _class?: Ref<Class<Doc>> } })?.of?._class === core.class.RefTo)
|
||||
|
||||
if (isRefArray && (attribute !== undefined || attrKey !== '')) {
|
||||
const cardWithLookup = card as any
|
||||
const lookupKey = attribute?.name ?? attrKey
|
||||
const lookupData = cardWithLookup.$lookup?.[lookupKey]
|
||||
const cardWithLookup = card as any
|
||||
const lookupKey = attribute?.name ?? attrKey
|
||||
const lookupData = cardWithLookup.$lookup?.[lookupKey]
|
||||
|
||||
if (lookupData !== undefined && lookupData !== null) {
|
||||
const resolveItem = async (v: any, index: number): Promise<string> => {
|
||||
// If we have lookup data and v is an ID, find the object
|
||||
let item = v
|
||||
if (isRef && lookupData !== undefined && typeof v === 'string') {
|
||||
const resolvedArray = Array.isArray(lookupData) ? lookupData : [lookupData]
|
||||
const translatedValues = await Promise.all(
|
||||
resolvedArray.map(async (v) => {
|
||||
if (typeof v === 'object' && v !== null && 'title' in v) {
|
||||
const title = v.title ?? ''
|
||||
if (typeof title === 'string' && isIntlString(title)) {
|
||||
return await translate(title as unknown as IntlString, {}, language)
|
||||
}
|
||||
return String(title)
|
||||
}
|
||||
return typeof v === 'string' ? v : String(v)
|
||||
})
|
||||
)
|
||||
return translatedValues.join(', ')
|
||||
const found = resolvedArray.find((obj) => obj._id === v)
|
||||
if (found !== undefined) {
|
||||
item = found
|
||||
} else if (resolvedArray[index] !== undefined) {
|
||||
// Fallback to index-based lookup if no _id matches (useful for tests or simplified data)
|
||||
item = resolvedArray[index]
|
||||
}
|
||||
}
|
||||
|
||||
const itemType = attrType?._class === core.class.ArrOf ? attrType.of : attrType
|
||||
return await formatSingleValue(item, itemType, hierarchy, language, userCache, elementFormatter)
|
||||
}
|
||||
|
||||
const translatedValues = await Promise.all(
|
||||
value.map(async (v) => {
|
||||
if (typeof v === 'object' && v !== null && 'title' in v) {
|
||||
const title = v.title ?? ''
|
||||
if (typeof title === 'string' && isIntlString(title)) {
|
||||
return await translate(title as unknown as IntlString, {}, language)
|
||||
}
|
||||
return String(title)
|
||||
}
|
||||
if (typeof v === 'string' && isIntlString(v)) {
|
||||
return await translate(v as unknown as IntlString, {}, language)
|
||||
}
|
||||
return typeof v === 'string' ? v : String(v)
|
||||
})
|
||||
)
|
||||
return translatedValues.join(', ')
|
||||
const formattedValues = await Promise.all(value.map(async (v, i) => await resolveItem(v, i)))
|
||||
return formattedValues.filter((v) => v !== '').join(', ')
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,6 +224,9 @@ export async function extractObjectTitleOrName (
|
||||
obj: Record<string, any>,
|
||||
language: string | undefined
|
||||
): Promise<string> {
|
||||
if (obj._class === core.class.TypeMarkup || obj._class === core.class.TypeCollaborativeDoc) {
|
||||
return '' // Should be handled by markupToMarkdown
|
||||
}
|
||||
if ('title' in obj) {
|
||||
const title = String(obj.title ?? '')
|
||||
if (isIntlString(title)) {
|
||||
|
||||
@@ -13,32 +13,25 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import converter from '@hcengineering/converter'
|
||||
import core, {
|
||||
type AnyAttribute,
|
||||
type Association,
|
||||
type Class,
|
||||
type Doc,
|
||||
type Hierarchy,
|
||||
type Ref,
|
||||
type PersonId,
|
||||
type Association,
|
||||
getDisplayTime,
|
||||
type Ref,
|
||||
getObjectValue
|
||||
} from '@hcengineering/core'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { translate, type IntlString, getResource } from '@hcengineering/platform'
|
||||
import type { AttributeModel } from '@hcengineering/view'
|
||||
import converter from '@hcengineering/converter'
|
||||
import { getFormattersForClass } from './registry'
|
||||
import {
|
||||
formatArrayValue,
|
||||
extractObjectTitleOrName,
|
||||
isIntlString,
|
||||
DocumentAttributeKey,
|
||||
DateFormatOption
|
||||
} from './utils'
|
||||
import { createMarkdownLink } from '../markdown/link'
|
||||
import { loadPersonName } from '../data/personLoader'
|
||||
import { createMarkdownLink } from '../markdown/link'
|
||||
import type { ValueFormatter } from '../types'
|
||||
import { getFormattersForClass } from './registry'
|
||||
import { DocumentAttributeKey, extractObjectTitleOrName, formatArrayValue, formatSingleValue } from './utils'
|
||||
|
||||
/** Resolved context for formatting: which object we display and its value */
|
||||
export interface DisplayContext {
|
||||
@@ -92,29 +85,6 @@ function getLookupData (card: Doc, ...keys: string[]): any {
|
||||
return undefined
|
||||
}
|
||||
|
||||
function formatDateValue (
|
||||
value: number | string | Date,
|
||||
isDateOnly: boolean,
|
||||
language: string | undefined
|
||||
): string | undefined {
|
||||
if (!isDateOnly && typeof value === 'number') {
|
||||
return getDisplayTime(value)
|
||||
}
|
||||
|
||||
const parsedDate = value instanceof Date ? value : new Date(value)
|
||||
if (Number.isNaN(parsedDate.getTime())) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
year: DateFormatOption.Numeric,
|
||||
month: DateFormatOption.Short,
|
||||
day: DateFormatOption.Numeric
|
||||
}
|
||||
|
||||
return parsedDate.toLocaleDateString(language ?? 'default', options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve which object should be displayed (card, ref, or custom attribute) and its value.
|
||||
* Used so formatters and fallbacks can format all types consistently.
|
||||
@@ -238,7 +208,8 @@ function resolveDisplayContext (
|
||||
}
|
||||
|
||||
const attributeKey = getAttributeKey(attr)
|
||||
const attribute = attr.attribute ?? hierarchy.findAttribute(displayClass, attributeKey)
|
||||
const attribute = hierarchy.findAttribute(displayClass, attributeKey) ?? (attr as any).attribute
|
||||
|
||||
const lookupKey = attribute?.name ?? attributeKey
|
||||
return { value, displayDoc, displayClass, attribute, lookupKey }
|
||||
}
|
||||
@@ -260,68 +231,28 @@ export async function formatCustomAttributeValue (
|
||||
|
||||
const attrType = attribute?.type
|
||||
|
||||
if (
|
||||
typeof value === 'number' &&
|
||||
(attrType?._class === core.class.TypeTimestamp || attrType?._class === core.class.TypeDate)
|
||||
) {
|
||||
const formattedDate = formatDateValue(value, attrType?._class === core.class.TypeDate, language)
|
||||
if (formattedDate !== undefined) {
|
||||
return formattedDate
|
||||
}
|
||||
}
|
||||
|
||||
if (value instanceof Date) {
|
||||
return formatDateValue(value, true, language) ?? ''
|
||||
}
|
||||
|
||||
if (
|
||||
typeof value === 'string' &&
|
||||
(attrType?._class === core.class.TypeTimestamp || attrType?._class === core.class.TypeDate)
|
||||
) {
|
||||
const formattedDate = formatDateValue(value, attrType?._class === core.class.TypeDate, language)
|
||||
if (formattedDate !== undefined) {
|
||||
return formattedDate
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof value === 'number' || typeof value === 'boolean') {
|
||||
return String(value)
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
if (isIntlString(value)) {
|
||||
try {
|
||||
return await translate(value as unknown as IntlString, {}, language)
|
||||
} catch {
|
||||
console.warn('Failed to translate intl string', value)
|
||||
}
|
||||
}
|
||||
|
||||
const isRef = attrType?._class === core.class.RefTo
|
||||
if (isRef && attribute !== undefined) {
|
||||
const cardWithLookup = card as any
|
||||
const lookupData = cardWithLookup.$lookup?.[attribute.name]
|
||||
if (lookupData !== undefined && lookupData !== null && typeof lookupData === 'object') {
|
||||
const title = await extractObjectTitleOrName(lookupData as Doc, language)
|
||||
const text = title !== '' ? title : value
|
||||
return await createMarkdownLink(hierarchy, lookupData as Doc, text)
|
||||
}
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return await formatArrayValue(value, attrType, attribute, attribute?.name ?? '', card, language)
|
||||
return await formatArrayValue(
|
||||
value,
|
||||
attrType,
|
||||
attribute,
|
||||
attribute?.name ?? '',
|
||||
card,
|
||||
hierarchy,
|
||||
language,
|
||||
undefined,
|
||||
async (d, title) => await createMarkdownLink(hierarchy, d, title)
|
||||
)
|
||||
}
|
||||
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
const obj = value as Record<string, any>
|
||||
const titleOrName = await extractObjectTitleOrName(obj, language)
|
||||
return titleOrName !== '' ? titleOrName : String(value)
|
||||
}
|
||||
|
||||
return String(value)
|
||||
return await formatSingleValue(
|
||||
value,
|
||||
attrType,
|
||||
hierarchy,
|
||||
language,
|
||||
undefined,
|
||||
async (d, title) => await createMarkdownLink(hierarchy, d, title)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -350,69 +281,42 @@ async function formatValueFallback (
|
||||
const attribute = ctx.attribute
|
||||
const attrType = attribute?.type
|
||||
|
||||
if (
|
||||
typeof value === 'number' &&
|
||||
(attrType?._class === core.class.TypeTimestamp || attrType?._class === core.class.TypeDate)
|
||||
) {
|
||||
const formattedDate = formatDateValue(value, attrType?._class === core.class.TypeDate, language)
|
||||
if (formattedDate !== undefined) {
|
||||
return formattedDate
|
||||
}
|
||||
}
|
||||
|
||||
if (value instanceof Date) {
|
||||
return formatDateValue(value, true, language) ?? ''
|
||||
}
|
||||
|
||||
if (
|
||||
typeof value === 'string' &&
|
||||
(attrType?._class === core.class.TypeTimestamp || attrType?._class === core.class.TypeDate)
|
||||
) {
|
||||
const formattedDate = formatDateValue(value, attrType?._class === core.class.TypeDate, language)
|
||||
if (formattedDate !== undefined) {
|
||||
return formattedDate
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof value === 'number' || typeof value === 'boolean') {
|
||||
return String(value)
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
const isRef = attrType?._class === core.class.RefTo
|
||||
if (isRef) {
|
||||
const lookupData = getLookupData(card, ctx.lookupKey, attribute?.name ?? '', attr.key)
|
||||
if (lookupData !== undefined && lookupData !== null && typeof lookupData === 'object') {
|
||||
const title = await extractObjectTitleOrName(lookupData as Doc, language)
|
||||
const text = title !== '' ? title : value
|
||||
return await createMarkdownLink(hierarchy, lookupData as Doc, text)
|
||||
}
|
||||
}
|
||||
|
||||
if (isIntlString(value)) {
|
||||
try {
|
||||
return await translate(value as unknown as IntlString, {}, language)
|
||||
} catch {
|
||||
console.warn('Failed to translate intl string', value)
|
||||
}
|
||||
}
|
||||
if (attr.key === DocumentAttributeKey.CreatedBy || attr.key === DocumentAttributeKey.ModifiedBy) {
|
||||
return await loadPersonName(value as PersonId, hierarchy, userCache)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return await formatArrayValue(value, attrType, attribute, ctx.lookupKey, card, language)
|
||||
return await formatArrayValue(
|
||||
value,
|
||||
attrType,
|
||||
attribute,
|
||||
ctx.lookupKey,
|
||||
card,
|
||||
hierarchy,
|
||||
language,
|
||||
userCache,
|
||||
async (d, title) => await createMarkdownLink(hierarchy, d, title)
|
||||
)
|
||||
}
|
||||
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
const obj = value as Record<string, any>
|
||||
const titleOrName = await extractObjectTitleOrName(obj, language)
|
||||
return titleOrName !== '' ? titleOrName : String(value)
|
||||
if (attr.key === DocumentAttributeKey.CreatedBy || attr.key === DocumentAttributeKey.ModifiedBy) {
|
||||
return await loadPersonName(value as PersonId, hierarchy, userCache)
|
||||
}
|
||||
|
||||
return String(value)
|
||||
const isRef = attrType?._class === core.class.RefTo
|
||||
if (isRef) {
|
||||
const lookupData = getLookupData(card, ctx.lookupKey, attribute?.name ?? '', attr.key)
|
||||
if (lookupData !== undefined && lookupData !== null && typeof lookupData === 'object') {
|
||||
const title = await extractObjectTitleOrName(lookupData as Doc, language)
|
||||
const text = title !== '' ? title : value
|
||||
return await createMarkdownLink(hierarchy, lookupData as Doc, text)
|
||||
}
|
||||
}
|
||||
|
||||
return await formatSingleValue(
|
||||
value,
|
||||
attrType,
|
||||
hierarchy,
|
||||
language,
|
||||
userCache,
|
||||
async (d, title) => await createMarkdownLink(hierarchy, d, title)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import { Button, eventToHTMLElement, Label, showPopup } from '@hcengineering/ui'
|
||||
import MarkupEditorPopup from './MarkupEditorPopup.svelte'
|
||||
import { StyledTextBox } from '@hcengineering/text-editor-resources'
|
||||
import type { EditorKitOptions } from '@hcengineering/text-editor-resources'
|
||||
import textEditorPlugin from '@hcengineering/text-editor'
|
||||
|
||||
// export let label: IntlString
|
||||
@@ -31,6 +32,7 @@
|
||||
// export let size: ButtonSize = 'x-large'
|
||||
export let justify: 'left' | 'center' = 'center'
|
||||
export let width: string | undefined = 'fit-content'
|
||||
export let kitOptions: Partial<EditorKitOptions> = { reference: true, emoji: true }
|
||||
|
||||
let shown: boolean = false
|
||||
</script>
|
||||
@@ -44,7 +46,7 @@
|
||||
height={value ? 'auto' : undefined}
|
||||
on:click={(ev) => {
|
||||
if (!shown && !readonly) {
|
||||
showPopup(MarkupEditorPopup, { value }, eventToHTMLElement(ev), (res) => {
|
||||
showPopup(MarkupEditorPopup, { value, kitOptions }, eventToHTMLElement(ev), (res) => {
|
||||
if (res != null) {
|
||||
value = res
|
||||
onChange(value)
|
||||
@@ -69,6 +71,7 @@
|
||||
content={value}
|
||||
{placeholder}
|
||||
alwaysEdit
|
||||
{kitOptions}
|
||||
mode={2}
|
||||
on:value={(e) => {
|
||||
onChange(e.detail)
|
||||
|
||||
@@ -16,10 +16,12 @@
|
||||
<script lang="ts">
|
||||
import { Card } from '@hcengineering/presentation'
|
||||
import { StyledTextBox } from '@hcengineering/text-editor-resources'
|
||||
import type { EditorKitOptions } from '@hcengineering/text-editor-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import view from '../plugin'
|
||||
|
||||
export let value: string
|
||||
export let kitOptions: Partial<EditorKitOptions> = { reference: true, emoji: true }
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
export let maxHeight: string = '40vh'
|
||||
@@ -43,6 +45,15 @@
|
||||
on:changeContent
|
||||
>
|
||||
<div class="flex-grow mt-4">
|
||||
<StyledTextBox autofocus content={value} alwaysEdit mode={2} hideExtraButtons {maxHeight} on:value={checkValue} />
|
||||
<StyledTextBox
|
||||
autofocus
|
||||
content={value}
|
||||
alwaysEdit
|
||||
{kitOptions}
|
||||
mode={2}
|
||||
hideExtraButtons
|
||||
{maxHeight}
|
||||
on:value={checkValue}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user