diff --git a/src/Umbraco.Web.UI.Client/src/apps/installer/consent/installer-content.test.ts b/src/Umbraco.Web.UI.Client/src/apps/installer/consent/installer-content.test.ts deleted file mode 100644 index 91dc65b90095..000000000000 --- a/src/Umbraco.Web.UI.Client/src/apps/installer/consent/installer-content.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { UmbInstallerConsentElement } from './installer-consent.element.js'; -import { expect, fixture, html } from '@open-wc/testing'; - -import { type UmbTestRunnerWindow, defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; - -// TODO: Write tests -describe('UmbInstallerConsentElement', () => { - let element: UmbInstallerConsentElement; - - beforeEach(async () => { - element = await fixture(html``); - }); - - it('is defined with its own instance', () => { - expect(element).to.be.instanceOf(UmbInstallerConsentElement); - }); - - if ((window as UmbTestRunnerWindow).__UMBRACO_TEST_RUN_A11Y_TEST) { - it('passes the a11y audit', async () => { - await expect(element).to.be.accessible(defaultA11yConfig); - }); - } -}); diff --git a/src/Umbraco.Web.UI.Client/src/apps/installer/database/installer-database.test.ts b/src/Umbraco.Web.UI.Client/src/apps/installer/database/installer-database.test.ts deleted file mode 100644 index a27f2cfc2c0d..000000000000 --- a/src/Umbraco.Web.UI.Client/src/apps/installer/database/installer-database.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { UmbInstallerDatabaseElement } from './installer-database.element.js'; -import { expect, fixture, html } from '@open-wc/testing'; - -import { type UmbTestRunnerWindow, defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; - -// TODO: Write tests -describe('UmbInstallerDatabaseElement', () => { - let element: UmbInstallerDatabaseElement; - - beforeEach(async () => { - element = await fixture(html``); - }); - - it('is defined with its own instance', () => { - expect(element).to.be.instanceOf(UmbInstallerDatabaseElement); - }); - - if ((window as UmbTestRunnerWindow).__UMBRACO_TEST_RUN_A11Y_TEST) { - it('passes the a11y audit', async () => { - await expect(element).to.be.accessible(defaultA11yConfig); - }); - } -}); diff --git a/src/Umbraco.Web.UI.Client/src/apps/installer/error/installer-error.test.ts b/src/Umbraco.Web.UI.Client/src/apps/installer/error/installer-error.test.ts deleted file mode 100644 index 5993e79ad62d..000000000000 --- a/src/Umbraco.Web.UI.Client/src/apps/installer/error/installer-error.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { UmbInstallerErrorElement } from './installer-error.element.js'; -import { expect, fixture, html } from '@open-wc/testing'; - -import { type UmbTestRunnerWindow, defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; - -// TODO: Write tests -describe('UmbInstallerErrorElement', () => { - let element: UmbInstallerErrorElement; - - beforeEach(async () => { - element = await fixture(html``); - }); - - it('is defined with its own instance', () => { - expect(element).to.be.instanceOf(UmbInstallerErrorElement); - }); - - if ((window as UmbTestRunnerWindow).__UMBRACO_TEST_RUN_A11Y_TEST) { - it('passes the a11y audit', async () => { - await expect(element).to.be.accessible(defaultA11yConfig); - }); - } -}); diff --git a/src/Umbraco.Web.UI.Client/src/apps/installer/installing/installer-installing.test.ts b/src/Umbraco.Web.UI.Client/src/apps/installer/installing/installer-installing.test.ts deleted file mode 100644 index a156a0648226..000000000000 --- a/src/Umbraco.Web.UI.Client/src/apps/installer/installing/installer-installing.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { UmbInstallerInstallingElement } from './installer-installing.element.js'; -import { expect, fixture, html } from '@open-wc/testing'; - -import { type UmbTestRunnerWindow, defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; - -// TODO: Write tests -describe('UmbInstallerInstallingElement', () => { - let element: UmbInstallerInstallingElement; - - beforeEach(async () => { - element = await fixture(html``); - }); - - it('is defined with its own instance', () => { - expect(element).to.be.instanceOf(UmbInstallerInstallingElement); - }); - - if ((window as UmbTestRunnerWindow).__UMBRACO_TEST_RUN_A11Y_TEST) { - it('passes the a11y audit', async () => { - await expect(element).to.be.accessible(defaultA11yConfig); - }); - } -}); diff --git a/src/Umbraco.Web.UI.Client/src/apps/installer/shared/layout/installer-layout.test.ts b/src/Umbraco.Web.UI.Client/src/apps/installer/shared/layout/installer-layout.test.ts deleted file mode 100644 index 7e38f173e246..000000000000 --- a/src/Umbraco.Web.UI.Client/src/apps/installer/shared/layout/installer-layout.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { UmbInstallerLayoutElement } from './installer-layout.element.js'; -import { expect, fixture, html } from '@open-wc/testing'; - -import { type UmbTestRunnerWindow, defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; - -// TODO: Write tests -describe('UmbInstallerLayoutElement', () => { - let element: UmbInstallerLayoutElement; - - beforeEach(async () => { - element = await fixture(html``); - }); - - it('is defined with its own instance', () => { - expect(element).to.be.instanceOf(UmbInstallerLayoutElement); - }); - - if ((window as UmbTestRunnerWindow).__UMBRACO_TEST_RUN_A11Y_TEST) { - it('passes the a11y audit', async () => { - await expect(element).to.be.accessible(defaultA11yConfig); - }); - } -}); diff --git a/src/Umbraco.Web.UI.Client/src/apps/installer/user/installer-user.test.ts b/src/Umbraco.Web.UI.Client/src/apps/installer/user/installer-user.test.ts deleted file mode 100644 index da510d409280..000000000000 --- a/src/Umbraco.Web.UI.Client/src/apps/installer/user/installer-user.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { UmbInstallerUserElement } from './installer-user.element.js'; -import { expect, fixture, html } from '@open-wc/testing'; - -import { type UmbTestRunnerWindow, defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; - -// TODO: Write tests -describe('UmbInstallerUserElement', () => { - let element: UmbInstallerUserElement; - - beforeEach(async () => { - element = await fixture(html``); - }); - - it('is defined with its own instance', () => { - expect(element).to.be.instanceOf(UmbInstallerUserElement); - }); - - if ((window as UmbTestRunnerWindow).__UMBRACO_TEST_RUN_A11Y_TEST) { - it('passes the a11y audit', async () => { - await expect(element).to.be.accessible(defaultA11yConfig); - }); - } -}); diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts index 0f54f27c9fe5..cd9cb0c4794b 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts @@ -654,7 +654,7 @@ export default { indexCannotRebuild: 'This index cannot be rebuilt because it has no assigned', iIndexPopulator: 'IIndexPopulator', corruptStatus: 'Possible corrupt index detected', - corruptErrorDescription: 'Error received when evaluating the index:' + corruptErrorDescription: 'Error received when evaluating the index:', }, placeholders: { username: 'Enter your username', diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-detail-workspace-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-detail-workspace-base.ts index db0a159d0df1..b2731f366c14 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-detail-workspace-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-detail-workspace-base.ts @@ -63,6 +63,20 @@ export interface UmbContentDetailWorkspaceContextArgs< saveModalToken?: UmbModalToken, UmbContentVariantPickerValue>; } +/** + * The base class for a content detail workspace context. + * @exports + * @abstract + * @class UmbContentDetailWorkspaceContextBase + * @augments {UmbEntityDetailWorkspaceContextBase} + * @implements {UmbContentWorkspaceContext} + * @template DetailModelType + * @template DetailRepositoryType + * @template ContentTypeDetailModelType + * @template VariantModelType + * @template VariantOptionModelType + * @template CreateArgsType + */ export abstract class UmbContentDetailWorkspaceContextBase< DetailModelType extends UmbContentDetailModel, DetailRepositoryType extends UmbDetailRepository = UmbDetailRepository, @@ -83,8 +97,11 @@ export abstract class UmbContentDetailWorkspaceContextBase< /* Content Data */ protected override readonly _data = new UmbContentWorkspaceDataManager(this); + + public override readonly data = this._data.current; public readonly values = this._data.createObservablePartOfCurrent((data) => data?.values); public readonly variants = this._data.createObservablePartOfCurrent((data) => data?.variants ?? []); + public override readonly persistedData = this._data.persisted; /* Content Type (Structure) Data */ public readonly structure; @@ -332,6 +349,10 @@ export abstract class UmbContentDetailWorkspaceContextBase< return this._data.getCurrent()?.variants?.find((x) => variantId.compare(x)); } + public getVariants(): Array | undefined { + return this._data.getCurrent()?.variants; + } + /** * Observe the property type * @param {string} propertyId - The id of the property @@ -440,7 +461,19 @@ export abstract class UmbContentDetailWorkspaceContextBase< this._data.finishPropertyValueChange(); }; - protected async _determineVariantOptions() { + /** + * Gets the changed variant ids + * @returns {Array} - The changed variant ids + * @memberof UmbContentDetailWorkspaceContextBase + */ + public getChangedVariants(): Array { + return this._data.getChangedVariants(); + } + + protected async _determineVariantOptions(): Promise<{ + options: VariantOptionModelType[]; + selected: string[]; + }> { const options = await firstValueFrom(this.variantOptions); const activeVariants = this.splitView.getActiveVariants(); @@ -465,7 +498,23 @@ export abstract class UmbContentDetailWorkspaceContextBase< }; /* validation */ + /** + * Run the mandatory validation for the save data + * @deprecated Use the public runMandatoryValidationForSaveData instead. Will be removed in v. 17. + * @protected + * @param {DetailModelType} saveData - The data to validate + * @memberof UmbContentDetailWorkspaceContextBase + */ protected async _runMandatoryValidationForSaveData(saveData: DetailModelType) { + this.runMandatoryValidationForSaveData(saveData); + } + + /** + * Run the mandatory validation for the save data + * @param {DetailModelType} saveData - The data to validate + * @memberof UmbContentDetailWorkspaceContextBase + */ + public async runMandatoryValidationForSaveData(saveData: DetailModelType) { // Check that the data is valid before we save it. // Check variants have a name: const variantsWithoutAName = saveData.variants.filter((x) => !x.name); @@ -482,7 +531,13 @@ export abstract class UmbContentDetailWorkspaceContextBase< } } - protected async _askServerToValidate(saveData: DetailModelType, variantIds: Array) { + /** + * Ask the server to validate the save data + * @param {DetailModelType} saveData - The data to validate + * @param {Array} variantIds - The variant ids to validate + * @memberof UmbContentDetailWorkspaceContextBase + */ + public async askServerToValidate(saveData: DetailModelType, variantIds: Array) { if (this.#validationRepositoryClass) { // Create the validation repository if it does not exist. (we first create this here when we need it) [NL] this.#validationRepository ??= new this.#validationRepositoryClass(this); @@ -516,6 +571,16 @@ export abstract class UmbContentDetailWorkspaceContextBase< return this._handleSubmit(); } + /** + * Get the data to save + * @param {Array} variantIds - The variant ids to save + * @returns {Promise} {Promise} + * @memberof UmbContentDetailWorkspaceContextBase + */ + public constructSaveData(variantIds: Array): Promise { + return this._data.constructData(variantIds); + } + protected async _handleSubmit() { const data = this.getData(); if (!data) { @@ -553,24 +618,42 @@ export abstract class UmbContentDetailWorkspaceContextBase< throw new Error('No variant picker modal token is set. There are multiple variants to save. Cannot proceed.'); } - const saveData = await this._data.constructData(variantIds); - await this._runMandatoryValidationForSaveData(saveData); + const saveData = await this.constructSaveData(variantIds); + await this.runMandatoryValidationForSaveData(saveData); if (this.#validateOnSubmit) { - await this._askServerToValidate(saveData, variantIds); + await this.askServerToValidate(saveData, variantIds); return this.validateAndSubmit( async () => { - return this._performCreateOrUpdate(variantIds, saveData); + return this.performCreateOrUpdate(variantIds, saveData); }, async () => { return this.invalidSubmit(); }, ); } else { - await this._performCreateOrUpdate(variantIds, saveData); + await this.performCreateOrUpdate(variantIds, saveData); } } + /** + * Perform the create or update of the content + * @deprecated Use the public performCreateOrUpdate instead. Will be removed in v. 17. + * @protected + * @param {Array} variantIds + * @param {DetailModelType} saveData + * @memberof UmbContentDetailWorkspaceContextBase + */ protected async _performCreateOrUpdate(variantIds: Array, saveData: DetailModelType) { + await this.performCreateOrUpdate(variantIds, saveData); + } + + /** + * Perform the create or update of the content + * @param {Array} variantIds - The variant ids to save + * @param {DetailModelType} saveData - The data to save + * @memberof UmbContentDetailWorkspaceContextBase + */ + public async performCreateOrUpdate(variantIds: Array, saveData: DetailModelType) { if (this.getIsNew()) { await this.#create(variantIds, saveData); } else { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/deprecation/deprecation.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/deprecation/deprecation.ts new file mode 100644 index 000000000000..5c799a3a4257 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/deprecation/deprecation.ts @@ -0,0 +1,29 @@ +import type { UmbDeprecationArgs } from './types.js'; + +/** + * Helper class for deprecation warnings. + * @exports + * @class UmbDeprecation + */ +export class UmbDeprecation { + #messagePrefix: string = 'Umbraco Backoffice:'; + #deprecated: string; + #removeInVersion: string; + #solution: string; + + constructor(args: UmbDeprecationArgs) { + this.#deprecated = args.deprecated; + this.#removeInVersion = args.removeInVersion; + this.#solution = args.solution; + } + + /** + * Logs a warning message to the console. + * @memberof UmbDeprecation + */ + warn() { + console.warn( + `${this.#messagePrefix} ${this.#deprecated} The feature will be removed in version ${this.#removeInVersion}. ${this.#solution}`, + ); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/deprecation/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/deprecation/index.ts new file mode 100644 index 000000000000..d7fa69c17143 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/deprecation/index.ts @@ -0,0 +1,3 @@ +export * from './deprecation.js'; + +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/deprecation/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/deprecation/types.ts new file mode 100644 index 000000000000..008aa5cd3b94 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/deprecation/types.ts @@ -0,0 +1,5 @@ +export interface UmbDeprecationArgs { + deprecated: string; + removeInVersion: string; + solution: string; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts index 078596afd77e..99441ca9968b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts @@ -23,4 +23,5 @@ export * from './sanitize/sanitize-html.function.js'; export * from './selection-manager/selection.manager.js'; export * from './state-manager/index.js'; export * from './string/index.js'; +export * from './deprecation/index.js'; export type * from './type/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts index 02198025a8d3..e6a1dbe4f2d8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts @@ -38,6 +38,7 @@ export abstract class UmbEntityDetailWorkspaceContextBase< public readonly unique = this.#entityContext.unique; public readonly data = this._data.current; + public readonly persistedData = this._data.persisted; public readonly loading = new UmbStateManager(this); protected _getDataPromise?: Promise; @@ -85,6 +86,14 @@ export abstract class UmbEntityDetailWorkspaceContextBase< return this._data.getCurrent(); } + /** + * Get the persisted data + * @returns { DetailModelType | undefined } The persisted data + */ + public getPersistedData(): DetailModelType | undefined { + return this._data.getPersisted(); + } + /** * Get the unique * @returns { string | undefined } The unique identifier @@ -122,10 +131,10 @@ export abstract class UmbEntityDetailWorkspaceContextBase< } async load(unique: string) { + this.resetState(); this.#entityContext.setUnique(unique); this.loading.addState({ unique: LOADING_STATE_UNIQUE, message: `Loading ${this.getEntityType()} Details` }); await this.#init; - this.resetState(); this._getDataPromise = this._detailRepository!.requestByUnique(unique); type GetDataType = Awaited['requestByUnique']>>; const response = (await this._getDataPromise) as GetDataType; @@ -147,6 +156,21 @@ export abstract class UmbEntityDetailWorkspaceContextBase< return response; } + /** + * Reload the workspace data + * @returns { Promise } The promise of the reload + */ + public async reload(): Promise { + const unique = this.getUnique(); + if (!unique) throw new Error('Unique is not set'); + const { data } = await this._detailRepository!.requestByUnique(unique); + + if (data) { + this._data.setPersisted(data); + this._data.setCurrent(data); + } + } + /** * Method to check if the workspace data is loaded. * @returns { Promise | undefined } true if the workspace data is loaded. @@ -166,9 +190,9 @@ export abstract class UmbEntityDetailWorkspaceContextBase< * @returns { Promise | undefined } The data of the scaffold. */ public async createScaffold(args: CreateArgsType) { + this.resetState(); this.loading.addState({ unique: LOADING_STATE_UNIQUE, message: `Creating ${this.getEntityType()} scaffold` }); await this.#init; - this.resetState(); this.setParent(args.parent); const request = this._detailRepository!.createScaffold(args.preset); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/submittable/submittable-workspace-context-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/submittable/submittable-workspace-context-base.ts index 1293abbc46cc..a641f1088768 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/submittable/submittable-workspace-context-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/submittable/submittable-workspace-context-base.ts @@ -63,7 +63,7 @@ export abstract class UmbSubmittableWorkspaceContextBase this.#isNew.setValue(undefined); } - getIsNew() { + public getIsNew() { return this.#isNew.getValue(); } @@ -75,19 +75,19 @@ export abstract class UmbSubmittableWorkspaceContextBase * If a Workspace has multiple validation contexts, then this method can be overwritten to return the correct one. * @returns Promise that resolves to void when the validation is complete. */ - async validate(): Promise> { + public async validate(): Promise> { //return this.validation.validate(); return Promise.all(this.#validationContexts.map((context) => context.validate())); } - async requestSubmit(): Promise { + public async requestSubmit(): Promise { return this.validateAndSubmit( () => this.submit(), () => this.invalidSubmit(), ); } - protected async validateAndSubmit(onValid: () => Promise, onInvalid: () => Promise): Promise { + public async validateAndSubmit(onValid: () => Promise, onInvalid: () => Promise): Promise { if (this.#submitPromise) { return this.#submitPromise; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/constants.ts index 2c9aa4e99b4f..b51328d5925e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/constants.ts @@ -1,15 +1,16 @@ export { UMB_DOCUMENT_ENTITY_TYPE, UMB_DOCUMENT_ROOT_ENTITY_TYPE } from './entity.js'; -export * from './paths.js'; export * from './collection/constants.js'; export * from './entity-actions/constants.js'; export * from './entity-bulk-actions/constants.js'; -export * from './property-dataset-context/constants.js'; export * from './modals/constants.js'; +export * from './paths.js'; +export * from './property-dataset-context/constants.js'; +export * from './publishing/constants.js'; export * from './recycle-bin/constants.js'; export * from './reference/constants.js'; export * from './repository/constants.js'; export * from './rollback/constants.js'; export * from './search/constants.js'; -export * from './workspace/constants.js'; export * from './user-permissions/constants.js'; +export * from './workspace/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/manifests.ts index 38252454235e..8caa4a29f5c1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/manifests.ts @@ -1,10 +1,6 @@ import { UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS, UMB_DOCUMENT_ITEM_REPOSITORY_ALIAS } from '../repository/index.js'; import { UMB_DOCUMENT_ENTITY_TYPE } from '../entity.js'; -import { - UMB_USER_PERMISSION_DOCUMENT_DELETE, - UMB_USER_PERMISSION_DOCUMENT_PUBLISH, - UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH, -} from '../user-permissions/constants.js'; +import { UMB_USER_PERMISSION_DOCUMENT_DELETE } from '../user-permissions/constants.js'; import { manifests as createBlueprintManifests } from './create-blueprint/manifests.js'; import { manifests as createManifests } from './create/manifests.js'; import { manifests as cultureAndHostnamesManifests } from './culture-and-hostnames/manifests.js'; @@ -13,11 +9,7 @@ import { manifests as moveManifests } from './move-to/manifests.js'; import { manifests as publicAccessManifests } from './public-access/manifests.js'; import { manifests as sortChildrenOfManifests } from './sort-children-of/manifests.js'; import { manifests as notificationManifests } from './notifications/manifests.js'; - -import { - UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS, - UMB_ENTITY_IS_TRASHED_CONDITION_ALIAS, -} from '@umbraco-cms/backoffice/recycle-bin'; +import { UMB_ENTITY_IS_TRASHED_CONDITION_ALIAS } from '@umbraco-cms/backoffice/recycle-bin'; const entityActions: Array = [ { @@ -40,52 +32,6 @@ const entityActions: Array = [ }, ], }, - { - type: 'entityAction', - kind: 'default', - alias: 'Umb.EntityAction.Document.Publish', - name: 'Publish Document Entity Action', - weight: 600, - api: () => import('./publish.action.js'), - forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], - meta: { - icon: 'icon-globe', - label: '#actions_publish', - additionalOptions: true, - }, - conditions: [ - { - alias: 'Umb.Condition.UserPermission.Document', - allOf: [UMB_USER_PERMISSION_DOCUMENT_PUBLISH], - }, - { - alias: UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS, - }, - ], - }, - { - type: 'entityAction', - kind: 'default', - alias: 'Umb.EntityAction.Document.Unpublish', - name: 'Unpublish Document Entity Action', - weight: 500, - api: () => import('./unpublish.action.js'), - forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], - meta: { - icon: 'icon-globe', - label: '#actions_unpublish', - additionalOptions: true, - }, - conditions: [ - { - alias: 'Umb.Condition.UserPermission.Document', - allOf: [UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH], - }, - { - alias: UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS, - }, - ], - }, /* TODO: Implement Permissions Entity Action { type: 'entityAction', diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-bulk-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-bulk-actions/manifests.ts index 986f5405bfae..65ee8658fc9a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-bulk-actions/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-bulk-actions/manifests.ts @@ -1,67 +1,5 @@ -import { UMB_DOCUMENT_COLLECTION_ALIAS } from '../collection/constants.js'; -import { UMB_DOCUMENT_ENTITY_TYPE } from '../entity.js'; -import { - UMB_USER_PERMISSION_DOCUMENT_PUBLISH, - UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH, -} from '../user-permissions/constants.js'; import { manifests as duplicateToManifests } from './duplicate-to/manifests.js'; import { manifests as moveToManifests } from './move-to/manifests.js'; import { manifests as trashManifests } from './trash/manifests.js'; -import { UMB_COLLECTION_ALIAS_CONDITION } from '@umbraco-cms/backoffice/collection'; -import type { ManifestEntityBulkAction } from '@umbraco-cms/backoffice/extension-registry'; -export const entityBulkActions: Array = [ - { - type: 'entityBulkAction', - kind: 'default', - alias: 'Umb.EntityBulkAction.Document.Publish', - name: 'Publish Document Entity Bulk Action', - weight: 50, - api: () => import('./publish/publish.action.js'), - meta: { - icon: 'icon-globe', - label: '#actions_publish', - }, - forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], - conditions: [ - { - alias: UMB_COLLECTION_ALIAS_CONDITION, - match: UMB_DOCUMENT_COLLECTION_ALIAS, - }, - { - alias: 'Umb.Condition.UserPermission.Document', - allOf: [UMB_USER_PERMISSION_DOCUMENT_PUBLISH], - }, - ], - }, - { - type: 'entityBulkAction', - kind: 'default', - alias: 'Umb.EntityBulkAction.Document.Unpublish', - name: 'Unpublish Document Entity Bulk Action', - weight: 40, - api: () => import('./unpublish/unpublish.action.js'), - meta: { - icon: 'icon-globe', - label: '#actions_unpublish', - }, - forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], - conditions: [ - { - alias: UMB_COLLECTION_ALIAS_CONDITION, - match: UMB_DOCUMENT_COLLECTION_ALIAS, - }, - { - alias: 'Umb.Condition.UserPermission.Document', - allOf: [UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH], - }, - ], - }, -]; - -export const manifests: Array = [ - ...entityBulkActions, - ...duplicateToManifests, - ...moveToManifests, - ...trashManifests, -]; +export const manifests: Array = [...duplicateToManifests, ...moveToManifests, ...trashManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/index.ts index 08160633bdc6..b172085015d5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/index.ts @@ -6,6 +6,8 @@ export * from './entity-actions/index.js'; export * from './constants.js'; export * from './global-contexts/index.js'; export * from './modals/index.js'; +export * from './paths.js'; +export * from './publishing/index.js'; export * from './recycle-bin/index.js'; export * from './reference/index.js'; export * from './repository/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/manifests.ts index 8b4d51bb6e6e..bb29839ea2ff 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/manifests.ts @@ -6,6 +6,7 @@ import { manifests as menuManifests } from './menu/manifests.js'; import { manifests as modalManifests } from './modals/manifests.js'; import { manifests as pickerManifests } from './picker/manifests.js'; import { manifests as propertyEditorManifests } from './property-editors/manifests.js'; +import { manifests as publishingManifests } from './publishing/manifests.js'; import { manifests as recycleBinManifests } from './recycle-bin/manifests.js'; import { manifests as repositoryManifests } from './repository/manifests.js'; import { manifests as rollbackManifests } from './rollback/manifests.js'; @@ -26,6 +27,7 @@ export const manifests: Array = ...modalManifests, ...pickerManifests, ...propertyEditorManifests, + ...publishingManifests, ...recycleBinManifests, ...repositoryManifests, ...rollbackManifests, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/constants.ts index afd26627425a..9f9e2d7731b2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/constants.ts @@ -1,6 +1,2 @@ -export * from './publish-modal/constants.js'; -export * from './publish-with-descendants-modal/constants.js'; export * from './save-modal/constants.js'; -export * from './unpublish-modal/constants.js'; -export * from './schedule-modal/constants.js'; export * from './document-picker-modal.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/manifests.ts index 9330615f56ce..ceb3f5196883 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/manifests.ts @@ -1,13 +1,3 @@ -import { manifest as publishModalManifest } from './publish-modal/manifest.js'; -import { manifest as publishWithDescendantsModalManifest } from './publish-with-descendants-modal/manifest.js'; import { manifest as saveModalManifest } from './save-modal/manifest.js'; -import { manifest as scheduleModalManifest } from './schedule-modal/manifest.js'; -import { manifest as unpublishModalManifest } from './unpublish-modal/manifest.js'; -export const manifests: Array = [ - publishModalManifest, - publishWithDescendantsModalManifest, - saveModalManifest, - scheduleModalManifest, - unpublishModalManifest, -]; +export const manifests: Array = [saveModalManifest]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-modal/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-modal/constants.ts deleted file mode 100644 index 0447b921930f..000000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-modal/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './document-publish-modal.token.js'; -export { UMB_DOCUMENT_PUBLISH_MODAL_ALIAS } from './manifest.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-modal/manifest.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-modal/manifest.ts deleted file mode 100644 index 3b206fd39aa5..000000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-modal/manifest.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { ManifestModal } from '@umbraco-cms/backoffice/modal'; - -export const UMB_DOCUMENT_PUBLISH_MODAL_ALIAS = 'Umb.Modal.DocumentPublish'; - -export const manifest: ManifestModal = { - type: 'modal', - alias: UMB_DOCUMENT_PUBLISH_MODAL_ALIAS, - name: 'Document Publish Modal', - element: () => import('./document-publish-modal.element.js'), -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-with-descendants-modal/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-with-descendants-modal/constants.ts deleted file mode 100644 index 4e75826676ff..000000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-with-descendants-modal/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './document-publish-with-descendants-modal.token.js'; -export { UMB_DOCUMENT_PUBLISH_WITH_DESCENDANTS_MODAL_ALIAS } from './manifest.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-with-descendants-modal/manifest.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-with-descendants-modal/manifest.ts deleted file mode 100644 index 31480cf8e254..000000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-with-descendants-modal/manifest.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { ManifestModal } from '@umbraco-cms/backoffice/modal'; - -export const UMB_DOCUMENT_PUBLISH_WITH_DESCENDANTS_MODAL_ALIAS = 'Umb.Modal.DocumentPublishWithDescendants'; - -export const manifest: ManifestModal = { - type: 'modal', - alias: UMB_DOCUMENT_PUBLISH_WITH_DESCENDANTS_MODAL_ALIAS, - name: 'Document Publish With Descendants Modal', - element: () => import('./document-publish-with-descendants-modal.element.js'), -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/schedule-modal/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/schedule-modal/constants.ts deleted file mode 100644 index 888ebd02cd2b..000000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/schedule-modal/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './document-schedule-modal.token.js'; -export { UMB_DOCUMENT_SCHEDULE_MODAL_ALIAS } from './manifest.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/schedule-modal/manifest.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/schedule-modal/manifest.ts deleted file mode 100644 index 91e46575fca1..000000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/schedule-modal/manifest.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { ManifestModal } from '@umbraco-cms/backoffice/modal'; - -export const UMB_DOCUMENT_SCHEDULE_MODAL_ALIAS = 'Umb.Modal.DocumentSchedule'; - -export const manifest: ManifestModal = { - type: 'modal', - alias: UMB_DOCUMENT_SCHEDULE_MODAL_ALIAS, - name: 'Document Schedule Modal', - element: () => import('./document-schedule-modal.element.js'), -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/types.ts index 161b986caeaf..98ad1aeb5014 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/types.ts @@ -4,9 +4,5 @@ import type { UmbContentVariantPickerData, UmbContentVariantPickerValue } from ' export type UmbDocumentVariantPickerData = UmbContentVariantPickerData; export type UmbDocumentVariantPickerValue = UmbContentVariantPickerValue; -export type * from './publish-modal/constants.js'; -export type * from './publish-with-descendants-modal/constants.js'; export type * from './save-modal/constants.js'; -export type * from './unpublish-modal/constants.js'; -export type * from './schedule-modal/constants.js'; export type * from './document-picker-modal.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/unpublish-modal/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/unpublish-modal/constants.ts deleted file mode 100644 index 43b40e4c546e..000000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/unpublish-modal/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './document-unpublish-modal.token.js'; -export { UMB_DOCUMENT_UNPUBLISH_MODAL_ALIAS } from './manifest.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/unpublish-modal/manifest.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/unpublish-modal/manifest.ts deleted file mode 100644 index 20004762bfaf..000000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/unpublish-modal/manifest.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { ManifestModal } from '@umbraco-cms/backoffice/modal'; - -export const UMB_DOCUMENT_UNPUBLISH_MODAL_ALIAS = 'Umb.Modal.DocumentUnpublish'; - -export const manifest: ManifestModal = { - type: 'modal', - alias: UMB_DOCUMENT_UNPUBLISH_MODAL_ALIAS, - name: 'Document Unpublish Modal', - element: () => import('./document-unpublish-modal.element.js'), -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/constants.ts new file mode 100644 index 000000000000..bf9ca11c4967 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/constants.ts @@ -0,0 +1,6 @@ +export * from './publish-with-descendants/constants.js'; +export * from './publish/constants.js'; +export * from './repository/constants.js'; +export * from './schedule-publish/constants.js'; +export * from './unpublish/constants.js'; +export * from './workspace-context/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/index.ts new file mode 100644 index 000000000000..aa2c6df15201 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/index.ts @@ -0,0 +1,4 @@ +export * from './repository/index.js'; +export * from './constants.js'; + +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/manifests.ts new file mode 100644 index 000000000000..3d67592fff1d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/manifests.ts @@ -0,0 +1,15 @@ +import { manifests as publishManifest } from './publish/manifests.js'; +import { manifests as publishWithDescendantsManifest } from './publish-with-descendants/manifests.js'; +import { manifests as repositoryManifests } from './repository/manifests.js'; +import { manifests as schedulePublishManifests } from './schedule-publish/manifests.js'; +import { manifests as unpublishManifests } from './unpublish/manifests.js'; +import { manifests as workspaceContextManifests } from './workspace-context/manifests.js'; + +export const manifests: Array = [ + ...publishManifest, + ...publishWithDescendantsManifest, + ...repositoryManifests, + ...schedulePublishManifests, + ...unpublishManifests, + ...workspaceContextManifests, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/pending-changes/document-published-pending-changes.manager.test.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/pending-changes/document-published-pending-changes.manager.test.ts new file mode 100644 index 000000000000..505ca53bc6dd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/pending-changes/document-published-pending-changes.manager.test.ts @@ -0,0 +1,217 @@ +import { expect } from '@open-wc/testing'; +import { Observable } from '@umbraco-cms/backoffice/external/rxjs'; +import { customElement } from '@umbraco-cms/backoffice/external/lit'; +import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api'; +import { UmbDocumentPublishedPendingChangesManager } from './document-published-pending-changes.manager.js'; +import { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api'; +import { type UmbDocumentDetailModel } from '../../types.js'; +import { UMB_DOCUMENT_ENTITY_TYPE } from '../../entity.js'; + +@customElement('test-my-controller-host') +class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} + +describe('UmbSelectionManager', () => { + let manager: UmbDocumentPublishedPendingChangesManager; + + beforeEach(() => { + const hostElement = new UmbTestControllerHostElement(); + manager = new UmbDocumentPublishedPendingChangesManager(hostElement); + }); + + describe('Public API', () => { + describe('properties', () => { + it('has a variantsWithChanges property', () => { + expect(manager).to.have.property('variantsWithChanges').to.be.an.instanceOf(Observable); + }); + }); + + describe('methods', () => { + it('has a process method', () => { + expect(manager).to.have.property('process').that.is.a('function'); + }); + + it('has a getVariantsWithChanges method', () => { + expect(manager).to.have.property('getVariantsWithChanges').that.is.a('function'); + }); + }); + }); + + describe('process', () => { + beforeEach(() => { + const hostElement = new UmbTestControllerHostElement(); + manager = new UmbDocumentPublishedPendingChangesManager(hostElement); + }); + + describe('invariant data', () => { + let persistedDocument: UmbDocumentDetailModel; + let publishedDocument: UmbDocumentDetailModel; + let documentBase: UmbDocumentDetailModel = { + entityType: UMB_DOCUMENT_ENTITY_TYPE, + urls: [ + { + culture: 'en-US', + url: '/document-1', + }, + ], + template: null, + unique: '1', + documentType: { + unique: 'document-type-1', + icon: 'icon-document', + collection: null, + }, + isTrashed: false, + variants: [ + { + state: DocumentVariantStateModel.PUBLISHED, + publishDate: '2023-02-06T15:32:24.957009', + culture: null, + segment: null, + name: 'Document 1', + createDate: '2023-02-06T15:32:05.350038', + updateDate: '2023-02-06T15:32:24.957009', + }, + ], + values: [ + { + editorAlias: 'Umbraco.TextBox', + alias: 'prop1', + culture: null, + segment: null, + value: '', + }, + ], + }; + + beforeEach(() => { + persistedDocument = structuredClone(documentBase); + publishedDocument = structuredClone(documentBase); + }); + + it('should set variantsWithChanges to an empty array if there are no pending changes', async () => { + await manager.process({ persistedData: persistedDocument, publishedData: publishedDocument }); + expect(manager.getVariantsWithChanges()).to.be.an('array').that.is.empty; + }); + + it('should have variants with changes when value is updated', async () => { + persistedDocument.values[0].value = 'value'; + await manager.process({ persistedData: persistedDocument, publishedData: publishedDocument }); + const variantsWithChanges = manager.getVariantsWithChanges(); + expect(variantsWithChanges).to.have.lengthOf(1); + expect(variantsWithChanges[0].variantId.toString()).to.equal('invariant'); + }); + + it('should have variants with changes when name of variant is updated', async () => { + persistedDocument.variants[0].name = 'Document 1 Updated'; + await manager.process({ persistedData: persistedDocument, publishedData: publishedDocument }); + const variantsWithChanges = manager.getVariantsWithChanges(); + expect(variantsWithChanges).to.have.lengthOf(1); + expect(variantsWithChanges[0].variantId.toString()).to.equal('invariant'); + }); + }); + + describe('variant data', () => { + let persistedDocument: UmbDocumentDetailModel; + let publishedDocument: UmbDocumentDetailModel; + let documentBase: UmbDocumentDetailModel = { + entityType: UMB_DOCUMENT_ENTITY_TYPE, + urls: [ + { + culture: 'en-US', + url: '/document-1', + }, + ], + template: null, + unique: '1', + documentType: { + unique: 'document-type-1', + icon: 'icon-document', + collection: null, + }, + isTrashed: false, + variants: [ + { + state: DocumentVariantStateModel.PUBLISHED, + publishDate: '2023-02-06T15:32:24.957009', + culture: 'en-US', + segment: null, + name: 'Document 1 (en-US)', + createDate: '2023-02-06T15:32:05.350038', + updateDate: '2023-02-06T15:32:24.957009', + }, + { + state: DocumentVariantStateModel.PUBLISHED, + publishDate: '2023-02-06T15:32:24.957009', + culture: 'da-DK', + segment: null, + name: 'Document 1 (da-DK)', + createDate: '2023-02-06T15:32:05.350038', + updateDate: '2023-02-06T15:32:24.957009', + }, + ], + values: [ + { + editorAlias: 'Umbraco.TextBox', + alias: 'prop1', + culture: 'en-US', + segment: null, + value: '', + }, + { + editorAlias: 'Umbraco.TextBox', + alias: 'prop1', + culture: 'da-DK', + segment: null, + value: '', + }, + ], + }; + + beforeEach(() => { + persistedDocument = structuredClone(documentBase); + publishedDocument = structuredClone(documentBase); + }); + + it('should not have variants with changes when there there are no pending changes', async () => { + await manager.process({ persistedData: persistedDocument, publishedData: publishedDocument }); + expect(manager.getVariantsWithChanges()).to.be.an('array').that.is.empty; + }); + + it('should have variants with changes when value is updated', async () => { + persistedDocument.values[0].value = 'value (en-US)'; + await manager.process({ persistedData: persistedDocument, publishedData: publishedDocument }); + const variantsWithChanges = manager.getVariantsWithChanges(); + expect(variantsWithChanges).to.have.lengthOf(1); + expect(variantsWithChanges[0].variantId.toString()).to.equal('en-US'); + }); + + it('should have variants with changes when multiple values are updated', async () => { + persistedDocument.values[0].value = 'value (en-US)'; + persistedDocument.values[1].value = 'value (da-DK)'; + await manager.process({ persistedData: persistedDocument, publishedData: publishedDocument }); + const variantsWithChanges = manager.getVariantsWithChanges(); + expect(variantsWithChanges).to.have.lengthOf(2); + expect(variantsWithChanges[0].variantId.toString()).to.equal('en-US'); + expect(variantsWithChanges[1].variantId.toString()).to.equal('da-DK'); + }); + + it('should have variants with changes when name of variant is updated', async () => { + persistedDocument.variants[0].name = 'Document 1 (en-US) Updated'; + await manager.process({ persistedData: persistedDocument, publishedData: publishedDocument }); + const variantsWithChanges = manager.getVariantsWithChanges(); + expect(variantsWithChanges).to.have.lengthOf(1); + expect(variantsWithChanges[0].variantId.toString()).to.equal('en-US'); + }); + + it('should have variants with changes when name of multiple variants are updated', async () => { + persistedDocument.variants[0].name = 'Document 1 (en-US) Updated'; + persistedDocument.variants[1].name = 'Document 1 (da-DK) Updated'; + await manager.process({ persistedData: persistedDocument, publishedData: publishedDocument }); + const variantsWithChanges = manager.getVariantsWithChanges(); + expect(variantsWithChanges).to.have.lengthOf(2); + expect(variantsWithChanges[0].variantId.toString()).to.equal('en-US'); + expect(variantsWithChanges[1].variantId.toString()).to.equal('da-DK'); + }); + }); + }); +}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/pending-changes/document-published-pending-changes.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/pending-changes/document-published-pending-changes.manager.ts new file mode 100644 index 000000000000..371fe98a7597 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/pending-changes/document-published-pending-changes.manager.ts @@ -0,0 +1,90 @@ +import type { UmbDocumentVariantModel } from '../../types.js'; +import type { + UmbDocumentPublishedPendingChangesManagerProcessArgs, + UmbPublishedVariantWithPendingChanges, +} from './types.js'; +import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import { UmbMergeContentVariantDataController } from '@umbraco-cms/backoffice/content'; +import { jsonStringComparison, UmbArrayState } from '@umbraco-cms/backoffice/observable-api'; + +/** + * Manages the pending changes for a published document. + * @exports + * @class UmbDocumentPublishedPendingChangesManager + * @augments {UmbControllerBase} + */ +export class UmbDocumentPublishedPendingChangesManager extends UmbControllerBase { + #variantsWithChanges = new UmbArrayState([], (x) => x.variantId.toString()); + public readonly variantsWithChanges = this.#variantsWithChanges.asObservable(); + + /** + * Checks each variant if there are any pending changes to publish. + * @param {UmbDocumentPublishedPendingChangesManagerProcessArgs} args - The arguments for the process. + * @param {UmbDocumentDetailModel} args.persistedData - The persisted document data. + * @param {UmbDocumentDetailModel} args.publishedData - The published document data. + * @returns {Promise} + * @memberof UmbDocumentPublishedPendingChangesManager + */ + async process(args: UmbDocumentPublishedPendingChangesManagerProcessArgs): Promise { + if (!args.persistedData) throw new Error('Persisted data is missing'); + if (!args.publishedData) throw new Error('Published data is missing'); + if (args.persistedData.unique !== args.publishedData.unique) + throw new Error('Persisted and published data does not have the same unique'); + + const variantIds = args.persistedData.variants?.map((x) => UmbVariantId.Create(x)) ?? []; + + const pendingChangesPromises = variantIds.map(async (variantId) => { + const mergedData = await new UmbMergeContentVariantDataController(this).process( + args.publishedData, + args.persistedData, + [variantId], + [variantId], + ); + + const mergedDataClone = structuredClone(mergedData); + const publishedDataClone = structuredClone(args.publishedData); + + // remove dates from the comparison + mergedDataClone.variants.forEach((variant) => this.#cleanVariantForComparison(variant)); + publishedDataClone.variants.forEach((variant) => this.#cleanVariantForComparison(variant)); + + const hasChanges = jsonStringComparison(mergedDataClone, publishedDataClone) === false; + + if (hasChanges) { + return { variantId }; + } else { + return null; + } + }); + + const variantsWithPendingChanges = (await Promise.all(pendingChangesPromises)).filter((x) => x !== null); + + this.#variantsWithChanges.setValue(variantsWithPendingChanges); + } + + /** + * Gets the variants with changes. + * @returns {Array} {Array} + * @memberof UmbDocumentPublishedPendingChangesManager + */ + getVariantsWithChanges(): Array { + return this.#variantsWithChanges.getValue(); + } + + #cleanVariantForComparison = (variant: UmbDocumentVariantModel) => { + // The server seems to have some date mismatches when quickly + // fetching a document after a save and comparing it to the published version. + // This is a temporary workaround to not include these dates in the comparison. + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + delete variant.updateDate; + }; + + /** + * Clear all states/values, + */ + clear() { + this.#variantsWithChanges.setValue([]); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/pending-changes/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/pending-changes/index.ts new file mode 100644 index 000000000000..56fa2950d148 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/pending-changes/index.ts @@ -0,0 +1 @@ +export * from './document-published-pending-changes.manager.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/pending-changes/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/pending-changes/types.ts new file mode 100644 index 000000000000..84b75b32a7b0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/pending-changes/types.ts @@ -0,0 +1,11 @@ +import type { UmbDocumentDetailModel } from '../../types.js'; +import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; + +export interface UmbDocumentPublishedPendingChangesManagerProcessArgs { + persistedData: UmbDocumentDetailModel; + publishedData: UmbDocumentDetailModel; +} + +export interface UmbPublishedVariantWithPendingChanges { + variantId: UmbVariantId; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/constants.ts new file mode 100644 index 000000000000..26f4f0dd5f39 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/constants.ts @@ -0,0 +1 @@ +export * from './modal/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/manifests.ts new file mode 100644 index 000000000000..107aa5246281 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/manifests.ts @@ -0,0 +1,4 @@ +import { manifests as modalManifests } from './modal/manifests.js'; +import { manifests as workspaceActionManifests } from './workspace-action/manifests.js'; + +export const manifests: Array = [...modalManifests, ...workspaceActionManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/constants.ts new file mode 100644 index 000000000000..310db622dc29 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/constants.ts @@ -0,0 +1 @@ +export * from './document-publish-with-descendants-modal.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-with-descendants-modal/document-publish-with-descendants-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.element.ts similarity index 96% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-with-descendants-modal/document-publish-with-descendants-modal.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.element.ts index c9cb75c085a1..032ac944e523 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-with-descendants-modal/document-publish-with-descendants-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.element.ts @@ -1,5 +1,5 @@ -import { UmbDocumentVariantState, type UmbDocumentVariantOptionModel } from '../../types.js'; -import { isNotPublishedMandatory } from '../utils.js'; +import { UmbDocumentVariantState, type UmbDocumentVariantOptionModel } from '../../../types.js'; +import { isNotPublishedMandatory } from '../../utils.js'; import type { UmbDocumentPublishWithDescendantsModalData, UmbDocumentPublishWithDescendantsModalValue, @@ -9,7 +9,7 @@ import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbSelectionManager } from '@umbraco-cms/backoffice/utils'; -import '../shared/document-variant-language-picker.element.js'; +import '../../../modals/shared/document-variant-language-picker.element.js'; @customElement('umb-document-publish-with-descendants-modal') export class UmbDocumentPublishWithDescendantsModalElement extends UmbModalBaseElement< diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-with-descendants-modal/document-publish-with-descendants-modal.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.stories.ts similarity index 98% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-with-descendants-modal/document-publish-with-descendants-modal.stories.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.stories.ts index fa89fc495ac4..225dba77eacb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-with-descendants-modal/document-publish-with-descendants-modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.stories.ts @@ -1,6 +1,4 @@ -import './document-publish-with-descendants-modal.element.js'; - -import { UmbDocumentVariantState } from '../../types.js'; +import { UmbDocumentVariantState } from '../../../types.js'; import type { UmbDocumentPublishWithDescendantsModalData, UmbDocumentPublishWithDescendantsModalValue, @@ -9,6 +7,8 @@ import type { UmbDocumentPublishWithDescendantsModalElement } from './document-p import type { Meta, StoryObj } from '@storybook/web-components'; import { html } from '@umbraco-cms/backoffice/external/lit'; +import './document-publish-with-descendants-modal.element.js'; + const modalData: UmbDocumentPublishWithDescendantsModalData = { options: [ { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-with-descendants-modal/document-publish-with-descendants-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.token.ts similarity index 82% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-with-descendants-modal/document-publish-with-descendants-modal.token.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.token.ts index 75b7729a5aee..3e25770093df 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-with-descendants-modal/document-publish-with-descendants-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.token.ts @@ -1,7 +1,8 @@ -import type { UmbDocumentVariantPickerData, UmbDocumentVariantPickerValue } from '../types.js'; -import { UMB_DOCUMENT_PUBLISH_WITH_DESCENDANTS_MODAL_ALIAS } from './manifest.js'; +import type { UmbDocumentVariantPickerData, UmbDocumentVariantPickerValue } from '../../../types.js'; import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; +export const UMB_DOCUMENT_PUBLISH_WITH_DESCENDANTS_MODAL_ALIAS = 'Umb.Modal.DocumentPublishWithDescendants'; + // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface UmbDocumentPublishWithDescendantsModalData extends UmbDocumentVariantPickerData {} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/manifests.ts new file mode 100644 index 000000000000..48f49f173344 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/manifests.ts @@ -0,0 +1,10 @@ +import { UMB_DOCUMENT_PUBLISH_WITH_DESCENDANTS_MODAL_ALIAS } from './constants.js'; + +export const manifests: Array = [ + { + type: 'modal', + alias: UMB_DOCUMENT_PUBLISH_WITH_DESCENDANTS_MODAL_ALIAS, + name: 'Document Publish With Descendants Modal', + element: () => import('./document-publish-with-descendants-modal.element.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/types.ts new file mode 100644 index 000000000000..310db622dc29 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/types.ts @@ -0,0 +1 @@ +export * from './document-publish-with-descendants-modal.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/types.ts new file mode 100644 index 000000000000..4aeeacf57dd0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/types.ts @@ -0,0 +1 @@ +export type * from './modal/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/workspace-action/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/workspace-action/manifests.ts new file mode 100644 index 000000000000..4f60dd42104c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/workspace-action/manifests.ts @@ -0,0 +1,30 @@ +import { + UMB_USER_PERMISSION_DOCUMENT_PUBLISH, + UMB_USER_PERMISSION_DOCUMENT_UPDATE, +} from '../../../user-permissions/constants.js'; +import { UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS } from '@umbraco-cms/backoffice/recycle-bin'; + +export const manifests: Array = [ + { + type: 'workspaceActionMenuItem', + kind: 'default', + alias: 'Umb.Document.WorkspaceActionMenuItem.PublishWithDescendants', + name: 'Publish with descendants', + weight: 10, + api: () => import('./publish-with-descendants.action.js'), + forWorkspaceActions: 'Umb.WorkspaceAction.Document.SaveAndPublish', + meta: { + label: '#buttons_publishDescendants', + icon: 'icon-globe', + }, + conditions: [ + { + alias: 'Umb.Condition.UserPermission.Document', + allOf: [UMB_USER_PERMISSION_DOCUMENT_UPDATE, UMB_USER_PERMISSION_DOCUMENT_PUBLISH], + }, + { + alias: UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS, + }, + ], + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/actions/publish-with-descendants.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/workspace-action/publish-with-descendants.action.ts similarity index 63% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/actions/publish-with-descendants.action.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/workspace-action/publish-with-descendants.action.ts index 45badee917c7..9e802ab80faa 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/actions/publish-with-descendants.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/workspace-action/publish-with-descendants.action.ts @@ -1,9 +1,9 @@ -import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '../document-workspace.context-token.js'; +import { UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT } from '../../workspace-context/constants.js'; import { UmbWorkspaceActionBase } from '@umbraco-cms/backoffice/workspace'; export class UmbDocumentPublishWithDescendantsWorkspaceAction extends UmbWorkspaceActionBase { override async execute() { - const workspaceContext = await this.getContext(UMB_DOCUMENT_WORKSPACE_CONTEXT); + const workspaceContext = await this.getContext(UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT); return workspaceContext.publishWithDescendants(); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/constants.ts new file mode 100644 index 000000000000..26f4f0dd5f39 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/constants.ts @@ -0,0 +1 @@ +export * from './modal/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/entity-action/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/entity-action/index.ts new file mode 100644 index 000000000000..91e19bc73546 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/entity-action/index.ts @@ -0,0 +1 @@ +export * from './publish.action.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/entity-action/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/entity-action/manifests.ts new file mode 100644 index 000000000000..f501f4a7f9a5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/entity-action/manifests.ts @@ -0,0 +1,29 @@ +import { UMB_DOCUMENT_ENTITY_TYPE } from '../../../entity.js'; +import { UMB_USER_PERMISSION_DOCUMENT_PUBLISH } from '../../../user-permissions/constants.js'; +import { UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS } from '@umbraco-cms/backoffice/recycle-bin'; + +export const manifests: Array = [ + { + type: 'entityAction', + kind: 'default', + alias: 'Umb.EntityAction.Document.Publish', + name: 'Publish Document Entity Action', + weight: 600, + api: () => import('./publish.action.js'), + forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], + meta: { + icon: 'icon-globe', + label: '#actions_publish', + additionalOptions: true, + }, + conditions: [ + { + alias: 'Umb.Condition.UserPermission.Document', + allOf: [UMB_USER_PERMISSION_DOCUMENT_PUBLISH], + }, + { + alias: UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS, + }, + ], + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/entity-action/publish.action.ts similarity index 94% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/entity-action/publish.action.ts index ff828b4a9eff..e74be197226a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/publish.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/entity-action/publish.action.ts @@ -1,6 +1,7 @@ -import { UMB_DOCUMENT_PUBLISH_MODAL } from '../modals/publish-modal/constants.js'; -import { UmbDocumentDetailRepository, UmbDocumentPublishingRepository } from '../repository/index.js'; -import type { UmbDocumentVariantOptionModel } from '../types.js'; +import type { UmbDocumentVariantOptionModel } from '../../../types.js'; +import { UMB_DOCUMENT_PUBLISH_MODAL } from '../modal/constants.js'; +import { UmbDocumentDetailRepository } from '../../../repository/index.js'; +import { UmbDocumentPublishingRepository } from '../../repository/index.js'; import { UMB_APP_LANGUAGE_CONTEXT, UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language'; import type { UmbEntityActionArgs } from '@umbraco-cms/backoffice/entity-action'; import { UmbEntityActionBase, UmbRequestReloadStructureForEntityEvent } from '@umbraco-cms/backoffice/entity-action'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/entity-bulk-action/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/entity-bulk-action/manifests.ts new file mode 100644 index 000000000000..9b50bc1ed379 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/entity-bulk-action/manifests.ts @@ -0,0 +1,30 @@ +import { UMB_DOCUMENT_ENTITY_TYPE } from '../../../entity.js'; +import { UMB_DOCUMENT_COLLECTION_ALIAS } from '../../../collection/constants.js'; +import { UMB_USER_PERMISSION_DOCUMENT_PUBLISH } from '../../../user-permissions/constants.js'; +import { UMB_COLLECTION_ALIAS_CONDITION } from '@umbraco-cms/backoffice/collection'; + +export const manifests: Array = [ + { + type: 'entityBulkAction', + kind: 'default', + alias: 'Umb.EntityBulkAction.Document.Publish', + name: 'Publish Document Entity Bulk Action', + weight: 50, + api: () => import('./publish.bulk-action.js'), + meta: { + icon: 'icon-globe', + label: '#actions_publish', + }, + forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], + conditions: [ + { + alias: UMB_COLLECTION_ALIAS_CONDITION, + match: UMB_DOCUMENT_COLLECTION_ALIAS, + }, + { + alias: 'Umb.Condition.UserPermission.Document', + allOf: [UMB_USER_PERMISSION_DOCUMENT_PUBLISH], + }, + ], + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-bulk-actions/publish/publish.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/entity-bulk-action/publish.bulk-action.ts similarity index 92% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-bulk-actions/publish/publish.action.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/entity-bulk-action/publish.bulk-action.ts index f9812adc84c5..541d693ae630 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-bulk-actions/publish/publish.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/entity-bulk-action/publish.bulk-action.ts @@ -1,8 +1,8 @@ -import { UmbDocumentPublishingRepository } from '../../repository/index.js'; -import { UmbPublishDocumentEntityAction } from '../../entity-actions/publish.action.js'; -import type { UmbDocumentVariantOptionModel } from '../../types.js'; -import { UMB_DOCUMENT_PUBLISH_MODAL } from '../../constants.js'; -import { UMB_DOCUMENT_ENTITY_TYPE } from '../../entity.js'; +import { UmbDocumentPublishingRepository } from '../../index.js'; +import type { UmbDocumentVariantOptionModel } from '../../../types.js'; +import { UMB_DOCUMENT_PUBLISH_MODAL } from '../../../constants.js'; +import { UMB_DOCUMENT_ENTITY_TYPE } from '../../../entity.js'; +import { UmbPublishDocumentEntityAction } from '../entity-action/index.js'; import { UmbEntityBulkActionBase } from '@umbraco-cms/backoffice/entity-bulk-action'; import { UMB_APP_LANGUAGE_CONTEXT, UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language'; import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/manifests.ts new file mode 100644 index 000000000000..5859e55b5533 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/manifests.ts @@ -0,0 +1,11 @@ +import { manifests as entityActionManifests } from './entity-action/manifests.js'; +import { manifests as entityBulkActionManifests } from './entity-bulk-action/manifests.js'; +import { manifests as modalManifests } from './modal/manifests.js'; +import { manifests as workspaceActionManifests } from './workspace-action/manifests.js'; + +export const manifests: Array = [ + ...entityActionManifests, + ...entityBulkActionManifests, + ...modalManifests, + ...workspaceActionManifests, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/constants.ts new file mode 100644 index 000000000000..e2baa59079e1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/constants.ts @@ -0,0 +1 @@ +export * from './document-publish-modal.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-modal/document-publish-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/document-publish-modal.element.ts similarity index 95% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-modal/document-publish-modal.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/document-publish-modal.element.ts index 6fd010b29509..17429a0c989a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-modal/document-publish-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/document-publish-modal.element.ts @@ -1,12 +1,12 @@ -import { UmbDocumentVariantState, type UmbDocumentVariantOptionModel } from '../../types.js'; -import { isNotPublishedMandatory } from '../utils.js'; +import { UmbDocumentVariantState, type UmbDocumentVariantOptionModel } from '../../../types.js'; +import { isNotPublishedMandatory } from '../../utils.js'; import type { UmbDocumentPublishModalData, UmbDocumentPublishModalValue } from './document-publish-modal.token.js'; import { css, customElement, html, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbSelectionManager } from '@umbraco-cms/backoffice/utils'; -import '../shared/document-variant-language-picker.element.js'; +import '../../../modals/shared/document-variant-language-picker.element.js'; @customElement('umb-document-publish-modal') export class UmbDocumentPublishModalElement extends UmbModalBaseElement< diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-modal/document-publish-modal.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/document-publish-modal.stories.ts similarity index 98% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-modal/document-publish-modal.stories.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/document-publish-modal.stories.ts index a0b1856403db..e01619eb4b8d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-modal/document-publish-modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/document-publish-modal.stories.ts @@ -1,6 +1,6 @@ import './document-publish-modal.element.js'; -import { UmbDocumentVariantState } from '../../types.js'; +import { UmbDocumentVariantState } from '../../../types.js'; import type { UmbDocumentPublishModalData, UmbDocumentPublishModalValue } from './document-publish-modal.token.js'; import type { UmbDocumentPublishModalElement } from './document-publish-modal.element.js'; import type { Meta, StoryObj } from '@storybook/web-components'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-modal/document-publish-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/document-publish-modal.token.ts similarity index 83% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-modal/document-publish-modal.token.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/document-publish-modal.token.ts index eac95369149e..39382f14fd7c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-modal/document-publish-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/document-publish-modal.token.ts @@ -1,7 +1,8 @@ -import type { UmbDocumentVariantPickerData, UmbDocumentVariantPickerValue } from '../types.js'; -import { UMB_DOCUMENT_PUBLISH_MODAL_ALIAS } from './manifest.js'; +import type { UmbDocumentVariantPickerData, UmbDocumentVariantPickerValue } from '../../../modals/types.js'; import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; +export const UMB_DOCUMENT_PUBLISH_MODAL_ALIAS = 'Umb.Modal.DocumentPublish'; + // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface UmbDocumentPublishModalData extends UmbDocumentVariantPickerData {} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/manifests.ts new file mode 100644 index 000000000000..15a3fe254b20 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/manifests.ts @@ -0,0 +1,10 @@ +import { UMB_DOCUMENT_PUBLISH_MODAL_ALIAS } from './constants.js'; + +export const manifests: Array = [ + { + type: 'modal', + alias: UMB_DOCUMENT_PUBLISH_MODAL_ALIAS, + name: 'Document Publish Modal', + element: () => import('./document-publish-modal.element.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/types.ts new file mode 100644 index 000000000000..2741ef483e91 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/types.ts @@ -0,0 +1 @@ +export type * from './document-publish-modal.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/types.ts new file mode 100644 index 000000000000..4aeeacf57dd0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/types.ts @@ -0,0 +1 @@ +export type * from './modal/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/workspace-action/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/workspace-action/manifests.ts new file mode 100644 index 000000000000..63d88495d95a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/workspace-action/manifests.ts @@ -0,0 +1,28 @@ +import { UMB_DOCUMENT_WORKSPACE_ALIAS } from '../../../workspace/constants.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; +import { UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS } from '@umbraco-cms/backoffice/recycle-bin'; + +export const manifests: Array = [ + { + type: 'workspaceAction', + kind: 'default', + alias: 'Umb.WorkspaceAction.Document.SaveAndPublish', + name: 'Save And Publish Document Workspace Action', + weight: 70, + api: () => import('./save-and-publish.action.js'), + meta: { + label: '#buttons_saveAndPublish', + look: 'primary', + color: 'positive', + }, + conditions: [ + { + alias: UMB_WORKSPACE_CONDITION_ALIAS, + match: UMB_DOCUMENT_WORKSPACE_ALIAS, + }, + { + alias: UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS, + }, + ], + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/actions/save-and-publish.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/workspace-action/save-and-publish.action.ts similarity index 74% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/actions/save-and-publish.action.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/workspace-action/save-and-publish.action.ts index a040d57fbc70..23e6e3ab33a3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/actions/save-and-publish.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/workspace-action/save-and-publish.action.ts @@ -1,11 +1,11 @@ -import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '../document-workspace.context-token.js'; +import { UmbDocumentUserPermissionCondition } from '../../../user-permissions/conditions/document-user-permission.condition.js'; import { UMB_USER_PERMISSION_DOCUMENT_PUBLISH, UMB_USER_PERMISSION_DOCUMENT_UPDATE, -} from '../../user-permissions/constants.js'; -import { UmbDocumentUserPermissionCondition } from '../../user-permissions/conditions/document-user-permission.condition.js'; -import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +} from '../../../user-permissions/constants.js'; +import { UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT } from '../../workspace-context/constants.js'; import { UmbWorkspaceActionBase } from '@umbraco-cms/backoffice/workspace'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; export class UmbDocumentSaveAndPublishWorkspaceAction extends UmbWorkspaceActionBase { constructor(host: UmbControllerHost, args: any) { @@ -32,7 +32,7 @@ export class UmbDocumentSaveAndPublishWorkspaceAction extends UmbWorkspaceAction } override async execute() { - const workspaceContext = await this.getContext(UMB_DOCUMENT_WORKSPACE_CONTEXT); + const workspaceContext = await this.getContext(UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT); return workspaceContext.saveAndPublish(); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/publishing/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/constants.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/publishing/constants.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/constants.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/publishing/document-publishing.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/document-publishing.repository.ts similarity index 84% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/publishing/document-publishing.repository.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/document-publishing.repository.ts index 02e4e5de8853..8ab30046ed7c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/publishing/document-publishing.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/document-publishing.repository.ts @@ -1,7 +1,7 @@ -import type { UmbDocumentVariantPublishModel } from '../../types.js'; +import type { UmbDocumentDetailModel, UmbDocumentVariantPublishModel } from '../../types.js'; import { UmbDocumentPublishingServerDataSource } from './document-publishing.server.data-source.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository'; +import { UmbRepositoryBase, type UmbRepositoryResponse } from '@umbraco-cms/backoffice/repository'; import { UMB_NOTIFICATION_CONTEXT, type UmbNotificationContext } from '@umbraco-cms/backoffice/notification'; import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; @@ -93,6 +93,16 @@ export class UmbDocumentPublishingRepository extends UmbRepositoryBase { return { error }; } + + /** + * Get the published data of a document + * @param {string} unique Document unique + * @returns { Promise>} Published document + * @memberof UmbDocumentPublishingRepository + */ + async published(unique: string): Promise> { + return this.#publishingDataSource.published(unique); + } } export { UmbDocumentPublishingRepository as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/publishing/document-publishing.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/document-publishing.server.data-source.ts similarity index 64% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/publishing/document-publishing.server.data-source.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/document-publishing.server.data-source.ts index e17da4549e75..9c8d088d7f8c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/publishing/document-publishing.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/document-publishing.server.data-source.ts @@ -1,4 +1,5 @@ -import type { UmbDocumentVariantPublishModel } from '../../types.js'; +import type { UmbDocumentDetailModel, UmbDocumentVariantPublishModel } from '../../types.js'; +import { UMB_DOCUMENT_ENTITY_TYPE } from '../../entity.js'; import type { CultureAndScheduleRequestModel, PublishDocumentRequestModel, @@ -9,6 +10,7 @@ import { DocumentService } from '@umbraco-cms/backoffice/external/backend-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import type { UmbDataSourceResponse } from '@umbraco-cms/backoffice/repository'; /** * A server data source for Document publishing @@ -109,4 +111,64 @@ export class UmbDocumentPublishingServerDataSource { DocumentService.putDocumentByIdPublishWithDescendants({ id: unique, requestBody }), ); } + + /** + * Get the published Document by its unique + * @param {string} unique - Document unique + * @returns {Promise>} Published document + * @memberof UmbDocumentPublishingServerDataSource + */ + async published(unique: string): Promise> { + if (!unique) throw new Error('Unique is missing'); + + const { data, error } = await tryExecuteAndNotify( + this.#host, + DocumentService.getDocumentByIdPublished({ id: unique }), + ); + + if (error || !data) { + return { error }; + } + + // TODO: make data mapper to prevent errors + const document: UmbDocumentDetailModel = { + entityType: UMB_DOCUMENT_ENTITY_TYPE, + unique: data.id, + values: data.values.map((value) => { + return { + editorAlias: value.editorAlias, + alias: value.alias, + culture: value.culture || null, + segment: value.segment || null, + value: value.value, + }; + }), + variants: data.variants.map((variant) => { + return { + culture: variant.culture || null, + segment: variant.segment || null, + state: variant.state, + name: variant.name, + publishDate: variant.publishDate || null, + createDate: variant.createDate, + updateDate: variant.updateDate, + }; + }), + urls: data.urls.map((url) => { + return { + culture: url.culture || null, + url: url.url, + }; + }), + template: data.template ? { unique: data.template.id } : null, + documentType: { + unique: data.documentType.id, + collection: data.documentType.collection ? { unique: data.documentType.collection.id } : null, + icon: data.documentType.icon, + }, + isTrashed: data.isTrashed, + }; + + return { data: document }; + } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/publishing/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/index.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/publishing/index.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/index.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/publishing/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/manifests.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/publishing/manifests.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/manifests.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/constants.ts new file mode 100644 index 000000000000..26f4f0dd5f39 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/constants.ts @@ -0,0 +1 @@ +export * from './modal/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/manifests.ts new file mode 100644 index 000000000000..107aa5246281 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/manifests.ts @@ -0,0 +1,4 @@ +import { manifests as modalManifests } from './modal/manifests.js'; +import { manifests as workspaceActionManifests } from './workspace-action/manifests.js'; + +export const manifests: Array = [...modalManifests, ...workspaceActionManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/constants.ts new file mode 100644 index 000000000000..0307e54b1733 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/constants.ts @@ -0,0 +1 @@ +export * from './document-schedule-modal.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/schedule-modal/document-schedule-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/document-schedule-modal.element.ts similarity index 98% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/schedule-modal/document-schedule-modal.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/document-schedule-modal.element.ts index 0a5ffe751d48..9779a0f3092a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/schedule-modal/document-schedule-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/document-schedule-modal.element.ts @@ -1,5 +1,5 @@ -import { UmbDocumentVariantLanguagePickerElement } from '../shared/document-variant-language-picker.element.js'; -import { UmbDocumentVariantState, type UmbDocumentVariantOptionModel } from '../../types.js'; +import { UmbDocumentVariantState, type UmbDocumentVariantOptionModel } from '../../../types.js'; +import { UmbDocumentVariantLanguagePickerElement } from '../../../modals/index.js'; import type { UmbDocumentScheduleModalData, UmbDocumentScheduleModalValue } from './document-schedule-modal.token.js'; import { css, customElement, html, repeat, state, when } from '@umbraco-cms/backoffice/external/lit'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/schedule-modal/document-schedule-modal.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/document-schedule-modal.stories.ts similarity index 98% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/schedule-modal/document-schedule-modal.stories.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/document-schedule-modal.stories.ts index 06c55eb87959..de340a17b1d9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/schedule-modal/document-schedule-modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/document-schedule-modal.stories.ts @@ -1,6 +1,6 @@ import './document-schedule-modal.element.js'; -import { UmbDocumentVariantState } from '../../types.js'; +import { UmbDocumentVariantState } from '../../../types.js'; import type { UmbDocumentScheduleModalData, UmbDocumentScheduleModalValue } from './document-schedule-modal.token.js'; import type { UmbDocumentScheduleModalElement } from './document-schedule-modal.element.js'; import type { Meta, StoryObj } from '@storybook/web-components'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/schedule-modal/document-schedule-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/document-schedule-modal.token.ts similarity index 82% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/schedule-modal/document-schedule-modal.token.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/document-schedule-modal.token.ts index de103d9e54dd..fed3dcfb50bc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/schedule-modal/document-schedule-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/document-schedule-modal.token.ts @@ -1,8 +1,9 @@ -import type { UmbDocumentVariantPickerData } from '../types.js'; -import { UMB_DOCUMENT_SCHEDULE_MODAL_ALIAS } from './manifest.js'; +import type { UmbDocumentVariantPickerData } from '../../../modals/types.js'; import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; import type { ScheduleRequestModel } from '@umbraco-cms/backoffice/external/backend-api'; +export const UMB_DOCUMENT_SCHEDULE_MODAL_ALIAS = 'Umb.Modal.DocumentSchedule'; + export interface UmbDocumentScheduleSelectionModel { unique: string; schedule?: ScheduleRequestModel | null; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/manifests.ts new file mode 100644 index 000000000000..ad54a2ad7dbe --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/manifests.ts @@ -0,0 +1,10 @@ +import { UMB_DOCUMENT_SCHEDULE_MODAL_ALIAS } from './constants.js'; + +export const manifests: Array = [ + { + type: 'modal', + alias: UMB_DOCUMENT_SCHEDULE_MODAL_ALIAS, + name: 'Document Schedule Modal', + element: () => import('./document-schedule-modal.element.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/types.ts new file mode 100644 index 000000000000..4367880d8c6d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/types.ts @@ -0,0 +1 @@ +export type * from './document-schedule-modal.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/types.ts new file mode 100644 index 000000000000..4aeeacf57dd0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/types.ts @@ -0,0 +1 @@ +export type * from './modal/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/workspace-action/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/workspace-action/manifests.ts new file mode 100644 index 000000000000..d9697986cabe --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/workspace-action/manifests.ts @@ -0,0 +1,30 @@ +import { + UMB_USER_PERMISSION_DOCUMENT_PUBLISH, + UMB_USER_PERMISSION_DOCUMENT_UPDATE, +} from '../../../user-permissions/constants.js'; +import { UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS } from '@umbraco-cms/backoffice/recycle-bin'; + +export const manifests: Array = [ + { + type: 'workspaceActionMenuItem', + kind: 'default', + alias: 'Umb.Document.WorkspaceActionMenuItem.SchedulePublishing', + name: 'Schedule publishing', + weight: 20, + api: () => import('./save-and-schedule.action.js'), + forWorkspaceActions: 'Umb.WorkspaceAction.Document.SaveAndPublish', + meta: { + label: '#buttons_schedulePublish', + icon: 'icon-globe', + }, + conditions: [ + { + alias: 'Umb.Condition.UserPermission.Document', + allOf: [UMB_USER_PERMISSION_DOCUMENT_UPDATE, UMB_USER_PERMISSION_DOCUMENT_PUBLISH], + }, + { + alias: UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS, + }, + ], + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/actions/save-and-schedule.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/workspace-action/save-and-schedule.action.ts similarity index 60% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/actions/save-and-schedule.action.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/workspace-action/save-and-schedule.action.ts index 7ce215746867..14cec080ef5a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/actions/save-and-schedule.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/workspace-action/save-and-schedule.action.ts @@ -1,9 +1,9 @@ -import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '../document-workspace.context-token.js'; +import { UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT } from '../../workspace-context/constants.js'; import { UmbWorkspaceActionBase } from '@umbraco-cms/backoffice/workspace'; export class UmbDocumentSaveAndScheduleWorkspaceAction extends UmbWorkspaceActionBase { override async execute() { - const workspaceContext = await this.getContext(UMB_DOCUMENT_WORKSPACE_CONTEXT); + const workspaceContext = await this.getContext(UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT); return workspaceContext.schedule(); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/types.ts new file mode 100644 index 000000000000..895cefd24991 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/types.ts @@ -0,0 +1,5 @@ +export type * from './publish-with-descendants/types.js'; +export type * from './publish/types.js'; +export type * from './schedule-publish/types.js'; +export type * from './unpublish/types.js'; +export type * from './workspace-context/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/constants.ts new file mode 100644 index 000000000000..26f4f0dd5f39 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/constants.ts @@ -0,0 +1 @@ +export * from './modal/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/entity-action/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/entity-action/index.ts new file mode 100644 index 000000000000..4993625d1f29 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/entity-action/index.ts @@ -0,0 +1 @@ +export * from './unpublish.action.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/entity-action/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/entity-action/manifests.ts new file mode 100644 index 000000000000..436dff320a34 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/entity-action/manifests.ts @@ -0,0 +1,29 @@ +import { UMB_DOCUMENT_ENTITY_TYPE } from '../../../entity.js'; +import { UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH } from '../../../user-permissions/constants.js'; +import { UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS } from '@umbraco-cms/backoffice/recycle-bin'; + +export const manifests: Array = [ + { + type: 'entityAction', + kind: 'default', + alias: 'Umb.EntityAction.Document.Unpublish', + name: 'Unpublish Document Entity Action', + weight: 500, + api: () => import('./unpublish.action.js'), + forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], + meta: { + icon: 'icon-globe', + label: '#actions_unpublish', + additionalOptions: true, + }, + conditions: [ + { + alias: 'Umb.Condition.UserPermission.Document', + allOf: [UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH], + }, + { + alias: UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS, + }, + ], + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/entity-action/unpublish.action.ts similarity index 95% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/entity-action/unpublish.action.ts index 58b5ff1ffbfe..5aa6e4125d15 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/entity-action/unpublish.action.ts @@ -1,6 +1,7 @@ -import { UmbDocumentDetailRepository, UmbDocumentPublishingRepository } from '../repository/index.js'; -import type { UmbDocumentVariantOptionModel } from '../types.js'; +import type { UmbDocumentVariantOptionModel } from '../../../types.js'; import { UMB_DOCUMENT_UNPUBLISH_MODAL } from '../constants.js'; +import { UmbDocumentDetailRepository } from '../../../repository/index.js'; +import { UmbDocumentPublishingRepository } from '../../repository/index.js'; import { UMB_APP_LANGUAGE_CONTEXT, UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language'; import { type UmbEntityActionArgs, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/entity-bulk-action/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/entity-bulk-action/manifests.ts new file mode 100644 index 000000000000..e473c143633d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/entity-bulk-action/manifests.ts @@ -0,0 +1,30 @@ +import { UMB_DOCUMENT_ENTITY_TYPE } from '../../../entity.js'; +import { UMB_DOCUMENT_COLLECTION_ALIAS } from '../../../collection/constants.js'; +import { UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH } from '../../../user-permissions/constants.js'; +import { UMB_COLLECTION_ALIAS_CONDITION } from '@umbraco-cms/backoffice/collection'; + +export const manifests: Array = [ + { + type: 'entityBulkAction', + kind: 'default', + alias: 'Umb.EntityBulkAction.Document.Unpublish', + name: 'Unpublish Document Entity Bulk Action', + weight: 40, + api: () => import('./unpublish.bulk-action.js'), + meta: { + icon: 'icon-globe', + label: '#actions_unpublish', + }, + forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], + conditions: [ + { + alias: UMB_COLLECTION_ALIAS_CONDITION, + match: UMB_DOCUMENT_COLLECTION_ALIAS, + }, + { + alias: 'Umb.Condition.UserPermission.Document', + allOf: [UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH], + }, + ], + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-bulk-actions/unpublish/unpublish.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/entity-bulk-action/unpublish.bulk-action.ts similarity index 95% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-bulk-actions/unpublish/unpublish.action.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/entity-bulk-action/unpublish.bulk-action.ts index 4f07398ee5c1..77f3f3f87dfd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-bulk-actions/unpublish/unpublish.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/entity-bulk-action/unpublish.bulk-action.ts @@ -1,7 +1,7 @@ -import { UmbUnpublishDocumentEntityAction } from '../../entity-actions/unpublish.action.js'; +import { UmbUnpublishDocumentEntityAction } from '../entity-action/index.js'; +import type { UmbDocumentVariantOptionModel } from '../../../types.js'; +import { UMB_DOCUMENT_ENTITY_TYPE, UMB_DOCUMENT_UNPUBLISH_MODAL } from '../../../constants.js'; import { UmbDocumentPublishingRepository } from '../../repository/index.js'; -import type { UmbDocumentVariantOptionModel } from '../../types.js'; -import { UMB_DOCUMENT_ENTITY_TYPE, UMB_DOCUMENT_UNPUBLISH_MODAL } from '../../constants.js'; import { UMB_CONFIRM_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import { UmbEntityBulkActionBase } from '@umbraco-cms/backoffice/entity-bulk-action'; import { UMB_APP_LANGUAGE_CONTEXT, UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/index.ts new file mode 100644 index 000000000000..67d897c5ab4b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/index.ts @@ -0,0 +1 @@ +export * from './entity-action/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/manifests.ts new file mode 100644 index 000000000000..5859e55b5533 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/manifests.ts @@ -0,0 +1,11 @@ +import { manifests as entityActionManifests } from './entity-action/manifests.js'; +import { manifests as entityBulkActionManifests } from './entity-bulk-action/manifests.js'; +import { manifests as modalManifests } from './modal/manifests.js'; +import { manifests as workspaceActionManifests } from './workspace-action/manifests.js'; + +export const manifests: Array = [ + ...entityActionManifests, + ...entityBulkActionManifests, + ...modalManifests, + ...workspaceActionManifests, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/constants.ts new file mode 100644 index 000000000000..1d9210a7a55f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/constants.ts @@ -0,0 +1 @@ +export * from './document-unpublish-modal.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/unpublish-modal/document-unpublish-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/document-unpublish-modal.element.ts similarity index 95% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/unpublish-modal/document-unpublish-modal.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/document-unpublish-modal.element.ts index e9a32c905c54..aa49be745535 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/unpublish-modal/document-unpublish-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/document-unpublish-modal.element.ts @@ -1,6 +1,6 @@ -import { UmbDocumentVariantState, type UmbDocumentVariantOptionModel } from '../../types.js'; -import { UmbDocumentReferenceRepository } from '../../reference/index.js'; -import { UMB_DOCUMENT_CONFIGURATION_CONTEXT } from '../../global-contexts/index.js'; +import { UmbDocumentVariantState, type UmbDocumentVariantOptionModel } from '../../../types.js'; +import { UmbDocumentReferenceRepository } from '../../../reference/index.js'; +import { UMB_DOCUMENT_CONFIGURATION_CONTEXT } from '../../../global-contexts/index.js'; import type { UmbDocumentUnpublishModalData, UmbDocumentUnpublishModalValue, @@ -10,7 +10,7 @@ import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbSelectionManager } from '@umbraco-cms/backoffice/utils'; -import '../shared/document-variant-language-picker.element.js'; +import '../../../modals/shared/document-variant-language-picker.element.js'; /** * @function isPublished diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/unpublish-modal/document-unpublish-modal.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/document-unpublish-modal.stories.ts similarity index 98% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/unpublish-modal/document-unpublish-modal.stories.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/document-unpublish-modal.stories.ts index 258c2b8c99a5..39edb84b3753 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/unpublish-modal/document-unpublish-modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/document-unpublish-modal.stories.ts @@ -1,6 +1,6 @@ import './document-unpublish-modal.element.js'; -import { UmbDocumentVariantState } from '../../types.js'; +import { UmbDocumentVariantState } from '../../../types.js'; import type { UmbDocumentUnpublishModalData, UmbDocumentUnpublishModalValue, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/unpublish-modal/document-unpublish-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/document-unpublish-modal.token.ts similarity index 82% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/unpublish-modal/document-unpublish-modal.token.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/document-unpublish-modal.token.ts index 35ca5fef5cd2..305923083406 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/unpublish-modal/document-unpublish-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/document-unpublish-modal.token.ts @@ -1,7 +1,8 @@ -import type { UmbDocumentVariantPickerData, UmbDocumentVariantPickerValue } from '../types.js'; -import { UMB_DOCUMENT_UNPUBLISH_MODAL_ALIAS } from './manifest.js'; +import type { UmbDocumentVariantPickerData, UmbDocumentVariantPickerValue } from '../../../modals/types.js'; import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; +export const UMB_DOCUMENT_UNPUBLISH_MODAL_ALIAS = 'Umb.Modal.DocumentUnpublish'; + export interface UmbDocumentUnpublishModalData extends UmbDocumentVariantPickerData { documentUnique?: string; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/manifests.ts new file mode 100644 index 000000000000..5aeea96318d2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/manifests.ts @@ -0,0 +1,10 @@ +import { UMB_DOCUMENT_UNPUBLISH_MODAL_ALIAS } from './constants.js'; + +export const manifests: Array = [ + { + type: 'modal', + alias: UMB_DOCUMENT_UNPUBLISH_MODAL_ALIAS, + name: 'Document Unpublish Modal', + element: () => import('./document-unpublish-modal.element.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/types.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/types.ts new file mode 100644 index 000000000000..4aeeacf57dd0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/types.ts @@ -0,0 +1 @@ +export type * from './modal/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/workspace-action/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/workspace-action/manifests.ts new file mode 100644 index 000000000000..30e257a93ddc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/workspace-action/manifests.ts @@ -0,0 +1,27 @@ +import { UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH } from '../../../user-permissions/constants.js'; +import { UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS } from '@umbraco-cms/backoffice/recycle-bin'; + +export const manifests: Array = [ + { + type: 'workspaceActionMenuItem', + kind: 'default', + alias: 'Umb.Document.WorkspaceActionMenuItem.Unpublish', + name: 'Unpublish', + weight: 0, + api: () => import('./unpublish.action.js'), + forWorkspaceActions: 'Umb.WorkspaceAction.Document.SaveAndPublish', + meta: { + label: '#actions_unpublish', + icon: 'icon-globe', + }, + conditions: [ + { + alias: 'Umb.Condition.UserPermission.Document', + allOf: [UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH], + }, + { + alias: UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS, + }, + ], + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/actions/unpublish.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/workspace-action/unpublish.action.ts similarity index 60% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/actions/unpublish.action.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/workspace-action/unpublish.action.ts index 021ee5611be4..319d2fa2b612 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/actions/unpublish.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/workspace-action/unpublish.action.ts @@ -1,9 +1,9 @@ -import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '../document-workspace.context-token.js'; +import { UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT } from '../../workspace-context/constants.js'; import { UmbWorkspaceActionBase } from '@umbraco-cms/backoffice/workspace'; export class UmbDocumentUnpublishWorkspaceAction extends UmbWorkspaceActionBase { override async execute() { - const workspaceContext = await this.getContext(UMB_DOCUMENT_WORKSPACE_CONTEXT); + const workspaceContext = await this.getContext(UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT); return workspaceContext.unpublish(); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/utils.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/utils.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/utils.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/utils.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/constants.ts new file mode 100644 index 000000000000..fe7ae40fcab1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/constants.ts @@ -0,0 +1 @@ +export * from './document-publishing.workspace-context.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/document-publishing.workspace-context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/document-publishing.workspace-context.token.ts new file mode 100644 index 000000000000..7e483fdda42d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/document-publishing.workspace-context.token.ts @@ -0,0 +1,8 @@ +import type { UmbDocumentPublishingWorkspaceContext } from './document-publishing.workspace-context.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +export const UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT = new UmbContextToken( + 'UmbWorkspaceContext', + undefined, + (context): context is UmbDocumentPublishingWorkspaceContext => context.publishedPendingChanges !== undefined, +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/document-publishing.workspace-context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/document-publishing.workspace-context.ts new file mode 100644 index 000000000000..3ee552845efb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/document-publishing.workspace-context.ts @@ -0,0 +1,402 @@ +import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '../../workspace/document-workspace.context-token.js'; +import type { + UmbDocumentDetailModel, + UmbDocumentVariantOptionModel, + UmbDocumentVariantPublishModel, +} from '../../types.js'; +import { UmbDocumentPublishingRepository } from '../repository/index.js'; +import { UmbDocumentPublishedPendingChangesManager } from '../pending-changes/index.js'; +import { UMB_DOCUMENT_SCHEDULE_MODAL } from '../schedule-publish/constants.js'; +import { UMB_DOCUMENT_PUBLISH_WITH_DESCENDANTS_MODAL } from '../publish-with-descendants/constants.js'; +import { UMB_DOCUMENT_PUBLISH_MODAL } from '../publish/constants.js'; +import { UmbUnpublishDocumentEntityAction } from '../unpublish/index.js'; +import { UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT } from './document-publishing.workspace-context.token.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; +import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; +import { + UmbRequestReloadChildrenOfEntityEvent, + UmbRequestReloadStructureForEntityEvent, +} from '@umbraco-cms/backoffice/entity-action'; +import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; +import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification'; +import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; +import { observeMultiple } from '@umbraco-cms/backoffice/observable-api'; +import { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api'; +import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; + +export class UmbDocumentPublishingWorkspaceContext extends UmbContextBase { + /** + * Manages the pending changes for the published document. + * @memberof UmbDocumentPublishingWorkspaceContext + */ + public readonly publishedPendingChanges = new UmbDocumentPublishedPendingChangesManager(this); + + #init: Promise; + #documentWorkspaceContext?: typeof UMB_DOCUMENT_WORKSPACE_CONTEXT.TYPE; + #eventContext?: typeof UMB_ACTION_EVENT_CONTEXT.TYPE; + #publishingRepository = new UmbDocumentPublishingRepository(this); + #publishedDocumentData?: UmbDocumentDetailModel; + #currentUnique?: UmbEntityUnique; + + constructor(host: UmbControllerHost) { + super(host, UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT); + + this.#init = Promise.all([ + this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, async (context) => { + this.#documentWorkspaceContext = context; + this.#initPendingChanges(); + }).asPromise(), + + this.consumeContext(UMB_ACTION_EVENT_CONTEXT, async (context) => { + this.#eventContext = context; + }).asPromise(), + ]); + } + + public async publish() { + throw new Error('Method not implemented.'); + } + + /** + * Save and publish the document + * @returns {Promise} + * @memberof UmbDocumentPublishingWorkspaceContext + */ + public async saveAndPublish(): Promise { + return this.#handleSaveAndPublish(); + } + + /** + * Schedule the document for publishing + * @returns {Promise} + * @memberof UmbDocumentPublishingWorkspaceContext + */ + public async schedule(): Promise { + await this.#init; + if (!this.#documentWorkspaceContext) throw new Error('Document workspace context is missing'); + + const unique = this.#documentWorkspaceContext.getUnique(); + if (!unique) throw new Error('Unique is missing'); + + const entityType = this.#documentWorkspaceContext.getEntityType(); + if (!entityType) throw new Error('Entity type is missing'); + + const { options, selected } = await this.#determineVariantOptions(); + + const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); + const result = await modalManagerContext + .open(this, UMB_DOCUMENT_SCHEDULE_MODAL, { + data: { + options, + pickableFilter: this.#readOnlyLanguageVariantsFilter, + }, + value: { selection: selected.map((unique) => ({ unique, schedule: {} })) }, + }) + .onSubmit() + .catch(() => undefined); + + if (!result?.selection.length) return; + + // Map to the correct format for the API (UmbDocumentVariantPublishModel) + const variants = + result?.selection.map((x) => ({ + variantId: UmbVariantId.FromString(x.unique), + schedule: x.schedule, + })) ?? []; + + if (!variants.length) return; + + // TODO: Validate content & Save changes for the selected variants — This was how it worked in v.13 [NL] + const { error } = await this.#publishingRepository.publish(unique, variants); + if (!error) { + // reload the document so all states are updated after the publish operation + await this.#documentWorkspaceContext.reload(); + this.#loadAndProcessLastPublished(); + + // request reload of this entity + const structureEvent = new UmbRequestReloadStructureForEntityEvent({ entityType, unique }); + this.#eventContext?.dispatchEvent(structureEvent); + } + } + + /** + * Publish the document with descendants + * @returns {Promise} + * @memberof UmbDocumentPublishingWorkspaceContext + */ + public async publishWithDescendants(): Promise { + await this.#init; + if (!this.#documentWorkspaceContext) throw new Error('Document workspace context is missing'); + + const unique = this.#documentWorkspaceContext.getUnique(); + if (!unique) throw new Error('Unique is missing'); + + const entityType = this.#documentWorkspaceContext.getEntityType(); + if (!entityType) throw new Error('Entity type is missing'); + + const { options, selected } = await this.#determineVariantOptions(); + + const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); + const result = await modalManagerContext + .open(this, UMB_DOCUMENT_PUBLISH_WITH_DESCENDANTS_MODAL, { + data: { + options, + pickableFilter: this.#readOnlyLanguageVariantsFilter, + }, + value: { selection: selected }, + }) + .onSubmit() + .catch(() => undefined); + + if (!result?.selection.length) return; + + // Map to variantIds + const variantIds = result?.selection.map((x) => UmbVariantId.FromString(x)) ?? []; + + if (!variantIds.length) return; + + const { error } = await this.#publishingRepository.publishWithDescendants( + unique, + variantIds, + result.includeUnpublishedDescendants ?? false, + ); + + if (!error) { + // reload the document so all states are updated after the publish operation + await this.#documentWorkspaceContext.reload(); + this.#loadAndProcessLastPublished(); + + // request reload of this entity + const structureEvent = new UmbRequestReloadStructureForEntityEvent({ entityType, unique }); + this.#eventContext?.dispatchEvent(structureEvent); + + // request reload of the children + const childrenEvent = new UmbRequestReloadChildrenOfEntityEvent({ entityType, unique }); + this.#eventContext?.dispatchEvent(childrenEvent); + } + } + + /** + * Unpublish the document + * @returns {Promise} + * @memberof UmbDocumentPublishingWorkspaceContext + */ + public async unpublish(): Promise { + await this.#init; + if (!this.#documentWorkspaceContext) throw new Error('Document workspace context is missing'); + + const unique = this.#documentWorkspaceContext.getUnique(); + if (!unique) throw new Error('Unique is missing'); + + const entityType = this.#documentWorkspaceContext.getEntityType(); + if (!entityType) throw new Error('Entity type is missing'); + + // TODO: remove meta + new UmbUnpublishDocumentEntityAction(this, { unique, entityType, meta: {} as never }).execute(); + } + + async #handleSaveAndPublish() { + await this.#init; + if (!this.#documentWorkspaceContext) throw new Error('Document workspace context is missing'); + + const unique = this.#documentWorkspaceContext.getUnique(); + if (!unique) throw new Error('Unique is missing'); + + let variantIds: Array = []; + + const { options, selected } = await this.#determineVariantOptions(); + + // If there is only one variant, we don't need to open the modal. + if (options.length === 0) { + throw new Error('No variants are available'); + } else if (options.length === 1) { + // If only one option we will skip ahead and save the document with the only variant available: + variantIds.push(UmbVariantId.Create(options[0])); + } else { + // If there are multiple variants, we will open the modal to let the user pick which variants to publish. + const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); + const result = await modalManagerContext + .open(this, UMB_DOCUMENT_PUBLISH_MODAL, { + data: { + options, + pickableFilter: this.#readOnlyLanguageVariantsFilter, + }, + value: { selection: selected }, + }) + .onSubmit() + .catch(() => undefined); + + if (!result?.selection.length || !unique) return; + + variantIds = result?.selection.map((x) => UmbVariantId.FromString(x)) ?? []; + } + + const saveData = await this.#documentWorkspaceContext.constructSaveData(variantIds); + await this.#documentWorkspaceContext.runMandatoryValidationForSaveData(saveData); + await this.#documentWorkspaceContext.askServerToValidate(saveData, variantIds); + + // TODO: Only validate the specified selection.. [NL] + return this.#documentWorkspaceContext.validateAndSubmit( + async () => { + return this.#performSaveAndPublish(variantIds, saveData); + }, + async () => { + // If data of the selection is not valid Then just save: + await this.#documentWorkspaceContext!.performCreateOrUpdate(variantIds, saveData); + // Notifying that the save was successful, but we did not publish, which is what we want to symbolize here. [NL] + const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT); + // TODO: Get rid of the save notification. + // TODO: Translate this message [NL] + notificationContext.peek('danger', { + data: { message: 'Document was not published, but we saved it for you.' }, + }); + // Reject even thought the save was successful, but we did not publish, which is what we want to symbolize here. [NL] + return await Promise.reject(); + }, + ); + } + + async #performSaveAndPublish(variantIds: Array, saveData: UmbDocumentDetailModel): Promise { + await this.#init; + if (!this.#documentWorkspaceContext) throw new Error('Document workspace context is missing'); + + const unique = this.#documentWorkspaceContext.getUnique(); + if (!unique) throw new Error('Unique is missing'); + + const entityType = this.#documentWorkspaceContext.getEntityType(); + if (!entityType) throw new Error('Entity type is missing'); + + await this.#documentWorkspaceContext.performCreateOrUpdate(variantIds, saveData); + + const { error } = await this.#publishingRepository.publish( + unique, + variantIds.map((variantId) => ({ variantId })), + ); + + if (!error) { + // reload the document so all states are updated after the publish operation + await this.#documentWorkspaceContext.reload(); + this.#loadAndProcessLastPublished(); + + const event = new UmbRequestReloadStructureForEntityEvent({ unique, entityType }); + this.#eventContext?.dispatchEvent(event); + } + } + + #readOnlyLanguageVariantsFilter = (option: UmbDocumentVariantOptionModel) => { + const readOnlyCultures = + this.#documentWorkspaceContext?.readOnlyState.getStates().map((s) => s.variantId.culture) ?? []; + return readOnlyCultures.includes(option.culture) === false; + }; + + async #determineVariantOptions(): Promise<{ + options: UmbDocumentVariantOptionModel[]; + selected: string[]; + }> { + await this.#init; + if (!this.#documentWorkspaceContext) throw new Error('Document workspace context is missing'); + + const options = await firstValueFrom(this.#documentWorkspaceContext.variantOptions); + + // TODO: this is a temporary copy of the content-detail workspace context method. + // we need to implement custom selection that makes sense for each the publishing modal. + let selected = this.#getChangedVariantsSelection(); + + // Selected can contain entries that are not part of the options, therefor the modal filters selection based on options. + selected = selected.filter((x) => options.some((o) => o.unique === x)); + + // Filter out read-only variants + const readOnlyCultures = this.#documentWorkspaceContext.readOnlyState.getStates().map((s) => s.variantId.culture); + selected = selected.filter((x) => readOnlyCultures.includes(x) === false); + + return { + options, + selected, + }; + } + + #getChangedVariantsSelection() { + if (!this.#documentWorkspaceContext) throw new Error('Document workspace context is missing'); + const activeVariants = this.#documentWorkspaceContext.splitView + .getActiveVariants() + .map((activeVariant) => UmbVariantId.Create(activeVariant)) + .map((x) => x.toString()); + const changedVariants = this.#documentWorkspaceContext.getChangedVariants().map((x) => x.toString()); + const selection = [...activeVariants, ...changedVariants]; + return [...new Set(selection)]; + } + + async #initPendingChanges() { + if (!this.#documentWorkspaceContext) throw new Error('Document workspace context is missing'); + this.observe( + observeMultiple([this.#documentWorkspaceContext.unique, this.#documentWorkspaceContext.isNew]), + ([unique, isNew]) => { + // We have loaded in a new document, so we need to clear the states + if (unique !== this.#currentUnique) { + this.#clear(); + } + + this.#currentUnique = unique; + + if (isNew === false && unique) { + this.#loadAndProcessLastPublished(); + } + }, + 'uniqueObserver', + ); + + this.observe( + this.#documentWorkspaceContext.persistedData, + () => this.#processPendingChanges(), + 'umbPersistedDataObserver', + ); + } + + #hasPublishedVariant() { + const variants = this.#documentWorkspaceContext?.getVariants(); + return ( + variants?.some( + (variant) => + variant.state === DocumentVariantStateModel.PUBLISHED || + variant.state === DocumentVariantStateModel.PUBLISHED_PENDING_CHANGES, + ) ?? false + ); + } + + async #loadAndProcessLastPublished() { + if (!this.#documentWorkspaceContext) throw new Error('Document workspace context is missing'); + + // No need to check pending changes for new documents + if (this.#documentWorkspaceContext.getIsNew()) return; + + const unique = this.#documentWorkspaceContext.getUnique(); + if (!unique) throw new Error('Unique is missing'); + + // Only load the published data if the document is already published or has been published before + const hasPublishedVariant = this.#hasPublishedVariant(); + if (!hasPublishedVariant) return; + + const { data } = await this.#publishingRepository.published(unique); + this.#publishedDocumentData = data; + this.#processPendingChanges(); + } + + #processPendingChanges() { + if (!this.#documentWorkspaceContext) throw new Error('Document workspace context is missing'); + + const persistedData = this.#documentWorkspaceContext.getPersistedData(); + const publishedData = this.#publishedDocumentData; + if (!persistedData || !publishedData) return; + + this.publishedPendingChanges.process({ persistedData, publishedData }); + } + + #clear() { + this.#publishedDocumentData = undefined; + this.publishedPendingChanges.clear(); + } +} + +export { UmbDocumentPublishingWorkspaceContext as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/manifests.ts new file mode 100644 index 000000000000..2dc258192b5d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/manifests.ts @@ -0,0 +1,17 @@ +import { UMB_DOCUMENT_WORKSPACE_ALIAS } from '../../workspace/constants.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; + +export const manifests: Array = [ + { + type: 'workspaceContext', + name: 'Document Publishing Workspace Context', + alias: 'Umb.WorkspaceContext.Document.Publishing', + api: () => import('./document-publishing.workspace-context.js'), + conditions: [ + { + alias: UMB_WORKSPACE_CONDITION_ALIAS, + match: UMB_DOCUMENT_WORKSPACE_ALIAS, + }, + ], + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/types.ts new file mode 100644 index 000000000000..ec3287a56802 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/types.ts @@ -0,0 +1 @@ +export type * from './document-publishing.workspace-context.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/constants.ts index acb88b44cb98..655e81e66dd1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/constants.ts @@ -1,5 +1,4 @@ export * from './detail/constants.js'; export * from './item/constants.js'; -export * from './publishing/constants.js'; export * from './url/constants.js'; export * from './validation/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/index.ts index 2acc65719340..4e403fde93b1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/index.ts @@ -1,6 +1,5 @@ export { UmbDocumentDetailRepository } from './detail/index.js'; export { UmbDocumentItemRepository } from './item/index.js'; -export { UmbDocumentPublishingRepository } from './publishing/index.js'; export { UmbDocumentUrlRepository, UMB_DOCUMENT_URL_REPOSITORY_ALIAS } from './url/index.js'; export { UmbDocumentPreviewRepository } from './preview/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/manifests.ts index c7ab4b7fdc35..7c077b7378a1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/manifests.ts @@ -1,11 +1,5 @@ import { manifests as detailManifests } from './detail/manifests.js'; import { manifests as itemManifests } from './item/manifests.js'; -import { manifests as publishingManifests } from './publishing/manifests.js'; import { manifests as urlManifests } from './url/manifests.js'; -export const manifests: Array = [ - ...detailManifests, - ...itemManifests, - ...publishingManifests, - ...urlManifests, -]; +export const manifests: Array = [...detailManifests, ...itemManifests, ...urlManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/types.ts index f3f882e74a61..a3407cf5aa4a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/types.ts @@ -18,6 +18,7 @@ export type * from './tree/types.js'; export type * from './user-permissions/types.js'; export type * from './entity.js'; export type * from './workspace/types.js'; +export type * from './publishing/types.js'; export interface UmbDocumentDetailModel extends UmbContentDetailModel { documentType: { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-split-view-variant-selector.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-split-view-variant-selector.element.ts index f08abb5f61ba..155dd1ce81f2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-split-view-variant-selector.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-split-view-variant-selector.element.ts @@ -1,6 +1,7 @@ import type { UmbDocumentVariantOptionModel } from '../types.js'; import { sortVariants } from '../utils.js'; -import { customElement, html } from '@umbraco-cms/backoffice/external/lit'; +import { UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT } from '../publishing/index.js'; +import { customElement, html, state } from '@umbraco-cms/backoffice/external/lit'; import { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api'; import { UmbWorkspaceSplitViewVariantSelectorElement } from '@umbraco-cms/backoffice/workspace'; @@ -9,17 +10,59 @@ const elementName = 'umb-document-workspace-split-view-variant-selector'; export class UmbDocumentWorkspaceSplitViewVariantSelectorElement extends UmbWorkspaceSplitViewVariantSelectorElement { protected override _variantSorter = sortVariants; + @state() + private _variantsWithPendingChanges: Array = []; + + #documentPublishingWorkspaceContext?: typeof UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT.TYPE; + #publishStateLocalizationMap = { [DocumentVariantStateModel.DRAFT]: 'content_unpublished', [DocumentVariantStateModel.PUBLISHED]: 'content_published', - [DocumentVariantStateModel.PUBLISHED_PENDING_CHANGES]: 'content_publishedPendingChanges', + // TODO: The pending changes state can be removed once the management Api removes this state + // We only keep it here to make typescript happy + // We should also make our own state model for this + [DocumentVariantStateModel.PUBLISHED_PENDING_CHANGES]: 'content_published', [DocumentVariantStateModel.NOT_CREATED]: 'content_notCreated', }; + constructor() { + super(); + this.consumeContext(UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT, (instance) => { + this.#documentPublishingWorkspaceContext = instance; + this.#observePendingChanges(); + }); + } + + #observePendingChanges() { + this.observe( + this.#documentPublishingWorkspaceContext?.publishedPendingChanges.variantsWithChanges, + (variants) => { + this._variantsWithPendingChanges = variants || []; + }, + '_observePendingChanges', + ); + } + + #hasPendingChanges(variant: UmbDocumentVariantOptionModel) { + return this._variantsWithPendingChanges.some((x) => x.variantId.compare(variant)); + } + + #getVariantState(variantOption: UmbDocumentVariantOptionModel) { + let term = this.#publishStateLocalizationMap[variantOption.variant?.state || DocumentVariantStateModel.NOT_CREATED]; + + if ( + (variantOption.variant?.state === DocumentVariantStateModel.PUBLISHED || + variantOption.variant?.state === DocumentVariantStateModel.PUBLISHED_PENDING_CHANGES) && + this.#hasPendingChanges(variantOption) + ) { + term = 'content_publishedPendingChanges'; + } + + return this.localize.term(term); + } + override _renderVariantDetails(variantOption: UmbDocumentVariantOptionModel) { - return html` ${this.localize.term( - this.#publishStateLocalizationMap[variantOption.variant?.state || DocumentVariantStateModel.NOT_CREATED], - )}`; + return html` ${this.#getVariantState(variantOption)}`; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index 6cd365699286..c4eb63ebf779 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -2,22 +2,18 @@ import { UmbDocumentTypeDetailRepository } from '../../document-types/repository import { UmbDocumentPropertyDatasetContext } from '../property-dataset-context/document-property-dataset.context.js'; import type { UmbDocumentDetailRepository } from '../repository/index.js'; import { UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS } from '../repository/index.js'; -import type { UmbDocumentVariantPublishModel, UmbDocumentDetailModel, UmbDocumentVariantModel } from '../types.js'; +import type { UmbDocumentDetailModel, UmbDocumentVariantModel } from '../types.js'; import { + UMB_CREATE_DOCUMENT_WORKSPACE_PATH_PATTERN, + UMB_CREATE_FROM_BLUEPRINT_DOCUMENT_WORKSPACE_PATH_PATTERN, UMB_DOCUMENT_COLLECTION_ALIAS, UMB_DOCUMENT_ENTITY_TYPE, - UMB_DOCUMENT_PUBLISH_MODAL, - UMB_DOCUMENT_PUBLISH_WITH_DESCENDANTS_MODAL, UMB_DOCUMENT_SAVE_MODAL, - UMB_DOCUMENT_SCHEDULE_MODAL, - UMB_CREATE_DOCUMENT_WORKSPACE_PATH_PATTERN, - UMB_CREATE_FROM_BLUEPRINT_DOCUMENT_WORKSPACE_PATH_PATTERN, UMB_EDIT_DOCUMENT_WORKSPACE_PATH_PATTERN, } from '../constants.js'; -import { UmbDocumentPublishingRepository } from '../repository/publishing/index.js'; -import { UmbUnpublishDocumentEntityAction } from '../entity-actions/unpublish.action.js'; -import { UmbDocumentValidationRepository } from '../repository/validation/document-validation.repository.js'; import { UmbDocumentPreviewRepository } from '../repository/preview/index.js'; +import { UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT, UmbDocumentPublishingRepository } from '../publishing/index.js'; +import { UmbDocumentValidationRepository } from '../repository/validation/index.js'; import { UMB_DOCUMENT_DETAIL_MODEL_VARIANT_SCAFFOLD, UMB_DOCUMENT_WORKSPACE_ALIAS } from './constants.js'; import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; import { UMB_INVARIANT_CULTURE, UmbVariantId } from '@umbraco-cms/backoffice/variant'; @@ -27,14 +23,7 @@ import { UmbWorkspaceIsNewRedirectControllerAlias, } from '@umbraco-cms/backoffice/workspace'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; -import { - UmbRequestReloadChildrenOfEntityEvent, - UmbRequestReloadStructureForEntityEvent, -} from '@umbraco-cms/backoffice/entity-action'; -import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import { UmbDocumentBlueprintDetailRepository } from '@umbraco-cms/backoffice/document-blueprint'; -import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification'; import { UmbContentDetailWorkspaceContextBase, type UmbContentCollectionWorkspaceContext, @@ -43,6 +32,7 @@ import { import type { UmbDocumentTypeDetailModel } from '@umbraco-cms/backoffice/document-type'; import { UmbIsTrashedEntityContext } from '@umbraco-cms/backoffice/recycle-bin'; import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app'; +import { UmbDeprecation } from '@umbraco-cms/backoffice/utils'; type ContentModel = UmbDocumentDetailModel; type ContentTypeModel = UmbDocumentTypeDetailModel; @@ -59,6 +49,11 @@ export class UmbDocumentWorkspaceContext UmbPublishableWorkspaceContext, UmbContentCollectionWorkspaceContext { + /** + * The publishing repository for the document workspace. + * @deprecated Will be removed in v17. Use the methods on the UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT instead. + * @memberof UmbDocumentWorkspaceContext + */ public readonly publishingRepository = new UmbDocumentPublishingRepository(this); readonly isTrashed = this._data.createObservablePartOfCurrent((data) => data?.isTrashed); @@ -70,6 +65,7 @@ export class UmbDocumentWorkspaceContext readonly templateId = this._data.createObservablePartOfCurrent((data) => data?.template?.unique || null); #isTrashedContext = new UmbIsTrashedEntityContext(this); + #publishingContext?: typeof UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT.TYPE; constructor(host: UmbControllerHost) { super(host, { @@ -85,6 +81,11 @@ export class UmbDocumentWorkspaceContext this.observe(this.contentTypeUnique, (unique) => this.structure.loadType(unique), null); + // TODO: Remove this in v17 as we have moved the publishing methods to the UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT. + this.consumeContext(UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT, (context) => { + this.#publishingContext = context; + }); + this.routes.setRoutes([ { path: UMB_CREATE_FROM_BLUEPRINT_DOCUMENT_WORKSPACE_PATH_PATTERN.toString(), @@ -204,6 +205,23 @@ export class UmbDocumentWorkspaceContext this._data.updateCurrent({ template: { unique: templateUnique } }); } + /** + * Request a submit of the workspace, in the case of Document Workspaces the validation does not need to be valid for this to be submitted. + * @returns {Promise} a promise which resolves once it has been completed. + */ + public override requestSubmit() { + return this._handleSubmit(); + } + + // Because we do not make validation prevent submission this also submits the workspace. [NL] + public override invalidSubmit() { + return this._handleSubmit(); + } + + public async saveAndPreview(): Promise { + return this.#handleSaveAndPreview(); + } + async #handleSaveAndPreview() { const unique = this.getUnique(); if (!unique) throw new Error('Unique is missing'); @@ -216,8 +234,8 @@ export class UmbDocumentWorkspaceContext culture = selected[0]; const variantIds = [UmbVariantId.FromString(culture)]; const saveData = await this._data.constructData(variantIds); - await this._runMandatoryValidationForSaveData(saveData); - await this._performCreateOrUpdate(variantIds, saveData); + await this.runMandatoryValidationForSaveData(saveData); + await this.performCreateOrUpdate(variantIds, saveData); } // Tell the server that we're entering preview mode. @@ -236,196 +254,70 @@ export class UmbDocumentWorkspaceContext previewWindow?.focus(); } - async #handleSaveAndPublish() { - const unique = this.getUnique(); - if (!unique) throw new Error('Unique is missing'); - - let variantIds: Array = []; - - const { options, selected } = await this._determineVariantOptions(); - - // If there is only one variant, we don't need to open the modal. - if (options.length === 0) { - throw new Error('No variants are available'); - } else if (options.length === 1) { - // If only one option we will skip ahead and save the document with the only variant available: - variantIds.push(UmbVariantId.Create(options[0])); - } else { - // If there are multiple variants, we will open the modal to let the user pick which variants to publish. - const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); - const result = await modalManagerContext - .open(this, UMB_DOCUMENT_PUBLISH_MODAL, { - data: { - options, - pickableFilter: this._readOnlyLanguageVariantsFilter, - }, - value: { selection: selected }, - }) - .onSubmit() - .catch(() => undefined); - - if (!result?.selection.length || !unique) return; - - variantIds = result?.selection.map((x) => UmbVariantId.FromString(x)) ?? []; - } - - const saveData = await this._data.constructData(variantIds); - await this._runMandatoryValidationForSaveData(saveData); - - await this._askServerToValidate(saveData, variantIds); - - // TODO: Only validate the specified selection.. [NL] - return this.validateAndSubmit( - async () => { - return this.#performSaveAndPublish(variantIds, saveData); - }, - async () => { - // If data of the selection is not valid Then just save: - await this._performCreateOrUpdate(variantIds, saveData); - // Notifying that the save was successful, but we did not publish, which is what we want to symbolize here. [NL] - const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT); - // TODO: Get rid of the save notification. - // TODO: Translate this message [NL] - notificationContext.peek('danger', { - data: { message: 'Document was not published, but we saved it for you.' }, - }); - // Reject even thought the save was successful, but we did not publish, which is what we want to symbolize here. [NL] - return await Promise.reject(); - }, - ); - } - - async #performSaveAndPublish(variantIds: Array, saveData: UmbDocumentDetailModel): Promise { - const unique = this.getUnique(); - if (!unique) throw new Error('Unique is missing'); - - await this._performCreateOrUpdate(variantIds, saveData); - - const { error } = await this.publishingRepository.publish( - unique, - variantIds.map((variantId) => ({ variantId })), - ); - - if (!error) { - const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); - const event = new UmbRequestReloadStructureForEntityEvent({ - unique: this.getUnique()!, - entityType: this.getEntityType(), - }); - - eventContext.dispatchEvent(event); - - this._closeModal(); - } - } - public async publish() { - throw new Error('Method not implemented.'); - } - - public async saveAndPreview(): Promise { - return this.#handleSaveAndPreview(); + new UmbDeprecation({ + deprecated: 'The Publish method on the UMB_DOCUMENT_WORKSPACE_CONTEXT is deprecated.', + removeInVersion: '17', + solution: 'Use the Publish method on the UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT instead.', + }).warn(); + if (!this.#publishingContext) throw new Error('Publishing context is missing'); + this.#publishingContext.publish(); } + /** + * Save the document and publish it. + * @deprecated Will be removed in v17. Use the UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT instead. + */ public async saveAndPublish(): Promise { - return this.#handleSaveAndPublish(); + new UmbDeprecation({ + deprecated: 'The SaveAndPublish method on the UMB_DOCUMENT_WORKSPACE_CONTEXT is deprecated.', + removeInVersion: '17', + solution: 'Use the SaveAndPublish method on the UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT instead.', + }).warn(); + if (!this.#publishingContext) throw new Error('Publishing context is missing'); + this.#publishingContext.saveAndPublish(); } + /** + * Schedule the document to be published at a later date. + * @deprecated Will be removed in v17. Use the UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT instead. + */ public async schedule() { - const { options, selected } = await this._determineVariantOptions(); - - const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); - const result = await modalManagerContext - .open(this, UMB_DOCUMENT_SCHEDULE_MODAL, { - data: { - options, - pickableFilter: this._readOnlyLanguageVariantsFilter, - }, - value: { selection: selected.map((unique) => ({ unique, schedule: {} })) }, - }) - .onSubmit() - .catch(() => undefined); - - if (!result?.selection.length) return; - - // Map to the correct format for the API (UmbDocumentVariantPublishModel) - const variants = - result?.selection.map((x) => ({ - variantId: UmbVariantId.FromString(x.unique), - schedule: x.schedule, - })) ?? []; - - if (!variants.length) return; - - // TODO: Validate content & Save changes for the selected variants — This was how it worked in v.13 [NL] - - const unique = this.getUnique(); - if (!unique) throw new Error('Unique is missing'); - await this.publishingRepository.publish(unique, variants); + new UmbDeprecation({ + deprecated: 'The Schedule method on the UMB_DOCUMENT_WORKSPACE_CONTEXT is deprecated.', + removeInVersion: '17', + solution: 'Use the Schedule method on the UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT instead.', + }).warn(); + if (!this.#publishingContext) throw new Error('Publishing context is missing'); + this.#publishingContext.schedule(); } + /** + * Unpublish the document. + * @deprecated Will be removed in v17. Use the UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT instead. + */ public async unpublish() { - const unique = this.getUnique(); - const entityType = this.getEntityType(); - if (!unique) throw new Error('Unique is missing'); - if (!entityType) throw new Error('Entity type is missing'); - - // TODO: remove meta - new UmbUnpublishDocumentEntityAction(this, { unique, entityType, meta: {} as never }).execute(); + new UmbDeprecation({ + deprecated: 'The Unpublish method on the UMB_DOCUMENT_WORKSPACE_CONTEXT is deprecated.', + removeInVersion: '17', + solution: 'Use the Unpublish method on the UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT instead.', + }).warn(); + if (!this.#publishingContext) throw new Error('Publishing context is missing'); + this.#publishingContext.unpublish(); } + /** + * Publish the document and all its descendants. + * @deprecated Will be removed in v17. Use the UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT instead. + */ public async publishWithDescendants() { - const unique = this.getUnique(); - if (!unique) throw new Error('Unique is missing'); - - const entityType = this.getEntityType(); - if (!entityType) throw new Error('Entity type is missing'); - - const { options, selected } = await this._determineVariantOptions(); - - const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); - const result = await modalManagerContext - .open(this, UMB_DOCUMENT_PUBLISH_WITH_DESCENDANTS_MODAL, { - data: { - options, - pickableFilter: this._readOnlyLanguageVariantsFilter, - }, - value: { selection: selected }, - }) - .onSubmit() - .catch(() => undefined); - - if (!result?.selection.length) return; - - // Map to variantIds - const variantIds = result?.selection.map((x) => UmbVariantId.FromString(x)) ?? []; - - if (!variantIds.length) return; - - const { error } = await this.publishingRepository.publishWithDescendants( - unique, - variantIds, - result.includeUnpublishedDescendants ?? false, - ); - - if (!error) { - const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); - - // request reload of this entity - const structureEvent = new UmbRequestReloadStructureForEntityEvent({ - entityType, - unique, - }); - - // request reload of the children - const childrenEvent = new UmbRequestReloadChildrenOfEntityEvent({ - entityType, - unique, - }); - - eventContext.dispatchEvent(structureEvent); - eventContext.dispatchEvent(childrenEvent); - } + new UmbDeprecation({ + deprecated: 'The PublishWithDescendants method on the UMB_DOCUMENT_WORKSPACE_CONTEXT is deprecated.', + removeInVersion: '17', + solution: 'Use the PublishWithDescendants method on the UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT instead.', + }).warn(); + if (!this.#publishingContext) throw new Error('Publishing context is missing'); + this.#publishingContext.publishWithDescendants(); } public createPropertyDatasetContext( diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/manifests.ts index a79c270056dc..4132437d96b3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/manifests.ts @@ -1,8 +1,3 @@ -import { - UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH, - UMB_USER_PERMISSION_DOCUMENT_UPDATE, - UMB_USER_PERMISSION_DOCUMENT_PUBLISH, -} from '../user-permissions/index.js'; import { UMB_DOCUMENT_ENTITY_TYPE } from '../entity.js'; import { UMB_DOCUMENT_WORKSPACE_ALIAS } from './constants.js'; import { UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS } from '@umbraco-cms/backoffice/recycle-bin'; @@ -79,28 +74,7 @@ export const manifests: Array = [ }, ], }, - { - type: 'workspaceAction', - kind: 'default', - alias: 'Umb.WorkspaceAction.Document.SaveAndPublish', - name: 'Save And Publish Document Workspace Action', - weight: 70, - api: () => import('./actions/save-and-publish.action.js'), - meta: { - label: '#buttons_saveAndPublish', - look: 'primary', - color: 'positive', - }, - conditions: [ - { - alias: UMB_WORKSPACE_CONDITION_ALIAS, - match: UMB_DOCUMENT_WORKSPACE_ALIAS, - }, - { - alias: UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS, - }, - ], - }, + { type: 'workspaceAction', kind: 'default', @@ -143,70 +117,4 @@ export const manifests: Array = [ }, ], }, - { - type: 'workspaceActionMenuItem', - kind: 'default', - alias: 'Umb.Document.WorkspaceActionMenuItem.Unpublish', - name: 'Unpublish', - weight: 0, - api: () => import('./actions/unpublish.action.js'), - forWorkspaceActions: 'Umb.WorkspaceAction.Document.SaveAndPublish', - meta: { - label: '#actions_unpublish', - icon: 'icon-globe', - }, - conditions: [ - { - alias: 'Umb.Condition.UserPermission.Document', - allOf: [UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH], - }, - { - alias: UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS, - }, - ], - }, - { - type: 'workspaceActionMenuItem', - kind: 'default', - alias: 'Umb.Document.WorkspaceActionMenuItem.PublishWithDescendants', - name: 'Publish with descendants', - weight: 10, - api: () => import('./actions/publish-with-descendants.action.js'), - forWorkspaceActions: 'Umb.WorkspaceAction.Document.SaveAndPublish', - meta: { - label: '#buttons_publishDescendants', - icon: 'icon-globe', - }, - conditions: [ - { - alias: 'Umb.Condition.UserPermission.Document', - allOf: [UMB_USER_PERMISSION_DOCUMENT_UPDATE, UMB_USER_PERMISSION_DOCUMENT_PUBLISH], - }, - { - alias: UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS, - }, - ], - }, - { - type: 'workspaceActionMenuItem', - kind: 'default', - alias: 'Umb.Document.WorkspaceActionMenuItem.SchedulePublishing', - name: 'Schedule publishing', - weight: 20, - api: () => import('./actions/save-and-schedule.action.js'), - forWorkspaceActions: 'Umb.WorkspaceAction.Document.SaveAndPublish', - meta: { - label: '#buttons_schedulePublish', - icon: 'icon-globe', - }, - conditions: [ - { - alias: 'Umb.Condition.UserPermission.Document', - allOf: [UMB_USER_PERMISSION_DOCUMENT_UPDATE, UMB_USER_PERMISSION_DOCUMENT_PUBLISH], - }, - { - alias: UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS, - }, - ], - }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info.element.ts index 33e90e5cb99a..b75d219adae1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info.element.ts @@ -1,5 +1,6 @@ import { UMB_DOCUMENT_PROPERTY_DATASET_CONTEXT, UMB_DOCUMENT_WORKSPACE_CONTEXT } from '../../../constants.js'; import type { UmbDocumentVariantModel } from '../../../types.js'; +import { UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT } from '../../../publishing/index.js'; import { TimeOptions } from './utils.js'; import { css, customElement, html, ifDefined, nothing, state } from '@umbraco-cms/backoffice/external/lit'; import { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api'; @@ -44,9 +45,12 @@ export class UmbDocumentWorkspaceViewInfoElement extends UmbLitElement { @state() private _variant?: UmbDocumentVariantModel; - #workspaceContext?: typeof UMB_DOCUMENT_WORKSPACE_CONTEXT.TYPE; + @state() + private _variantsWithPendingChanges: Array = []; + #workspaceContext?: typeof UMB_DOCUMENT_WORKSPACE_CONTEXT.TYPE; #templateRepository = new UmbTemplateItemRepository(this); + #documentPublishingWorkspaceContext?: typeof UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT.TYPE; @state() private _routeBuilder?: UmbModalRouteBuilder; @@ -74,6 +78,11 @@ export class UmbDocumentWorkspaceViewInfoElement extends UmbLitElement { this._variant = currentVariant; }); }); + + this.consumeContext(UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT, (instance) => { + this.#documentPublishingWorkspaceContext = instance; + this.#observePendingChanges(); + }); } #observeContent() { @@ -111,6 +120,20 @@ export class UmbDocumentWorkspaceViewInfoElement extends UmbLitElement { ); } + #observePendingChanges() { + this.observe( + this.#documentPublishingWorkspaceContext?.publishedPendingChanges.variantsWithChanges, + (variants) => { + this._variantsWithPendingChanges = variants || []; + }, + '_observePendingChanges', + ); + } + + #hasPendingChanges(variant: UmbDocumentVariantModel) { + return this._variantsWithPendingChanges.some((x) => x.variantId.compare(variant)); + } + #renderStateTag() { switch (this._variant?.state) { case DocumentVariantStateModel.DRAFT: @@ -119,18 +142,17 @@ export class UmbDocumentWorkspaceViewInfoElement extends UmbLitElement { ${this.localize.term('content_unpublished')} `; + // TODO: The pending changes state can be removed once the management Api removes this state + // We should also make our own state model for this case DocumentVariantStateModel.PUBLISHED: + case DocumentVariantStateModel.PUBLISHED_PENDING_CHANGES: { + const term = this.#hasPendingChanges(this._variant) ? 'content_publishedPendingChanges' : 'content_published'; return html` - - ${this.localize.term('content_published')} - - `; - case DocumentVariantStateModel.PUBLISHED_PENDING_CHANGES: - return html` - - ${this.localize.term('content_publishedPendingChanges')} + + ${this.localize.term(term)} `; + } default: return html` @@ -160,10 +182,7 @@ export class UmbDocumentWorkspaceViewInfoElement extends UmbLitElement { const editDocumentTypePath = this._routeBuilder?.({ entityType: 'document-type' }) ?? ''; return html` -
- Publication Status - ${this.#renderStateTag()} -
+
${this.#renderStateTag()}
${this.#renderCreateDate()}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/item/media-item.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/item/media-item.repository.ts index 178d7a6e17ff..17082f54ab43 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/item/media-item.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/item/media-item.repository.ts @@ -13,6 +13,10 @@ export class UmbMediaItemRepository extends UmbItemRepositoryBase