Merge branch 'main' into feature/partial-view-editor

This commit is contained in:
Julia Gru
2023-05-10 10:39:26 +02:00
28 changed files with 1068 additions and 38 deletions

View File

@@ -40,7 +40,7 @@ export class UmbAppElement extends UmbLitElement {
*/
@property({ type: String })
// TODO: get from server config
private backofficePath = import.meta.env.DEV ? '' : '/umbraco';
private backofficePath = import.meta.env.DEV ? '/' : '/umbraco';
private _routes: UmbRoute[] = [
{

View File

@@ -23,6 +23,7 @@ const CORE_PACKAGES = [
import('./search/umbraco-package'),
import('./templating/umbraco-package'),
import('./umbraco-news/umbraco-package'),
import('./tags/umbraco-package'),
];
@defineElement('umb-backoffice')

View File

@@ -10,7 +10,6 @@ import { manifest as multipleTextString } from './multiple-text-string/manifests
import { manifest as textArea } from './textarea/manifests';
import { manifest as slider } from './slider/manifests';
import { manifest as toggle } from './toggle/manifests';
import { manifests as tags } from './tags/manifests';
import { manifest as markdownEditor } from './markdown-editor/manifests';
import { manifest as radioButtonList } from './radio-button-list/manifests';
import { manifest as checkboxList } from './checkbox-list/manifests';
@@ -66,7 +65,6 @@ export const manifests: Array<ManifestPropertyEditorUI> = [
...blockGrid,
...collectionView,
...tinyMCE,
...tags,
{
type: 'propertyEditorUI',
alias: 'Umb.PropertyEditorUI.Number',

View File

@@ -1,33 +0,0 @@
import { html } from 'lit';
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { customElement, property } from 'lit/decorators.js';
import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extensions-registry';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
/**
* @element umb-property-editor-ui-tags
*/
@customElement('umb-property-editor-ui-tags')
export class UmbPropertyEditorUITagsElement extends UmbLitElement implements UmbPropertyEditorExtensionElement {
@property()
value = '';
@property({ type: Array, attribute: false })
public config = [];
render() {
return html`<div>umb-property-editor-ui-tags</div>`;
}
static styles = [UUITextStyles];
}
export default UmbPropertyEditorUITagsElement;
declare global {
interface HTMLElementTagNameMap {
'umb-property-editor-ui-tags': UmbPropertyEditorUITagsElement;
}
}

View File

@@ -0,0 +1 @@
export * from './tags-input/tags-input.element';

View File

@@ -0,0 +1,414 @@
import { css, html, nothing } from 'lit';
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { customElement, property, query, queryAll, state } from 'lit/decorators.js';
import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins';
import { repeat } from 'lit/directives/repeat.js';
import { UUIInputElement, UUIInputEvent, UUITagElement } from '@umbraco-ui/uui';
import { UmbTagRepository } from '../../repository/tag.repository';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
import { TagResponseModel } from '@umbraco-cms/backoffice/backend-api';
@customElement('umb-tags-input')
export class UmbTagsInputElement extends FormControlMixin(UmbLitElement) {
@property({ type: String })
group?: string;
@property({ type: String })
culture?: string | null;
_items: string[] = [];
@property({ type: Array })
public set items(newTags: string[]) {
const newItems = newTags.filter((x) => x !== '');
this._items = newItems;
super.value = this._items.join(',');
}
public get items(): string[] {
return this._items;
}
@state()
private _matches: Array<TagResponseModel> = [];
@state()
private _currentInput = '';
@query('#main-tag')
private _mainTag!: UUITagElement;
@query('#tag-input')
private _tagInput!: UUIInputElement;
@query('#input-width-tracker')
private _widthTracker!: HTMLElement;
@queryAll('.options')
private _optionCollection?: HTMLCollectionOf<HTMLInputElement>;
#repository = new UmbTagRepository(this);
constructor() {
super();
console.log('tags-input');
}
public focus() {
this._tagInput.focus();
}
protected getFormElement() {
return undefined;
}
async #getExistingTags(query: string) {
if (!this.group || this.culture === undefined || !query) return;
const { data } = await this.#repository.queryTags(this.group, this.culture, query);
if (!data) return;
this._matches = data.items;
}
#onKeydown(e: KeyboardEvent) {
//Prevent tab away if there is a input.
if (e.key === 'Tab' && (this._tagInput.value as string).trim().length && !this._matches.length) {
e.preventDefault();
this.#createTag();
return;
}
if (e.key === 'Enter') {
this.#createTag();
return;
}
if (e.key === 'ArrowDown' || e.key === 'Tab') {
e.preventDefault();
this._currentInput = this._optionCollection?.item(0)?.value ?? this._currentInput;
this._optionCollection?.item(0)?.focus();
return;
}
this.#inputError(false);
}
#onInput(e: UUIInputEvent) {
this._currentInput = e.target.value as string;
if (!this._currentInput || !this._currentInput.length) {
this._matches = [];
} else {
this.#getExistingTags(this._currentInput);
}
}
protected updated(): void {
this._mainTag.style.width = `${this._widthTracker.offsetWidth - 4}px`;
}
#onBlur() {
if (this._matches.length) return;
else this.#createTag();
}
#createTag() {
this.#inputError(false);
const newTag = (this._tagInput.value as string).trim();
if (!newTag) return;
const tagExists = this.items.find((tag) => tag === newTag);
if (tagExists) return this.#inputError(true);
this.#inputError(false);
this.items = [...this.items, newTag];
this._tagInput.value = '';
this._currentInput = '';
this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true }));
}
#inputError(error: boolean) {
if (error) {
this._mainTag.style.border = '1px solid var(--uui-color-danger)';
this._tagInput.style.color = 'var(--uui-color-danger)';
return;
}
this._mainTag.style.border = '';
this._tagInput.style.color = '';
}
#delete(tag: string) {
const currentItems = [...this.items];
const index = currentItems.findIndex((x) => x === tag);
currentItems.splice(index, 1);
currentItems.length ? (this.items = [...currentItems]) : (this.items = []);
this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true }));
}
/** Dropdown */
#optionClick(index: number) {
this._tagInput.value = this._optionCollection?.item(index)?.value ?? '';
this.#createTag();
this.focus();
return;
}
#optionKeydown(e: KeyboardEvent, index: number) {
if (e.key === 'Enter' || e.key === 'Tab') {
e.preventDefault();
this._currentInput = this._optionCollection?.item(index)?.value ?? '';
this.#createTag();
this.focus();
return;
}
if (e.key === 'ArrowDown') {
e.preventDefault();
if (!this._optionCollection?.item(index + 1)) return;
this._optionCollection?.item(index + 1)?.focus();
this._currentInput = this._optionCollection?.item(index + 1)?.value ?? '';
return;
}
if (e.key === 'ArrowUp') {
e.preventDefault();
if (!this._optionCollection?.item(index - 1)) return;
this._optionCollection?.item(index - 1)?.focus();
this._currentInput = this._optionCollection?.item(index - 1)?.value ?? '';
}
if (e.key === 'Backspace') {
this.focus();
}
}
/** Render */
render() {
return html`
<div id="wrapper">
${this.#enteredTags()}
<span id="main-tag-wrapper">
<uui-tag id="input-width-tracker" aria-hidden="true" style="visibility:hidden;opacity:0;position:absolute;">
${this._currentInput}
</uui-tag>
<uui-tag look="outline" id="main-tag" @click="${this.focus}" slot="trigger">
<input
id="tag-input"
aria-label="tag input"
placeholder="Enter tag"
.value="${this._currentInput ?? undefined}"
@keydown="${this.#onKeydown}"
@input="${this.#onInput}"
@blur="${this.#onBlur}" />
<uui-icon id="icon-add" name="umb:add"></uui-icon>
${this.#renderTagOptions()}
</uui-tag>
<span>
</div>
`;
}
#enteredTags() {
return html` ${this.items.map((tag) => {
return html`
<uui-tag class="tag">
<span>${tag}</span>
<uui-icon name="umb:wrong" @click="${() => this.#delete(tag)}"></uui-icon>
</uui-tag>
`;
})}`;
}
#renderTagOptions() {
if (!this._currentInput.length || !this._matches.length) return nothing;
const matchfilter = this._matches.filter((tag) => tag.text !== this._items.find((x) => x === tag.text));
if (!matchfilter.length) return;
return html`
<div id="matchlist">
${repeat(
matchfilter.slice(0, 5),
(tag: TagResponseModel) => tag.id,
(tag: TagResponseModel, index: number) => {
return html` <input
class="options"
id="tag-${tag.id}"
type="radio"
name="${tag.group}"
@click="${() => this.#optionClick(index)}"
@keydown="${(e: KeyboardEvent) => this.#optionKeydown(e, index)}"
value="${tag.text}" />
<label for="tag-${tag.id}"> ${tag.text} </label>`;
}
)}
</div>
`;
}
static styles = [
UUITextStyles,
css`
#wrapper {
box-sizing: border-box;
display: flex;
gap: var(--uui-size-space-2);
flex-wrap: wrap;
align-items: center;
padding: var(--uui-size-space-2);
border: 1px solid var(--uui-color-border);
background-color: var(--uui-input-background-color, var(--uui-color-surface));
flex: 1;
}
#main-tag-wrapper {
position: relative;
}
/** Tags */
uui-tag {
position: relative;
max-width: 200px;
}
uui-tag uui-icon {
cursor: pointer;
min-width: 12.8px !important;
}
uui-tag span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/** Created tags */
.tag uui-icon {
margin-left: var(--uui-size-space-2);
}
.tag uui-icon:hover,
.tag uui-icon:active {
color: var(--uui-color-selected-contrast);
}
/** Main tag */
#main-tag {
padding: 3px;
background-color: var(--uui-color-selected-contrast);
min-width: 20px;
position: relative;
border-radius: var(--uui-size-5, 12px);
}
#main-tag uui-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
#main-tag:hover uui-icon,
#main-tag:active uui-icon {
color: var(--uui-color-selected);
}
#main-tag #tag-input:focus ~ uui-icon,
#main-tag #tag-input:not(:placeholder-shown) ~ uui-icon {
display: none;
}
#main-tag:has(*:hover),
#main-tag:has(*:active),
#main-tag:has(*:focus) {
border: 1px solid var(--uui-color-selected-emphasis);
}
#main-tag:has(#tag-input:not(:focus)):hover {
cursor: pointer;
border: 1px solid var(--uui-color-selected-emphasis);
}
#main-tag:not(:focus-within) #tag-input:placeholder-shown {
opacity: 0;
}
#main-tag:has(#tag-input:focus),
#main-tag:has(#tag-input:not(:placeholder-shown)) {
min-width: 65px;
}
#main-tag #tag-input {
box-sizing: border-box;
max-height: 25.8px;
background: none;
font: inherit;
color: var(--uui-color-selected);
line-height: reset;
padding: 0 var(--uui-size-space-2);
margin: 0.5px 0 -0.5px;
border: none;
outline: none;
width: 100%;
}
/** Dropdown matchlist */
#matchlist input[type='radio'] {
-webkit-appearance: none;
appearance: none;
/* For iOS < 15 to remove gradient background */
background-color: transparent;
/* Not removed via appearance */
margin: 0;
}
uui-tag:focus-within #matchlist {
display: flex;
}
#matchlist {
display: none;
display: flex;
flex-direction: column;
background-color: var(--uui-color-surface);
position: absolute;
width: 150px;
left: 0;
top: var(--uui-size-space-6);
border-radius: var(--uui-border-radius);
border: 1px solid var(--uui-color-border);
}
#matchlist label {
display: none;
cursor: pointer;
box-sizing: border-box;
display: block;
width: 100%;
background: none;
border: none;
text-align: left;
padding: 10px 12px;
/** Overflow */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
#matchlist label:hover,
#matchlist label:focus,
#matchlist label:focus-within,
#matchlist input[type='radio']:focus + label {
display: block;
background-color: var(--uui-color-focus);
color: var(--uui-color-selected-contrast);
}
`,
];
}
export default UmbTagsInputElement;
declare global {
interface HTMLElementTagNameMap {
'umb-tags-input': UmbTagsInputElement;
}
}

View File

@@ -0,0 +1,54 @@
import { Meta, StoryObj } from '@storybook/web-components';
import './tags-input.element';
import type { UmbTagsInputElement } from './tags-input.element';
const meta: Meta<UmbTagsInputElement> = {
title: 'Components/Inputs/Tags',
component: 'umb-tags-input',
};
export default meta;
type Story = StoryObj<UmbTagsInputElement>;
export const Overview: Story = {
args: {
group: 'Fruits',
items: [],
},
};
export const WithTags: Story = {
args: {
group: 'default',
items: ['Flour', 'Eggs', 'Butter', 'Sugar', 'Salt', 'Milk'],
},
};
export const WithTags2: Story = {
args: {
group: 'default',
items: [
'Cranberry',
'Kiwi',
'Blueberries',
'Watermelon',
'Tomato',
'Mango',
'Strawberry',
'Water Chestnut',
'Papaya',
'Orange Rind',
'Olives',
'Pear',
'Sultana',
'Mulberry',
'Lychee',
'Lemon',
'Apple',
'Banana',
'Dragonfruit',
'Blackberry',
'Raspberry',
],
},
};

View File

@@ -0,0 +1,11 @@
import { manifests as repositoryManifests } from './repository/manifests';
import { manifests as propertyEditorManifests } from './property-editors/manifests';
import { UmbEntrypointOnInit } from '@umbraco-cms/backoffice/extensions-api';
import './components';
export const manifests = [...repositoryManifests, ...propertyEditorManifests];
export const onInit: UmbEntrypointOnInit = (host, extensionRegistry) => {
extensionRegistry.registerMany(manifests);
};

View File

@@ -0,0 +1,19 @@
import type { ManifestPropertyEditorModel } from '@umbraco-cms/backoffice/extensions-registry';
export const manifest: ManifestPropertyEditorModel = {
type: 'propertyEditorModel',
name: 'Tags',
alias: 'Umbraco.Tags',
meta: {
config: {
properties: [
{
alias: 'startNodeId',
label: 'Start node',
description: '',
propertyEditorUI: 'Umb.PropertyEditorUI.Tags',
},
],
},
},
};

View File

@@ -0,0 +1,4 @@
import { manifests as tagsUI } from './tags/manifests';
import type { ManifestTypes } from '@umbraco-cms/backoffice/extensions-registry';
export const manifests: Array<ManifestTypes> = [...tagsUI];

View File

@@ -0,0 +1,68 @@
import { html } from 'lit';
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { customElement, property, state } from 'lit/decorators.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { UmbTagsInputElement } from '../../components/tags-input/tags-input.element';
import { UMB_WORKSPACE_PROPERTY_CONTEXT_TOKEN } from '../../../core/components/workspace-property/workspace-property.context';
import { UmbPropertyEditorExtensionElement } from '@umbraco-cms/backoffice/extensions-registry';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
import { DataTypePropertyPresentationModel } from '@umbraco-cms/backoffice/backend-api';
/**
* @element umb-property-editor-ui-tags
*/
@customElement('umb-property-editor-ui-tags')
export class UmbPropertyEditorUITagsElement extends UmbLitElement implements UmbPropertyEditorExtensionElement {
@property()
value: string[] = [];
@state()
private _group?: string;
@state()
private _culture?: string | null;
//TODO: Use type from VariantID
@property({ type: Array, attribute: false })
public set config(config: Array<DataTypePropertyPresentationModel>) {
const group = config.find((x) => x.alias === 'group');
if (group) this._group = group.value as string;
const items = config.find((x) => x.alias === 'items');
if (items) this.value = items.value as Array<string>;
}
constructor() {
super();
this.consumeContext(UMB_WORKSPACE_PROPERTY_CONTEXT_TOKEN, (context) => {
this.observe(context.variantId, (id) => {
if (id && id.culture !== undefined) {
this._culture = id.culture;
}
});
});
}
private _onChange(event: CustomEvent) {
this.value = ((event.target as UmbTagsInputElement).value as string).split(',');
this.dispatchEvent(new CustomEvent('property-value-change'));
}
render() {
return html`<umb-tags-input
group="${ifDefined(this._group)}"
.culture=${this._culture}
.items=${this.value}
@change=${this._onChange}></umb-tags-input>`;
}
static styles = [UUITextStyles];
}
export default UmbPropertyEditorUITagsElement;
declare global {
interface HTMLElementTagNameMap {
'umb-property-editor-ui-tags': UmbPropertyEditorUITagsElement;
}
}

View File

@@ -0,0 +1,23 @@
import { UmbTagRepository } from './tag.repository';
import { UmbTagStore } from './tag.store';
import type { ManifestStore, ManifestRepository } from '@umbraco-cms/backoffice/extensions-registry';
export const TAG_REPOSITORY_ALIAS = 'Umb.Repository.Tags';
const repository: ManifestRepository = {
type: 'repository',
alias: TAG_REPOSITORY_ALIAS,
name: 'Tags Repository',
class: UmbTagRepository,
};
export const TAG_STORE_ALIAS = 'Umb.Store.Tags';
const store: ManifestStore = {
type: 'store',
alias: TAG_STORE_ALIAS,
name: 'Tags Store',
class: UmbTagStore,
};
export const manifests = [repository, store];

View File

@@ -0,0 +1,44 @@
import { v4 as uuidv4 } from 'uuid';
import { TagResource } from '@umbraco-cms/backoffice/backend-api';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller';
import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
/**
* A data source for the Tag that fetches data from the server
* @export
* @class UmbTagServerDataSource
* @implements {RepositoryDetailDataSource}
*/
export class UmbTagServerDataSource {
#host: UmbControllerHostElement;
/**
* Creates an instance of UmbTagServerDataSource.
* @param {UmbControllerHostElement} host
* @memberof UmbTagServerDataSource
*/
constructor(host: UmbControllerHostElement) {
this.#host = host;
}
/**
* Get a list of tags on the server
* @return {*}
* @memberof UmbTagServerDataSource
*/
async getCollection({
query,
skip,
take,
tagGroup,
culture,
}: {
query: string;
skip: number;
take: number;
tagGroup?: string;
culture?: string;
}) {
return tryExecuteAndNotify(this.#host, TagResource.getTag({ query, skip, take, tagGroup, culture }));
}
}

View File

@@ -0,0 +1,64 @@
import { UmbTagServerDataSource } from './sources/tag.server.data';
import { UmbTagStore, UMB_TAG_STORE_CONTEXT_TOKEN } from './tag.store';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller';
import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api';
export class UmbTagRepository {
#init!: Promise<unknown>;
#host: UmbControllerHostElement;
#dataSource: UmbTagServerDataSource;
#tagStore?: UmbTagStore;
constructor(host: UmbControllerHostElement) {
this.#host = host;
this.#dataSource = new UmbTagServerDataSource(this.#host);
this.#init = Promise.all([
new UmbContextConsumerController(this.#host, UMB_TAG_STORE_CONTEXT_TOKEN, (instance) => {
this.#tagStore = instance;
}).asPromise(),
]);
}
async requestTags(
tagGroupName: string,
culture: string | null,
{ skip, take, query } = { skip: 0, take: 1000, query: '' }
) {
await this.#init;
const requestCulture = culture || '';
const { data, error } = await this.#dataSource.getCollection({
skip,
take,
tagGroup: tagGroupName,
culture: requestCulture,
query,
});
if (data) {
// TODO: allow to append an array of items to the store
// TODO: append culture? "Invariant" if null.
data.items.forEach((x) => this.#tagStore?.append(x));
}
return {
data,
error,
asObservable: () => this.#tagStore!.byQuery(tagGroupName, requestCulture, query),
};
}
async queryTags(
tagGroupName: string,
culture: string | null,
query: string,
{ skip, take } = { skip: 0, take: 1000 }
) {
return this.requestTags(tagGroupName, culture, { skip, take, query });
}
}

View File

@@ -0,0 +1,75 @@
import type { TagResponseModel } from '@umbraco-cms/backoffice/backend-api';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
import { UmbStoreBase } from '@umbraco-cms/backoffice/store';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller';
export const UMB_TAG_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbTagStore>('UmbTagStore');
/**
* @export
* @class UmbTagStore
* @extends {UmbStoreBase}
* @description - Data Store for Template Details
*/
export class UmbTagStore extends UmbStoreBase {
public readonly data = this._data.asObservable();
/**
* Creates an instance of UmbTagStore.
* @param {UmbControllerHostElement} host
* @memberof UmbTagStore
*/
constructor(host: UmbControllerHostElement) {
super(host, UMB_TAG_STORE_CONTEXT_TOKEN.toString(), new UmbArrayState<TagResponseModel>([], (x) => x.id));
}
/**
* Append a tag to the store
* @param {TagResponseModel} TAG
* @memberof UmbTagStore
*/
append(tag: TagResponseModel) {
this._data.append([tag]);
}
/**
* Append a tag to the store
* @param {id} TagResponseModel id.
* @memberof UmbTagStore
*/
byId(id: TagResponseModel['id']) {
return this._data.getObservablePart((x) => x.find((y) => y.id === id));
}
items(group: TagResponseModel['group'], culture: string) {
return this._data.getObservablePart((items) =>
items.filter((item) => item.group === group && item.culture === culture)
);
}
// TODO
// There isnt really any way to exclude certain tags when searching for suggestions.
// This is important for the skip/take in the endpoint. We do not want to get the tags from database that we already have picked.
// Forexample: we have 10 different tags that includes "berry" (and searched for "berry") and we have a skip of 0 and take of 5.
// If we already has picked lets say 4 of them, the list will only show 1 more, even though there is more remaining in the database.
byQuery(group: TagResponseModel['group'], culture: string, query: string) {
return this._data.getObservablePart((items) =>
items.filter(
(item) =>
item.group === group &&
item.culture === culture &&
item.query?.toLocaleLowerCase().includes(query.toLocaleLowerCase())
)
);
}
/**
* Removes tag in the store with the given uniques
* @param {string[]} uniques
* @memberof UmbTagStore
*/
remove(uniques: Array<TagResponseModel['id']>) {
this._data.remove(uniques);
}
}

View File

@@ -0,0 +1,10 @@
export const name = 'Umbraco.Core.UserManagement';
export const version = '0.0.1';
export const extensions = [
{
name: 'Tags Management Entry Point',
alias: 'Umb.EntryPoint.TagsManagement',
type: 'entryPoint',
loader: () => import('./index'),
},
];

View File

@@ -30,6 +30,7 @@ import { handlers as packageHandlers } from './domains/package.handlers';
import { handlers as rteEmbedHandlers } from './domains/rte-embed.handlers';
import { handlers as stylesheetHandlers } from './domains/stylesheet.handlers';
import { handlers as partialViewsHandlers } from './domains/partial-views.handlers';
import { handlers as tagHandlers } from './domains/tag-handlers';
const handlers = [
serverHandlers.serverVersionHandler,
@@ -63,6 +64,7 @@ const handlers = [
...rteEmbedHandlers,
...stylesheetHandlers,
...partialViewsHandlers,
...tagHandlers,
];
switch (import.meta.env.VITE_UMBRACO_INSTALL_STATUS) {

View File

@@ -378,7 +378,16 @@ export const data: Array<DataTypeResponseModel | FolderTreeItemResponseModel> =
parentId: null,
propertyEditorAlias: 'Umbraco.Tags',
propertyEditorUiAlias: 'Umb.PropertyEditorUI.Tags',
values: [],
values: [
{
alias: 'group',
value: 'Fruits',
},
{
alias: 'items',
value: [],
},
],
},
{
$type: '',

View File

@@ -0,0 +1,195 @@
import { rest } from 'msw';
import { umbracoPath } from '@umbraco-cms/backoffice/utils';
import { PagedTagResponseModel, TagResponseModel } from '@umbraco-cms/backoffice/backend-api';
export const handlers = [
rest.get(umbracoPath('/tag'), (_req, res, ctx) => {
// didnt add culture logic here
const query = _req.url.searchParams.get('query');
if (!query || !query.length) return;
const tagGroup = _req.url.searchParams.get('tagGroup') ?? 'default';
const skip = parseInt(_req.url.searchParams.get('skip') ?? '0', 10);
const take = parseInt(_req.url.searchParams.get('take') ?? '5', 10);
const TagsByGroup = TagData.filter((tag) => tag.group?.toLocaleLowerCase() === tagGroup.toLocaleLowerCase());
const TagsMatch = TagsByGroup.filter((tag) => tag.text?.toLocaleLowerCase().includes(query.toLocaleLowerCase()));
const Tags = TagsMatch.slice(skip, skip + take);
const PagedData: PagedTagResponseModel = {
total: Tags.length,
items: Tags,
};
return res(ctx.status(200), ctx.json<PagedTagResponseModel>(PagedData));
}),
];
// Mock Data
const TagData: TagResponseModel[] = [
{
id: '1',
text: 'Cranberry',
group: 'Fruits',
nodeCount: 1,
},
{
id: '2',
text: 'Kiwi',
group: 'Fruits',
nodeCount: 1,
},
{
id: '3',
text: 'Blueberries',
group: 'Fruits',
nodeCount: 1,
},
{
id: '4',
text: 'Watermelon',
group: 'Fruits',
nodeCount: 1,
},
{
id: '5',
text: 'Tomato',
group: 'Fruits',
nodeCount: 1,
},
{
id: '6',
text: 'Mango',
group: 'Fruits',
nodeCount: 1,
},
{
id: '7',
text: 'Strawberry',
group: 'Fruits',
nodeCount: 1,
},
{
id: '8',
text: 'Water Chestnut',
group: 'Fruits',
nodeCount: 1,
},
{
id: '9',
text: 'Papaya',
group: 'Fruits',
nodeCount: 1,
},
{
id: '10',
text: 'Orange Rind',
group: 'Fruits',
nodeCount: 1,
},
{
id: '11',
text: 'Olives',
group: 'Fruits',
nodeCount: 1,
},
{
id: '12',
text: 'Pear',
group: 'Fruits',
nodeCount: 1,
},
{
id: '13',
text: 'Sultana',
group: 'Fruits',
nodeCount: 1,
},
{
id: '14',
text: 'Mulberry',
group: 'Fruits',
nodeCount: 1,
},
{
id: '15',
text: 'Lychee',
group: 'Fruits',
nodeCount: 1,
},
{
id: '16',
text: 'Lemon',
group: 'Fruits',
nodeCount: 1,
},
{
id: '17',
text: 'Apple',
group: 'Fruits',
nodeCount: 1,
},
{
id: '18',
text: 'Banana',
group: 'Fruits',
nodeCount: 1,
},
{
id: '19',
text: 'Dragonfruit',
group: 'Fruits',
nodeCount: 1,
},
{
id: '20',
text: 'Blackberry',
group: 'Fruits',
nodeCount: 1,
},
{
id: '21',
text: 'Raspberry',
group: 'Fruits',
nodeCount: 1,
},
{
id: '22',
text: 'Flour',
group: 'Cake Ingredients',
nodeCount: 1,
},
{
id: '23',
text: 'Eggs',
group: 'Cake Ingredients',
nodeCount: 1,
},
{
id: '24',
text: 'Butter',
group: 'Cake Ingredients',
nodeCount: 1,
},
{
id: '25',
text: 'Sugar',
group: 'Cake Ingredients',
nodeCount: 1,
},
{
id: '26',
text: 'Salt',
group: 'Cake Ingredients',
nodeCount: 1,
},
{
id: '26',
text: 'Milk',
group: 'Cake Ingredients',
nodeCount: 1,
},
];