Skip to content

Commit e00290f

Browse files
Feature: Clipboard (#17820)
* wip clipboard context + tests * clean up property action module + register copy action * split manifests * add clipboard module * import type * export type * mark all methods async * scaffold copy + paste property actions * scaffold workspace, collection, repo + data sources * remove references to language * register detail manifests * call repo when creating * load clipboard collection data * remove debugger * register clipboard item picker modal * return value from picker * accept native error * clean up data source * add tests for error states * make clipboard local storage manager * add clip entry entity type * create unique in scaffold * add clipboard entry item data * align naming * move around * name alignment * fix imports * fix missing entityType * clean up * use picker input context * remove unused context * Update clipboard.context.ts * map to item model * poc paste property action * register copy/paste as kinds * lint fix * add tests * rename test * add repository tests * register delete clipboard action + enable action dropdown outside of context menu * remove notifcation * export entity type * temp use repo instead of context * delete unused copy property action * make data source non breaking * Update vite.config.ts * add icons for clipboard copy, paste + entry * remove unused * return if there is no property value * add kind interfaces * pass entry type for copy and paste * register clipboard for block list * implement filter on entry type * delete unused context * remove references * rename data to value and don't force an array * make icons and single value * allow to add create and update dates for clipboard entries * use clipboard icon * add create and update dates * export constants * don't set as an array * reload picker content * add copy to clipboard button to block list entry * make picker element * allow to pick multiple * remove generic block list clipboard actions * Revert "remove generic block list clipboard actions" This reverts commit 6ea65a0. * add get methods * wip construct block clipboard entry value * add method to get exposes + add jsdocs * add expose * remove todo + add jsdocs * move clipboard out of core package * add package files * load package again * render entry icon * render correct icon * remove clipboard from core vite.config * Update package-lock.json * wip copy/paste resolvers * allow multiple accepted entry types * move logic to resolvers * transforming clipboard block value to fit block list * wip copy/paste resolvers * clean up * remove unused * fix missing exports * fix tests * return clipboard entry unique from modal * Update block-list-entries.context.ts * clipboard feature: clipboard property value cloner (#17824) * restructure of property package * content data merge controller tests * deprecate meta from propertyValueResolver * temp work * temp * poc * rename to cloner * stached block value cloner work * block list implementation * correct property value implementation * RTE Block Property Value Cloner * Block Grid Value Cloner * update with comments * try out cloner * wip translators * Revert "delete unused context" This reverts commit ec31ae5. * move translator + cloner logic to context * clean up * implement read from clipboard in block list property editor * remove debugger * values array * handle paste * Update types.ts * move files * Update clipboard-local-storage.manager.ts * set both create and update date when creating a clipboard entry * align naming * handle paste * clean up + wip block grid translators * updates types * add grid block copy translator * only allow paste translator to handle a single value * align copy and paste translators * remove debugger * move to folders * add block const * rename * add tests * Update index.ts * use correct type * add tests for UmbBlockListToBlockClipboardCopyTranslator * fix tests * add translator tests * add tests * organize * organize * clean up translator tests * align naming * remove unused button * only render copy property action if property has a value * use constants * copy single grid block * get block grid property value from clipboard entry * add clear method to extension registry + add js docs * Update index.ts * add tests for copy value resolver * add icon for clipboard * use clipboard icon in modal * add tests * remove unused setting * fix log * only create array once * filter for supported paste translators * use write method instead of duplicating the code * add condition config type * use config type * Update manifests.ts * add support for multi picker * move multiple look up logic to context * add js docs * add js docs * remove unused * remove unused * remove unused * implement paste translator filtering for block catalogue modal * temp color translator * adding a UmbPropertyValueDataPotentiallyWithEditorAlias * simplify observer * append user unique to local storage key * remove temp color picker clipboard implementations * more explicit extension type name * more renaming * type specifications * fix test and missing type * more types for test * renaming of paste translators * rename folder * rename value resolvers * correct variable name * wip tests for clipboard context * clean up tests correctly * add more tests for clipboard context * Update clipboard.context.test.ts * use after each for clean up * fix test that times out * correct name * optimize * remove webkit * newest first when picking * use fingerprint local storage key to obfuscate user id * rename method * use const * set content max length * return object including the selection uniques * show confirm dialog before pasting value * only show confirm dialog if there is a value * Feature: clipboard block insert (#17935) * insert methods * fix originData * move logic to base class * progress on Grid and RTE * correct for Block Grid paste implementation * update async across * remove expose from block clipboard entry model * remove expose from grid block clipboard entry * remove todo * wip move value expand responsibility * wip split clipboard context * experiment with an extendable property context * create propertyContexts + proxy events * rename methods * move proxy to context + update grid copy logic * split tests * remove unused * add property context extension type * fix tests * fix tests * reorganize * Update clipboard.property-context.ts * use context * register property context clipboard kind * register for block grid * remove redundant code * rename to replace * Revert "rename to replace" This reverts commit eb0535e. * rename prop * rename label * improve block clean-up abilities * Update rte-base.element.ts * make local get of clipboard context * add asyncFilter option to the type * add is compatible method to the interface * support asyncFilter in picker * add compatability check for block list * more explicit name * wip implementation of filter * add compatability checks * add temp types * add clipboard filtering for grid and list * lint corrections --------- Co-authored-by: Niels Lyngsø <[email protected]> Co-authored-by: Niels Lyngsø <[email protected]>
1 parent 434bac7 commit e00290f

File tree

230 files changed

+6552
-387
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

230 files changed

+6552
-387
lines changed

src/Umbraco.Web.UI.Client/package-lock.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Umbraco.Web.UI.Client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"./block-rte": "./dist-cms/packages/block/block-rte/index.js",
2626
"./block-type": "./dist-cms/packages/block/block-type/index.js",
2727
"./block": "./dist-cms/packages/block/block/index.js",
28+
"./clipboard": "./dist-cms/packages/clipboard/index.js",
2829
"./code-editor": "./dist-cms/packages/code-editor/index.js",
2930
"./collection": "./dist-cms/packages/core/collection/index.js",
3031
"./components": "./dist-cms/packages/core/components/index.js",

src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import './components/index.js';
1313

1414
const CORE_PACKAGES = [
1515
import('../../packages/block/umbraco-package.js'),
16+
import('../../packages/clipboard/umbraco-package.js'),
1617
import('../../packages/code-editor/umbraco-package.js'),
1718
import('../../packages/data-type/umbraco-package.js'),
1819
import('../../packages/dictionary/umbraco-package.js'),

src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,11 @@ export class UmbExtensionRegistry<
163163
return !this.#exclusions.includes(ext.alias);
164164
};
165165

166+
/**
167+
* Register an extension.
168+
* @param {(ManifestTypes | ManifestKind<ManifestTypes>)} manifest - The extension to register.
169+
* @memberof UmbExtensionRegistry
170+
*/
166171
register(manifest: ManifestTypes | ManifestKind<ManifestTypes>): void {
167172
const isValid = this.#validateExtension(manifest);
168173
if (!isValid) {
@@ -185,19 +190,39 @@ export class UmbExtensionRegistry<
185190
]);
186191
}
187192

193+
/**
194+
* Get all registered extensions.
195+
* @returns {Array<ManifestTypes>} - All registered extensions.
196+
* @memberof UmbExtensionRegistry
197+
*/
188198
getAllExtensions(): Array<ManifestTypes> {
189199
return this._extensions.getValue();
190200
}
191201

202+
/**
203+
* Register many extensions.
204+
* @param {(Array<ManifestTypes | ManifestKind<ManifestTypes>>)} manifests - The extensions to register.
205+
* @memberof UmbExtensionRegistry
206+
*/
192207
registerMany(manifests: Array<ManifestTypes | ManifestKind<ManifestTypes>>): void {
193208
// we have to register extensions individually, so we ensure a manifest is valid before continuing to the next one
194209
manifests.forEach((manifest) => this.register(manifest));
195210
}
196211

212+
/**
213+
* Unregister many extensions with the given aliases.
214+
* @param {Array<string>} aliases - The aliases of the extensions to unregister.
215+
* @memberof UmbExtensionRegistry
216+
*/
197217
unregisterMany(aliases: Array<string>): void {
198218
aliases.forEach((alias) => this.unregister(alias));
199219
}
200220

221+
/**
222+
* Unregister an extension with the given alias.
223+
* @param {string} alias - The alias of the extension to unregister.
224+
* @memberof UmbExtensionRegistry
225+
*/
201226
unregister(alias: string): void {
202227
const newKindsValues = this._kinds.getValue().filter((kind) => kind.alias !== alias);
203228
const newExtensionsValues = this._extensions.getValue().filter((extension) => extension.alias !== alias);
@@ -206,6 +231,12 @@ export class UmbExtensionRegistry<
206231
this._extensions.setValue(newExtensionsValues);
207232
}
208233

234+
/**
235+
* Check if an extension with the given alias is registered.
236+
* @param {string} alias - The alias of the extension to check.
237+
* @returns {boolean} - true if an extension with the given alias is registered.
238+
* @memberof UmbExtensionRegistry
239+
*/
209240
isRegistered(alias: string): boolean {
210241
if (this._extensions.getValue().find((ext) => ext.alias === alias)) {
211242
return true;
@@ -218,6 +249,15 @@ export class UmbExtensionRegistry<
218249
return false;
219250
}
220251

252+
/**
253+
* Clears all extensions and kinds from the registry.
254+
* @memberof UmbExtensionRegistry
255+
*/
256+
clear(): void {
257+
this._extensions.setValue([]);
258+
this._kinds.setValue([]);
259+
}
260+
221261
#validateExtension(manifest: ManifestTypes | ManifestKind<ManifestTypes>): boolean {
222262
if (!manifest.type) {
223263
console.error(`Extension is missing type`, manifest);
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { expect } from '@open-wc/testing';
2+
import { customElement } from 'lit/decorators.js';
3+
import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api';
4+
import { UMB_BLOCK_GRID_PROPERTY_EDITOR_SCHEMA_ALIAS } from '../../../property-editors/constants.js';
5+
import { UmbBlockGridToBlockClipboardCopyPropertyValueTranslator } from './block-grid-to-block-copy-translator.js';
6+
import type { UmbBlockGridValueModel } from '../../../types.js';
7+
import type { UmbBlockClipboardEntryValueModel } from '@umbraco-cms/backoffice/block';
8+
9+
@customElement('test-controller-host')
10+
class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {}
11+
12+
describe('UmbBlockListToBlockClipboardCopyPropertyValueTranslator', () => {
13+
let hostElement: UmbTestControllerHostElement;
14+
let copyTranslator: UmbBlockGridToBlockClipboardCopyPropertyValueTranslator;
15+
16+
const blockGridPropertyValue: UmbBlockGridValueModel = {
17+
contentData: [
18+
{
19+
key: 'contentKey',
20+
contentTypeKey: 'contentTypeKey',
21+
values: [
22+
{
23+
culture: null,
24+
segment: null,
25+
alias: 'headline',
26+
editorAlias: 'Umbraco.TextBox',
27+
value: 'Headline value',
28+
},
29+
],
30+
},
31+
],
32+
layout: {
33+
[UMB_BLOCK_GRID_PROPERTY_EDITOR_SCHEMA_ALIAS]: [
34+
{
35+
columnSpan: 12,
36+
rowSpan: 1,
37+
areas: [],
38+
contentKey: 'contentKey',
39+
settingsKey: null,
40+
},
41+
],
42+
},
43+
settingsData: [],
44+
expose: [
45+
{
46+
contentKey: 'contentKey',
47+
culture: null,
48+
segment: null,
49+
},
50+
],
51+
};
52+
53+
const blockClipboardEntryValue: UmbBlockClipboardEntryValueModel = {
54+
contentData: blockGridPropertyValue.contentData,
55+
layout: [
56+
{
57+
contentKey: 'contentKey',
58+
settingsKey: null,
59+
},
60+
],
61+
settingsData: blockGridPropertyValue.settingsData,
62+
};
63+
64+
beforeEach(async () => {
65+
hostElement = new UmbTestControllerHostElement();
66+
copyTranslator = new UmbBlockGridToBlockClipboardCopyPropertyValueTranslator(hostElement);
67+
document.body.innerHTML = '';
68+
document.body.appendChild(hostElement);
69+
});
70+
71+
describe('Public API', () => {
72+
describe('methods', () => {
73+
it('has a translate method', () => {
74+
expect(copyTranslator).to.have.property('translate').that.is.a('function');
75+
});
76+
});
77+
});
78+
79+
describe('translate', () => {
80+
it('returns the block clipboard entry value', async () => {
81+
const result = await copyTranslator.translate(blockGridPropertyValue);
82+
expect(result).to.deep.equal(blockClipboardEntryValue);
83+
});
84+
});
85+
});
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import type { UmbBlockGridValueModel } from '../../../types.js';
2+
import { UMB_BLOCK_GRID_PROPERTY_EDITOR_SCHEMA_ALIAS } from '../../../property-editors/constants.js';
3+
import type { UmbGridBlockClipboardEntryValueModel } from '../../types.js';
4+
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
5+
import type { UmbClipboardCopyPropertyValueTranslator } from '@umbraco-cms/backoffice/clipboard';
6+
import type { UmbBlockClipboardEntryValueModel } from '@umbraco-cms/backoffice/block';
7+
8+
export class UmbBlockGridToBlockClipboardCopyPropertyValueTranslator
9+
extends UmbControllerBase
10+
implements UmbClipboardCopyPropertyValueTranslator<UmbBlockGridValueModel, UmbBlockClipboardEntryValueModel>
11+
{
12+
/**
13+
* Translates a Block Grid property value to a Block clipboard entry value.
14+
* @param {UmbBlockGridValueModel} propertyValue - The Block Grid property value.
15+
* @returns {Promise<UmbBlockClipboardEntryValueModel>} - The Block clipboard entry value.
16+
* @memberof UmbBlockGridToBlockClipboardCopyPropertyValueTranslator
17+
*/
18+
async translate(propertyValue: UmbBlockGridValueModel): Promise<UmbBlockClipboardEntryValueModel> {
19+
if (!propertyValue) {
20+
throw new Error('Property value is missing.');
21+
}
22+
23+
return this.#constructBlockValue(propertyValue);
24+
}
25+
26+
#constructGridBlockValue(propertyValue: UmbBlockGridValueModel): UmbGridBlockClipboardEntryValueModel {
27+
const valueClone = structuredClone(propertyValue);
28+
29+
const gridBlockValue: UmbGridBlockClipboardEntryValueModel = {
30+
contentData: valueClone.contentData,
31+
layout: valueClone.layout?.[UMB_BLOCK_GRID_PROPERTY_EDITOR_SCHEMA_ALIAS] ?? undefined,
32+
settingsData: valueClone.settingsData,
33+
};
34+
35+
return gridBlockValue;
36+
}
37+
38+
#constructBlockValue(propertyValue: UmbBlockGridValueModel): UmbBlockClipboardEntryValueModel {
39+
const gridBlockValue = this.#constructGridBlockValue(propertyValue);
40+
41+
const layout: UmbBlockClipboardEntryValueModel['layout'] = gridBlockValue.layout?.map((gridLayout) => {
42+
return {
43+
contentKey: gridLayout.contentKey,
44+
settingsKey: gridLayout.settingsKey,
45+
};
46+
});
47+
48+
return {
49+
contentData: gridBlockValue.contentData,
50+
layout: layout,
51+
settingsData: gridBlockValue.settingsData,
52+
};
53+
}
54+
}
55+
56+
export { UmbBlockGridToBlockClipboardCopyPropertyValueTranslator as api };
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { UMB_BLOCK_GRID_PROPERTY_EDITOR_UI_ALIAS } from '../../property-editors/constants.js';
2+
import { UMB_BLOCK_CLIPBOARD_ENTRY_VALUE_TYPE } from '@umbraco-cms/backoffice/block';
3+
4+
export const manifests: Array<UmbExtensionManifest> = [
5+
{
6+
type: 'clipboardCopyPropertyValueTranslator',
7+
alias: 'Umb.ClipboardCopyPropertyValueTranslator.BlockGridToBlock',
8+
name: 'Block Grid to Block Clipboard Copy Property Value Translator',
9+
api: () => import('./copy/block-grid-to-block-copy-translator.js'),
10+
fromPropertyEditorUi: UMB_BLOCK_GRID_PROPERTY_EDITOR_UI_ALIAS,
11+
toClipboardEntryValueType: UMB_BLOCK_CLIPBOARD_ENTRY_VALUE_TYPE,
12+
},
13+
{
14+
type: 'clipboardPastePropertyValueTranslator',
15+
alias: 'Umb.ClipboardPastePropertyValueTranslator.BlockToBlockGrid',
16+
name: 'Block To Block Grid Clipboard Paste Property Value Translator',
17+
weight: 900,
18+
api: () => import('./paste/block-to-block-grid-paste-translator.js'),
19+
fromClipboardEntryValueType: UMB_BLOCK_CLIPBOARD_ENTRY_VALUE_TYPE,
20+
toPropertyEditorUi: UMB_BLOCK_GRID_PROPERTY_EDITOR_UI_ALIAS,
21+
},
22+
];
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { expect } from '@open-wc/testing';
2+
import { customElement } from 'lit/decorators.js';
3+
import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api';
4+
import { UMB_BLOCK_GRID_PROPERTY_EDITOR_SCHEMA_ALIAS } from '../../../property-editors/constants.js';
5+
import type { UmbBlockGridValueModel } from '../../../types.js';
6+
import { UmbBlockToBlockGridClipboardPastePropertyValueTranslator } from './block-to-block-grid-paste-translator.js';
7+
import type { UmbBlockClipboardEntryValueModel } from '@umbraco-cms/backoffice/block';
8+
9+
@customElement('test-controller-host')
10+
class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {}
11+
12+
describe('UmbBlockToBlockGridClipboardPastePropertyValueTranslator', () => {
13+
let hostElement: UmbTestControllerHostElement;
14+
let copyTranslator: UmbBlockToBlockGridClipboardPastePropertyValueTranslator;
15+
16+
const blockGridPropertyValue: UmbBlockGridValueModel = {
17+
contentData: [
18+
{
19+
key: 'contentKey',
20+
contentTypeKey: 'contentTypeKey',
21+
values: [
22+
{
23+
culture: null,
24+
segment: null,
25+
alias: 'headline',
26+
editorAlias: 'Umbraco.TextBox',
27+
value: 'Headline value',
28+
},
29+
],
30+
},
31+
],
32+
layout: {
33+
[UMB_BLOCK_GRID_PROPERTY_EDITOR_SCHEMA_ALIAS]: [
34+
{
35+
columnSpan: 12,
36+
rowSpan: 1,
37+
areas: [],
38+
contentKey: 'contentKey',
39+
settingsKey: null,
40+
},
41+
],
42+
},
43+
settingsData: [],
44+
expose: [],
45+
};
46+
47+
const blockClipboardEntryValue: UmbBlockClipboardEntryValueModel = {
48+
contentData: blockGridPropertyValue.contentData,
49+
layout: [
50+
{
51+
contentKey: 'contentKey',
52+
settingsKey: null,
53+
},
54+
],
55+
settingsData: blockGridPropertyValue.settingsData,
56+
};
57+
58+
const config1: Array<{ alias: string; value: [{ contentElementTypeKey: string }] }> = [
59+
{
60+
alias: 'blocks',
61+
value: [
62+
{
63+
contentElementTypeKey: 'contentTypeKey',
64+
},
65+
],
66+
},
67+
];
68+
69+
const config2: Array<{ alias: string; value: [{ contentElementTypeKey: string }] }> = [
70+
{
71+
alias: 'blocks',
72+
value: [
73+
{
74+
contentElementTypeKey: 'contentTypeKey2',
75+
},
76+
],
77+
},
78+
];
79+
80+
beforeEach(async () => {
81+
hostElement = new UmbTestControllerHostElement();
82+
copyTranslator = new UmbBlockToBlockGridClipboardPastePropertyValueTranslator(hostElement);
83+
document.body.innerHTML = '';
84+
document.body.appendChild(hostElement);
85+
});
86+
87+
describe('Public API', () => {
88+
describe('methods', () => {
89+
it('has a translate method', () => {
90+
expect(copyTranslator).to.have.property('translate').that.is.a('function');
91+
});
92+
});
93+
});
94+
95+
describe('translate', () => {
96+
it('returns the block grid property value', async () => {
97+
const result = await copyTranslator.translate(blockClipboardEntryValue);
98+
expect(result).to.deep.equal(blockGridPropertyValue);
99+
});
100+
});
101+
102+
describe('isCompatibleValue', () => {
103+
it('returns true if the value is compatible', async () => {
104+
const result = await copyTranslator.isCompatibleValue(blockClipboardEntryValue, config1);
105+
expect(result).to.be.true;
106+
});
107+
108+
it('returns false if the value is not compatible', async () => {
109+
const result = await copyTranslator.isCompatibleValue(blockClipboardEntryValue, config2);
110+
expect(result).to.be.false;
111+
});
112+
});
113+
});

0 commit comments

Comments
 (0)