diff --git a/src/Umbraco.Web.UI.Client/.github/README.md b/src/Umbraco.Web.UI.Client/.github/README.md index c8ee810d64..d9f0f72c16 100644 --- a/src/Umbraco.Web.UI.Client/.github/README.md +++ b/src/Umbraco.Web.UI.Client/.github/README.md @@ -2,7 +2,9 @@ This is the working repository of the upcoming new Backoffice to Umbraco CMS. +[![Build and test](https://github.com/umbraco/Umbraco.CMS.Backoffice/actions/workflows/build_test.yml/badge.svg)](https://github.com/umbraco/Umbraco.CMS.Backoffice/actions/workflows/build_test.yml) [![Storybook](https://github.com/umbraco/Umbraco.CMS.Backoffice/actions/workflows/azure-static-web-apps-ambitious-stone-0033b3603.yml/badge.svg)](https://github.com/umbraco/Umbraco.CMS.Backoffice/actions/workflows/azure-static-web-apps-ambitious-stone-0033b3603.yml) +[![SonarCloud](https://sonarcloud.io/api/project_badges/measure?project=umbraco_Umbraco.CMS.Backoffice&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=umbraco_Umbraco.CMS.Backoffice) ## Installation instructions diff --git a/src/Umbraco.Web.UI.Client/sonar-project.properties b/src/Umbraco.Web.UI.Client/sonar-project.properties new file mode 100644 index 0000000000..44998fae7d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/sonar-project.properties @@ -0,0 +1,9 @@ +# Define the same root directory for sources and tests +sonar.sources = src/ +sonar.tests = src/ + +# Include test files in the test scope +sonar.test.inclusions = src/**/*.test.ts + +# Exclude test and stories files from the source scope +sonar.exclusions = src/**/*.test.ts, src/**/*.stories.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.element.ts index cb2b5cbba9..299d8b0405 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.element.ts @@ -279,7 +279,7 @@ export class UmbTableElement extends LitElement { const element = document.createElement('umb-ufm-render') as UmbUfmRenderElement; element.inline = true; element.markdown = column.labelTemplate; - element.value = value; + element.value = { value }; return element; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/ufm-component.model.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/ufm-component.model.ts index 9c3a2afa14..9cf61264f4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/ufm-component.model.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/ufm-component.model.ts @@ -1,8 +1,8 @@ import type { ManifestApi, UmbApi } from '@umbraco-cms/backoffice/extension-api'; -import type { Tokens } from '@umbraco-cms/backoffice/external/marked'; +import type { UfmToken } from '@umbraco-cms/backoffice/ufm'; export interface UmbUfmComponentApi extends UmbApi { - render(token: Tokens.Generic): string | undefined; + render(token: UfmToken): string | undefined; } export interface MetaUfmComponent { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/grid/document-grid-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/grid/document-grid-collection-view.element.ts index d651ea1ce5..5218ca0e54 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/grid/document-grid-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/grid/document-grid-collection-view.element.ts @@ -172,26 +172,26 @@ export class UmbDocumentGridCollectionViewElement extends UmbLitElement { ${repeat( this._userDefinedProperties, (column) => column.alias, - (column) => html` -
  • - ${column.header}: - ${when( - column.nameTemplate, - () => html` - - `, - () => html`${getPropertyValueByAlias(item, column.alias)}`, - )} -
  • - `, + (column) => this.#renderProperty(item, column), )} `; } + #renderProperty(item: UmbDocumentCollectionItemModel, column: UmbCollectionColumnConfiguration) { + const value = getPropertyValueByAlias(item, column.alias); + return html` +
  • + ${column.header}: + ${when( + column.nameTemplate, + () => html``, + () => html`${value}`, + )} +
  • + `; + } + static override styles = [ UmbTextStyles, css` diff --git a/src/Umbraco.Web.UI.Client/src/packages/ufm/components/ufm-render/ufm-render.element.ts b/src/Umbraco.Web.UI.Client/src/packages/ufm/components/ufm-render/ufm-render.element.ts index 65f33c51b4..56173caf27 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/ufm/components/ufm-render/ufm-render.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/ufm/components/ufm-render/ufm-render.element.ts @@ -28,7 +28,7 @@ UmbDomPurify.addHook('afterSanitizeAttributes', function (node) { } }); -const UmbMarked = new Marked({ +export const UmbMarked = new Marked({ async: true, gfm: true, breaks: true, diff --git a/src/Umbraco.Web.UI.Client/src/packages/ufm/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/ufm/manifests.ts index b32b66dddf..ec604e0842 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/ufm/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/ufm/manifests.ts @@ -4,19 +4,15 @@ export const manifests: Array = [ { type: 'ufmComponent', alias: 'Umb.Markdown.LabelValue', - name: 'Label Value Markdown Component', + name: 'Label Value UFM Component', api: () => import('./ufm-components/label-value.component.js'), - meta: { - marker: '=', - }, + meta: { marker: '=' }, }, { type: 'ufmComponent', alias: 'Umb.Markdown.Localize', - name: 'Localize Markdown Component', + name: 'Localize UFM Component', api: () => import('./ufm-components/localize.component.js'), - meta: { - marker: '#', - }, + meta: { marker: '#' }, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/ufm/plugins/marked-ufm.plugin.ts b/src/Umbraco.Web.UI.Client/src/packages/ufm/plugins/marked-ufm.plugin.ts index 9ed749585d..86c17cc4c9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/ufm/plugins/marked-ufm.plugin.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/ufm/plugins/marked-ufm.plugin.ts @@ -3,7 +3,11 @@ import type { MarkedExtension, Tokens } from '@umbraco-cms/backoffice/external/m export interface UfmPlugin { alias: string; marker: string; - render?: (token: Tokens.Generic) => string | undefined; + render?: (token: UfmToken) => string | undefined; +} + +export interface UfmToken extends Tokens.Generic { + text?: string; } export function ufm(plugins: Array = []): MarkedExtension { @@ -12,13 +16,9 @@ export function ufm(plugins: Array = []): MarkedExtension { return { name: alias, level: 'inline', - start: (src: string) => { - const regex = new RegExp(`\\{${marker}`); - const match = src.match(regex); - return match ? match.index : -1; - }, - tokenizer(src: string): Tokens.Generic | undefined { - const pattern = `^(? src.indexOf(`{${marker}`), + tokenizer: (src: string) => { + const pattern = `^\\{${marker}([^}]*)\\}`; const regex = new RegExp(pattern); const match = src.match(regex); diff --git a/src/Umbraco.Web.UI.Client/src/packages/ufm/plugins/marked-ufm.test.ts b/src/Umbraco.Web.UI.Client/src/packages/ufm/plugins/marked-ufm.test.ts new file mode 100644 index 0000000000..ef01944675 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/ufm/plugins/marked-ufm.test.ts @@ -0,0 +1,32 @@ +import { expect } from '@open-wc/testing'; +import { ufm } from './marked-ufm.plugin.js'; +import { UmbMarked } from '../index.js'; +import { UmbUfmLabelValueComponent } from '../ufm-components/label-value.component.js'; +import { UmbUfmLocalizeComponent } from '../ufm-components/localize.component.js'; + +describe('UmbMarkedUfm', () => { + describe('UFM parsing', () => { + const runs = [ + { ufm: '{=prop1}', expected: '' }, + { ufm: '{= prop1}', expected: '' }, + { ufm: '{= prop1 }', expected: '' }, + { ufm: '{{=prop1}}', expected: '{}' }, + { ufm: '{#general_add}', expected: '' }, + ]; + + // Manually configuring the UFM components for testing. + UmbMarked.use( + ufm([ + { alias: 'Umb.Markdown.LabelValue', marker: '=', render: new UmbUfmLabelValueComponent().render }, + { alias: 'Umb.Markdown.Localize', marker: '#', render: new UmbUfmLocalizeComponent().render }, + ]), + ); + + runs.forEach((run) => { + it(`Parsing "${run.ufm}"`, async () => { + const markup = await UmbMarked.parseInline(run.ufm); + expect(markup).to.equal(run.expected); + }); + }); + }); +}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/ufm/ufm-components/label-value.component.ts b/src/Umbraco.Web.UI.Client/src/packages/ufm/ufm-components/label-value.component.ts index 00c58d0027..7fa6b5d815 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/ufm/ufm-components/label-value.component.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/ufm/ufm-components/label-value.component.ts @@ -1,10 +1,11 @@ +import type { UfmToken } from '../plugins/marked-ufm.plugin.js'; import { UmbUfmComponentBase } from './ufm-component-base.js'; -import type { Tokens } from '@umbraco-cms/backoffice/external/marked'; import './label-value.element.js'; export class UmbUfmLabelValueComponent extends UmbUfmComponentBase { - render(token: Tokens.Generic) { + render(token: UfmToken) { + if (!token.text) return; return ``; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/ufm/ufm-components/label-value.element.ts b/src/Umbraco.Web.UI.Client/src/packages/ufm/ufm-components/label-value.element.ts index be54d466fc..cb28e01989 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/ufm/ufm-components/label-value.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/ufm/ufm-components/label-value.element.ts @@ -1,5 +1,5 @@ import { UMB_UFM_RENDER_CONTEXT } from '../components/ufm-render/index.js'; -import { customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { customElement, nothing, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; const elementName = 'ufm-label-value'; @@ -31,7 +31,7 @@ export class UmbUfmLabelValueElement extends UmbLitElement { } override render() { - return this._value ?? `{${this.alias}}`; + return this._value !== undefined ? this._value : nothing; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/ufm/ufm-components/localize.component.ts b/src/Umbraco.Web.UI.Client/src/packages/ufm/ufm-components/localize.component.ts index aacc2cd503..79890f75f5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/ufm/ufm-components/localize.component.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/ufm/ufm-components/localize.component.ts @@ -1,8 +1,9 @@ +import type { UfmToken } from '../plugins/marked-ufm.plugin.js'; import { UmbUfmComponentBase } from './ufm-component-base.js'; -import type { Tokens } from '@umbraco-cms/backoffice/external/marked'; export class UmbUfmLocalizeComponent extends UmbUfmComponentBase { - render(token: Tokens.Generic) { + render(token: UfmToken) { + if (!token.text) return; return ``; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/ufm/ufm-components/ufm-component-base.ts b/src/Umbraco.Web.UI.Client/src/packages/ufm/ufm-components/ufm-component-base.ts index 1109c69e76..71f552363e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/ufm/ufm-components/ufm-component-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/ufm/ufm-components/ufm-component-base.ts @@ -1,7 +1,7 @@ -import type { Tokens } from '@umbraco-cms/backoffice/external/marked'; +import type { UfmToken } from '../plugins/marked-ufm.plugin.js'; import type { UmbUfmComponentApi } from '@umbraco-cms/backoffice/extension-registry'; export abstract class UmbUfmComponentBase implements UmbUfmComponentApi { - abstract render(token: Tokens.Generic): string | undefined; + abstract render(token: UfmToken): string | undefined; destroy() {} }