Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: UFM component filters #2246

Merged
merged 13 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/packages/core/extension-registry/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import type { ManifestTinyMcePlugin } from './tinymce-plugin.model.js';
import type { ManifestTree } from './tree.model.js';
import type { ManifestTreeItem } from './tree-item.model.js';
import type { ManifestUfmComponent } from './ufm-component.model.js';
import type { ManifestUfmFilter } from './ufm-filter.model.js';
import type { ManifestUserProfileApp } from './user-profile-app.model.js';
import type { ManifestWorkspace, ManifestWorkspaceRoutableKind } from './workspace.model.js';
import type { ManifestWorkspaceAction, ManifestWorkspaceActionDefaultKind } from './workspace-action.model.js';
Expand Down Expand Up @@ -117,6 +118,7 @@ export type * from './tinymce-plugin.model.js';
export type * from './tree-item.model.js';
export type * from './tree.model.js';
export type * from './ufm-component.model.js';
export type * from './ufm-filter.model.js';
export type * from './user-granular-permission.model.js';
export type * from './user-profile-app.model.js';
export type * from './workspace-action-menu-item.model.js';
Expand Down Expand Up @@ -210,6 +212,7 @@ export type ManifestTypes =
| ManifestTreeItem
| ManifestTreeStore
| ManifestUfmComponent
| ManifestUfmFilter
| ManifestUserProfileApp
| ManifestWorkspaceActionMenuItem
| ManifestWorkspaceActions
Expand Down
14 changes: 14 additions & 0 deletions src/packages/core/extension-registry/models/ufm-filter.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { ManifestApi, UmbApi } from '@umbraco-cms/backoffice/extension-api';

export interface UmbUfmFilterApi extends UmbApi {
filter(...args: Array<unknown>): string | undefined | null;
}

export interface MetaUfmFilter {
alias: string;
}

export interface ManifestUfmFilter extends ManifestApi<UmbUfmFilterApi> {
type: 'ufmFilter';
meta: MetaUfmFilter;
}
15 changes: 15 additions & 0 deletions src/packages/ufm/components/content-name/content-name.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { UfmToken } from '../../plugins/marked-ufm.plugin.js';
import { UmbUfmComponentBase } from '../ufm-component-base.js';

import './content-name.element.js';

export class UmbUfmContentNameComponent extends UmbUfmComponentBase {
render(token: UfmToken) {
if (!token.text) return;

const attributes = super.getAttributes(token.text);
return `<ufm-content-name ${attributes}></ufm-content-name>`;
}
}

export { UmbUfmContentNameComponent as api };
74 changes: 74 additions & 0 deletions src/packages/ufm/components/content-name/content-name.element.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { UmbUfmElementBase } from '../ufm-element-base.js';
import { UMB_UFM_RENDER_CONTEXT } from '../ufm-render/ufm-render.context.js';
import { customElement, property } from '@umbraco-cms/backoffice/external/lit';
import { UmbDocumentItemRepository } from '@umbraco-cms/backoffice/document';
import { UmbMediaItemRepository } from '@umbraco-cms/backoffice/media';
import { UmbMemberItemRepository } from '@umbraco-cms/backoffice/member';

const elementName = 'ufm-content-name';

@customElement(elementName)
export class UmbUfmContentNameElement extends UmbUfmElementBase {
@property()
alias?: string;

#documentRepository?: UmbDocumentItemRepository;
#mediaRepository?: UmbMediaItemRepository;
#memberRepository?: UmbMemberItemRepository;

constructor() {
super();

this.consumeContext(UMB_UFM_RENDER_CONTEXT, (context) => {
this.observe(
context.value,
async (value) => {
const temp =
this.alias && typeof value === 'object'
? ((value as Record<string, unknown>)[this.alias] as string)
: (value as string);

const entityType = Array.isArray(temp) && temp.length > 0 ? temp[0].type : null;
const uniques = Array.isArray(temp) ? temp.map((x) => x.unique) : temp ? [temp] : [];

if (uniques?.length) {
const repository = this.#getRepository(entityType);
if (repository) {
const { data } = await repository.requestItems(uniques);
this.value = data ? data.map((item) => item.name).join(', ') : '';
return;
}
}

this.value = '';
},
'observeValue',
);
});
}

#getRepository(entityType?: string | null) {
switch (entityType) {
case 'media':
if (!this.#mediaRepository) this.#mediaRepository = new UmbMediaItemRepository(this);
return this.#mediaRepository;

case 'member':
if (!this.#memberRepository) this.#memberRepository = new UmbMemberItemRepository(this);
return this.#memberRepository;

case 'document':
default:
if (!this.#documentRepository) this.#documentRepository = new UmbDocumentItemRepository(this);
return this.#documentRepository;
}
}
}

export { UmbUfmContentNameElement as element };

declare global {
interface HTMLElementTagNameMap {
[elementName]: UmbUfmContentNameElement;
}
}
3 changes: 3 additions & 0 deletions src/packages/ufm/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './ufm-render/index.js';
export * from './ufm-component-base.js';
export * from './ufm-element-base.js';
15 changes: 15 additions & 0 deletions src/packages/ufm/components/label-value/label-value.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { UfmToken } from '../../plugins/marked-ufm.plugin.js';
import { UmbUfmComponentBase } from '../ufm-component-base.js';

import './label-value.element.js';

export class UmbUfmLabelValueComponent extends UmbUfmComponentBase {
render(token: UfmToken) {
if (!token.text) return;

const attributes = super.getAttributes(token.text);
return `<ufm-label-value ${attributes}></ufm-label-value>`;
}
}

export { UmbUfmLabelValueComponent as api };
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import { UMB_UFM_RENDER_CONTEXT } from '../components/ufm-render/index.js';
import { customElement, nothing, property, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UMB_UFM_RENDER_CONTEXT } from '../ufm-render/ufm-render.context.js';
import { UmbUfmElementBase } from '../ufm-element-base.js';
import { customElement, property } from '@umbraco-cms/backoffice/external/lit';

const elementName = 'ufm-label-value';

@customElement(elementName)
export class UmbUfmLabelValueElement extends UmbLitElement {
export class UmbUfmLabelValueElement extends UmbUfmElementBase {
@property()
alias?: string;

@state()
private _value?: unknown;

constructor() {
super();

Expand All @@ -20,19 +17,15 @@ export class UmbUfmLabelValueElement extends UmbLitElement {
context.value,
(value) => {
if (this.alias !== undefined && value !== undefined && typeof value === 'object') {
this._value = (value as Record<string, unknown>)[this.alias];
this.value = (value as Record<string, unknown>)[this.alias];
} else {
this._value = value;
this.value = value;
}
},
'observeValue',
);
});
}

override render() {
return this._value !== undefined ? this._value : nothing;
}
}

export { UmbUfmLabelValueElement as element };
Expand Down
15 changes: 15 additions & 0 deletions src/packages/ufm/components/localize/localize.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { UfmToken } from '../../plugins/marked-ufm.plugin.js';
import { UmbUfmComponentBase } from '../ufm-component-base.js';

import './localize.element.js';

export class UmbUfmLocalizeComponent extends UmbUfmComponentBase {
render(token: UfmToken) {
if (!token.text) return;

const attributes = super.getAttributes(token.text);
return `<ufm-localize ${attributes}></ufm-localize>`;
}
}

export { UmbUfmLocalizeComponent as api };
26 changes: 26 additions & 0 deletions src/packages/ufm/components/localize/localize.element.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { UmbUfmElementBase } from '../ufm-element-base.js';
import { customElement, property } from '@umbraco-cms/backoffice/external/lit';

const elementName = 'ufm-localize';

@customElement(elementName)
export class UmbUfmLocalizeElement extends UmbUfmElementBase {
@property()
public set alias(value: string | undefined) {
if (!value) return;
this.#alias = value;
this.value = this.localize.term(value);
}
public get alias(): string | undefined {
return this.#alias;
}
#alias?: string;
}

export { UmbUfmLocalizeElement as element };

declare global {
interface HTMLElementTagNameMap {
[elementName]: UmbUfmLocalizeElement;
}
}
25 changes: 25 additions & 0 deletions src/packages/ufm/components/manifests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { ManifestUfmComponent } from '@umbraco-cms/backoffice/extension-registry';

export const manifests: Array<ManifestUfmComponent> = [
{
type: 'ufmComponent',
alias: 'Umb.Markdown.LabelValue',
name: 'Label Value UFM Component',
api: () => import('./label-value/label-value.component.js'),
meta: { marker: '=' },
},
{
type: 'ufmComponent',
alias: 'Umb.Markdown.Localize',
name: 'Localize UFM Component',
api: () => import('./localize/localize.component.js'),
meta: { marker: '#' },
},
{
type: 'ufmComponent',
alias: 'Umb.Markdown.ContentName',
name: 'Content Name UFM Component',
api: () => import('./content-name/content-name.component.js'),
meta: { marker: '~' },
},
];
25 changes: 25 additions & 0 deletions src/packages/ufm/components/ufm-component-base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { UfmToken } from '../plugins/marked-ufm.plugin.js';
import type { UmbUfmComponentApi } from '@umbraco-cms/backoffice/extension-registry';

export abstract class UmbUfmComponentBase implements UmbUfmComponentApi {
protected getAttributes(text: string): string | null {
if (!text) return null;

const pipeIndex = text.indexOf('|');

if (pipeIndex === -1) {
return `alias="${text.trim()}"`;
}

const alias = text.substring(0, pipeIndex).trim();
const filters = text.substring(pipeIndex + 1).trim();

return Object.entries({ alias, filters })
.map(([key, value]) => (value ? `${key}="${value.trim()}"` : null))
.join(' ');
}

abstract render(token: UfmToken): string | undefined;

destroy() {}
}
54 changes: 54 additions & 0 deletions src/packages/ufm/components/ufm-element-base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { UMB_UFM_CONTEXT } from '../contexts/ufm.context.js';
import { nothing, property, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';

// eslint-disable-next-line local-rules/enforce-element-suffix-on-element-class-name
export abstract class UmbUfmElementBase extends UmbLitElement {
#filterFuncArgs?: Array<{ alias: string; args: Array<string> }>;

@property()
public set filters(value: string | undefined) {
this.#filters = value;

this.#filterFuncArgs = value
?.split('|')
.filter((item) => item)
.map((item) => {
const [alias, ...args] = item.split(':').map((x) => x.trim());
return { alias, args };
});
}
public get filters(): string | undefined {
return this.#filters;
}
#filters?: string;

@state()
value?: unknown;

#ufmContext?: typeof UMB_UFM_CONTEXT.TYPE;

constructor() {
super();

this.consumeContext(UMB_UFM_CONTEXT, (ufmContext) => {
this.#ufmContext = ufmContext;
});
}

override render() {
if (!this.#ufmContext) return nothing;

let values = Array.isArray(this.value) ? this.value : [this.value];
if (this.#filterFuncArgs) {
for (const item of this.#filterFuncArgs) {
const filter = this.#ufmContext.getFilterByAlias(item.alias);
if (filter) {
values = values.map((value) => filter(value, ...item.args));
}
}
}

return values.join(', ');
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';

export class UmbUfmRenderContext extends UmbContextBase<UmbUfmRenderContext> {
#value = new UmbObjectState<unknown>(undefined);
Expand Down
Loading
Loading