diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts index 279e297a9466..e1ea23c1d8a0 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts @@ -1469,6 +1469,7 @@ export default { noDiff: 'There are no differences between the current (draft) version and the selected version.', contentRolledBack: 'Content has been rolled back', documentRolledBack: 'Document has been rolled back', + elementRolledBack: 'Element has been rolled back', headline: 'Select a version to compare with the current version', htmlHelp: 'This displays the selected version as HTML, if you wish to see the difference between 2 versions at the same time, use the diff view', diff --git a/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/index.ts b/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/index.ts index c813558d9e77..3d76f338dddc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/index.ts @@ -1 +1 @@ -export { UmbElementAuditLogRepository } from './repository/index.js'; +export * from './repository/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/info-app/element-history-workspace-info-app.element.ts b/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/info-app/element-history-workspace-info-app.element.ts deleted file mode 100644 index 65cf3e9d0193..000000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/info-app/element-history-workspace-info-app.element.ts +++ /dev/null @@ -1,212 +0,0 @@ -import { UmbElementAuditLogRepository } from '../repository/index.js'; -import { UMB_ELEMENT_WORKSPACE_CONTEXT } from '../../workspace/constants.js'; -import type { UmbElementAuditLogModel } from '../types.js'; -import { getElementHistoryTagStyleAndText } from './utils.js'; -import { css, customElement, html, nothing, repeat, state, when } from '@umbraco-cms/backoffice/external/lit'; -import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbPaginationManager, UMB_DATE_TIME_FORMAT_OPTIONS } from '@umbraco-cms/backoffice/utils'; -import { UmbRequestReloadStructureForEntityEvent } from '@umbraco-cms/backoffice/entity-action'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { UmbUserItemRepository } from '@umbraco-cms/backoffice/user'; -import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; -import type { ManifestEntityAction } from '@umbraco-cms/backoffice/entity-action'; -import type { UmbUserItemModel } from '@umbraco-cms/backoffice/user'; -import type { UUIPaginationEvent } from '@umbraco-cms/backoffice/external/uui'; - -@customElement('umb-element-history-workspace-info-app') -export class UmbElementHistoryWorkspaceInfoAppElement extends UmbLitElement { - #allowedActions = new Set(['Umb.EntityAction.Element.Rollback']); - - #auditLogRepository = new UmbElementAuditLogRepository(this); - - #pagination = new UmbPaginationManager(); - - #userItemRepository = new UmbUserItemRepository(this); - - #userMap = new Map(); - - #workspaceContext?: typeof UMB_ELEMENT_WORKSPACE_CONTEXT.TYPE; - - @state() - private _currentPageNumber = 1; - - @state() - private _items: Array = []; - - @state() - private _totalPages = 1; - - constructor() { - super(); - - this.#pagination.setPageSize(10); - this.observe(this.#pagination.currentPage, (number) => (this._currentPageNumber = number)); - this.observe(this.#pagination.totalPages, (number) => (this._totalPages = number)); - - this.consumeContext(UMB_ACTION_EVENT_CONTEXT, (context) => { - context?.addEventListener(UmbRequestReloadStructureForEntityEvent.TYPE, () => { - this.#requestAuditLogs(); - }); - }); - - this.consumeContext(UMB_ELEMENT_WORKSPACE_CONTEXT, (instance) => { - this.#workspaceContext = instance; - this.#requestAuditLogs(); - }); - } - - #onPageChange(event: UUIPaginationEvent) { - this.#pagination.setCurrentPageNumber(event.target?.current); - this.#requestAuditLogs(); - } - - async #requestAuditLogs() { - if (!this.#workspaceContext) return; - const unique = this.#workspaceContext.getUnique(); - if (!unique) throw new Error('Element unique is required'); - - const { data } = await this.#auditLogRepository.requestAuditLog({ - unique, - skip: this.#pagination.getSkip(), - take: this.#pagination.getPageSize(), - }); - - if (data) { - this._items = data.items; - this.#pagination.setTotalItems(data.total); - this.#requestAndCacheUserItems(); - } - } - - async #requestAndCacheUserItems() { - const allUsers = this._items?.map((item) => item.user.unique).filter(Boolean) as string[]; - const uniqueUsers = [...new Set(allUsers)]; - const uncachedUsers = uniqueUsers.filter((unique) => !this.#userMap.has(unique)); - - // If there are no uncached user items, we don't need to make a request - if (uncachedUsers.length === 0) return; - - const { data: items } = await this.#userItemRepository.requestItems(uncachedUsers); - - if (items) { - items.forEach((item) => { - // cache the user item - this.#userMap.set(item.unique, item); - this.requestUpdate('_items'); - }); - } - } - - override render() { - return html` - - - this.#allowedActions.has(manifest.alias)}> - -
- ${when( - this._items, - () => this.#renderHistory(), - () => html`
`, - )} - ${this.#renderPagination()} -
-
- `; - } - - #renderHistory() { - if (!this._items?.length) return html`${this.localize.term('content_noItemsToShow')}`; - return html` - - ${repeat( - this._items, - (item) => item.timestamp, - (item) => { - const { text, style } = getElementHistoryTagStyleAndText(item.logType); - const user = this.#userMap.get(item.user.unique); - - return html` - - - -
- - ${this.localize.term(text.label, item.parameters)} - - ${this.localize.term(text.desc, item.parameters)} -
-
- `; - }, - )} -
- `; - } - - #renderPagination() { - if (this._totalPages <= 1) return nothing; - return html` - - `; - } - - static override styles = [ - UmbTextStyles, - css` - #content { - display: block; - padding: var(--uui-size-space-4) var(--uui-size-space-5); - } - - #loader { - display: flex; - justify-content: center; - } - - .log-type { - display: grid; - grid-template-columns: var(--uui-size-40) auto; - gap: var(--uui-size-layout-1); - } - - .log-type uui-tag { - justify-self: center; - height: fit-content; - margin-top: auto; - margin-bottom: auto; - } - - uui-pagination { - flex: 1; - display: flex; - justify-content: center; - margin-top: var(--uui-size-layout-1); - } - `, - ]; -} - -export default UmbElementHistoryWorkspaceInfoAppElement; - -declare global { - interface HTMLElementTagNameMap { - 'umb-element-history-workspace-info-app': UmbElementHistoryWorkspaceInfoAppElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/info-app/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/info-app/manifests.ts index 1304e49b05c5..01309cf050c8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/info-app/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/info-app/manifests.ts @@ -1,13 +1,16 @@ +import { UMB_ELEMENT_AUDIT_LOG_REPOSITORY_ALIAS } from '../repository/constants.js'; import { UMB_ELEMENT_WORKSPACE_ALIAS } from '../../workspace/constants.js'; import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { type: 'workspaceInfoApp', + kind: 'auditLog', name: 'Element History Workspace Info App', alias: 'Umb.WorkspaceInfoApp.Element.History', - element: () => import('./element-history-workspace-info-app.element.js'), - weight: 80, + meta: { + auditLogRepositoryAlias: UMB_ELEMENT_AUDIT_LOG_REPOSITORY_ALIAS, + }, conditions: [ { alias: UMB_WORKSPACE_CONDITION_ALIAS, diff --git a/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/info-app/utils.ts b/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/info-app/utils.ts deleted file mode 100644 index 26ae57c61c75..000000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/info-app/utils.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { UmbElementAuditLog, type UmbElementAuditLogType } from '../utils/index.js'; - -interface HistoryStyleMap { - look: 'default' | 'primary' | 'secondary' | 'outline' | 'placeholder'; - color: 'default' | 'danger' | 'warning' | 'positive'; -} - -interface HistoryLocalizeKeys { - label: string; - desc: string; -} - -interface HistoryData { - style: HistoryStyleMap; - text: HistoryLocalizeKeys; -} - -/** - * @description Helper function to get look and color for uui-tag and localization keys for the label and description. - * @param type AuditTypeModel - * @returns {HistoricData} - */ -export function getElementHistoryTagStyleAndText(type: UmbElementAuditLogType): HistoryData { - switch (type) { - case UmbElementAuditLog.SAVE: - return { - style: { look: 'primary', color: 'default' }, - text: { label: 'auditTrails_smallSave', desc: 'auditTrailsElement_save' }, - }; - - case UmbElementAuditLog.SAVE_VARIANT: - return { - style: { look: 'primary', color: 'default' }, - text: { label: 'auditTrails_smallSaveVariant', desc: 'auditTrailsElement_savevariant' }, - }; - - case UmbElementAuditLog.PUBLISH: - return { - style: { look: 'primary', color: 'positive' }, - text: { label: 'auditTrails_smallPublish', desc: 'auditTrailsElement_publish' }, - }; - - case UmbElementAuditLog.UNPUBLISH: - return { - style: { look: 'primary', color: 'warning' }, - text: { label: 'auditTrails_smallUnpublish', desc: 'auditTrailsElement_unpublish' }, - }; - - case UmbElementAuditLog.PUBLISH_VARIANT: - return { - style: { look: 'primary', color: 'positive' }, - text: { label: 'auditTrails_smallPublishVariant', desc: 'auditTrailsElement_publishvariant' }, - }; - - case UmbElementAuditLog.UNPUBLISH_VARIANT: - return { - style: { look: 'primary', color: 'warning' }, - text: { label: 'auditTrails_smallUnpublishVariant', desc: 'auditTrailsElement_unpublishvariant' }, - }; - - case UmbElementAuditLog.CONTENT_VERSION_ENABLE_CLEANUP: - return { - style: { look: 'secondary', color: 'default' }, - text: { - label: 'auditTrails_smallContentVersionEnableCleanup', - desc: 'auditTrailsElement_contentversionenablecleanup', - }, - }; - - case UmbElementAuditLog.CONTENT_VERSION_PREVENT_CLEANUP: - return { - style: { look: 'secondary', color: 'default' }, - text: { - label: 'auditTrails_smallContentVersionPreventCleanup', - desc: 'auditTrailsElement_contentversionpreventcleanup', - }, - }; - - case UmbElementAuditLog.COPY: - return { - style: { look: 'secondary', color: 'default' }, - text: { label: 'auditTrails_smallCopy', desc: 'auditTrailsElement_copy' }, - }; - - case UmbElementAuditLog.MOVE: - return { - style: { look: 'secondary', color: 'default' }, - text: { label: 'auditTrails_smallMove', desc: 'auditTrailsElement_move' }, - }; - - case UmbElementAuditLog.DELETE: - return { - style: { look: 'secondary', color: 'danger' }, - text: { label: 'auditTrails_smallDelete', desc: 'auditTrailsElement_delete' }, - }; - - case UmbElementAuditLog.ROLL_BACK: - return { - style: { look: 'secondary', color: 'default' }, - text: { label: 'auditTrails_smallRollBack', desc: 'auditTrailsElement_rollback' }, - }; - - case UmbElementAuditLog.CUSTOM: - return { - style: { look: 'placeholder', color: 'default' }, - text: { label: 'auditTrails_smallCustom', desc: 'auditTrailsElement_custom' }, - }; - - default: - return { - style: { look: 'placeholder', color: 'default' }, - text: { label: type, desc: '' }, - }; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/manifests.ts index c72138184b5a..d804039738aa 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/manifests.ts @@ -1,3 +1,4 @@ import { manifests as infoAppManifests } from './info-app/manifests.js'; +import { manifests as repositoryManifests } from './repository/manifests.js'; -export const manifests: Array = [...infoAppManifests]; +export const manifests: Array = [...infoAppManifests, ...repositoryManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/repository/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/repository/constants.ts new file mode 100644 index 000000000000..dc45c0cd9bad --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/repository/constants.ts @@ -0,0 +1 @@ +export const UMB_ELEMENT_AUDIT_LOG_REPOSITORY_ALIAS = 'Umb.Repository.Element.AuditLog'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/repository/element-audit-log.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/repository/element-audit-log.repository.ts index dfcb7fb3e596..419b248906ac 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/repository/element-audit-log.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/repository/element-audit-log.repository.ts @@ -1,9 +1,75 @@ import type { UmbElementAuditLogModel } from '../types.js'; +import { UmbElementAuditLog } from '../utils/index.js'; import { UmbElementAuditLogServerDataSource } from './element-audit-log.server.data-source.js'; -import type { UmbAuditLogRepository, UmbAuditLogRequestArgs } from '@umbraco-cms/backoffice/audit-log'; +import type { + UmbAuditLogRepository, + UmbAuditLogRequestArgs, + UmbAuditLogTagData, +} from '@umbraco-cms/backoffice/audit-log'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository'; +const UMB_ELEMENT_AUDITLOG_TAG_LOOKUP: Readonly> = Object.freeze({ + [UmbElementAuditLog.SAVE]: { + style: { look: 'primary', color: 'default' }, + text: { label: 'auditTrails_smallSave', desc: 'auditTrailsElement_save' }, + }, + [UmbElementAuditLog.SAVE_VARIANT]: { + style: { look: 'primary', color: 'default' }, + text: { label: 'auditTrails_smallSaveVariant', desc: 'auditTrailsElement_savevariant' }, + }, + [UmbElementAuditLog.PUBLISH]: { + style: { look: 'primary', color: 'positive' }, + text: { label: 'auditTrails_smallPublish', desc: 'auditTrailsElement_publish' }, + }, + [UmbElementAuditLog.UNPUBLISH]: { + style: { look: 'primary', color: 'warning' }, + text: { label: 'auditTrails_smallUnpublish', desc: 'auditTrailsElement_unpublish' }, + }, + [UmbElementAuditLog.PUBLISH_VARIANT]: { + style: { look: 'primary', color: 'positive' }, + text: { label: 'auditTrails_smallPublishVariant', desc: 'auditTrailsElement_publishvariant' }, + }, + [UmbElementAuditLog.UNPUBLISH_VARIANT]: { + style: { look: 'primary', color: 'warning' }, + text: { label: 'auditTrails_smallUnpublishVariant', desc: 'auditTrailsElement_unpublishvariant' }, + }, + [UmbElementAuditLog.CONTENT_VERSION_ENABLE_CLEANUP]: { + style: { look: 'secondary', color: 'default' }, + text: { + label: 'auditTrails_smallContentVersionEnableCleanup', + desc: 'auditTrailsElement_contentversionenablecleanup', + }, + }, + [UmbElementAuditLog.CONTENT_VERSION_PREVENT_CLEANUP]: { + style: { look: 'secondary', color: 'default' }, + text: { + label: 'auditTrails_smallContentVersionPreventCleanup', + desc: 'auditTrailsElement_contentversionpreventcleanup', + }, + }, + [UmbElementAuditLog.COPY]: { + style: { look: 'secondary', color: 'default' }, + text: { label: 'auditTrails_smallCopy', desc: 'auditTrailsElement_copy' }, + }, + [UmbElementAuditLog.MOVE]: { + style: { look: 'secondary', color: 'default' }, + text: { label: 'auditTrails_smallMove', desc: 'auditTrailsElement_move' }, + }, + [UmbElementAuditLog.DELETE]: { + style: { look: 'secondary', color: 'danger' }, + text: { label: 'auditTrails_smallDelete', desc: 'auditTrailsElement_delete' }, + }, + [UmbElementAuditLog.ROLL_BACK]: { + style: { look: 'secondary', color: 'default' }, + text: { label: 'auditTrails_smallRollBack', desc: 'auditTrailsElement_rollback' }, + }, + [UmbElementAuditLog.CUSTOM]: { + style: { look: 'placeholder', color: 'default' }, + text: { label: 'auditTrails_smallCustom', desc: 'auditTrailsElement_custom' }, + }, +}); + /** * Repository for the element audit log * @class UmbElementAuditLogRepository @@ -26,7 +92,7 @@ export class UmbElementAuditLogRepository } /** - * Request the audit log for a element + * Request the audit log for an element * @param {UmbAuditLogRequestArgs} args * @returns {*} * @memberof UmbElementAuditLogRepository @@ -34,4 +100,21 @@ export class UmbElementAuditLogRepository async requestAuditLog(args: UmbAuditLogRequestArgs) { return this.#dataSource.getAuditLog(args); } + + /** + * Get the tag style and localization data for a given audit log type + * @param {string} logType + * @returns {UmbAuditLogTagData} + * @memberof UmbElementAuditLogRepository + */ + getTagStyleAndText(logType: string): UmbAuditLogTagData { + return ( + UMB_ELEMENT_AUDITLOG_TAG_LOOKUP[logType] ?? { + style: { look: 'placeholder', color: 'default' }, + text: { label: logType, desc: '' }, + } + ); + } } + +export { UmbElementAuditLogRepository as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/repository/index.ts b/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/repository/index.ts index 18e8be399339..04273f5f87c2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/repository/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/repository/index.ts @@ -1 +1,2 @@ +export { UMB_ELEMENT_AUDIT_LOG_REPOSITORY_ALIAS } from './constants.js'; export { UmbElementAuditLogRepository } from './element-audit-log.repository.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/repository/manifests.ts new file mode 100644 index 000000000000..e521bcaf09c4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/elements/audit-log/repository/manifests.ts @@ -0,0 +1,10 @@ +import { UMB_ELEMENT_AUDIT_LOG_REPOSITORY_ALIAS } from './constants.js'; + +export const manifests: Array = [ + { + type: 'repository', + alias: UMB_ELEMENT_AUDIT_LOG_REPOSITORY_ALIAS, + name: 'Element Audit Log Repository', + api: () => import('./element-audit-log.repository.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/audit-log-action/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/audit-log-action/manifests.ts new file mode 100644 index 000000000000..f6b7e69ae574 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/audit-log-action/manifests.ts @@ -0,0 +1,32 @@ +import { UMB_ELEMENT_ENTITY_TYPE } from '../../entity.js'; +import { + UMB_ELEMENT_DETAIL_REPOSITORY_ALIAS, + UMB_ELEMENT_ROLLBACK_REPOSITORY_ALIAS, + UMB_ELEMENT_USER_PERMISSION_CONDITION_ALIAS, + UMB_USER_PERMISSION_ELEMENT_ROLLBACK, +} from '../../constants.js'; +import { UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS } from '@umbraco-cms/backoffice/recycle-bin'; + +export const manifests: Array = [ + { + type: 'auditLogAction', + kind: 'contentRollback', + alias: 'Umb.AuditLogAction.Element.Rollback', + name: 'Element Audit Log Rollback Action', + forEntityTypes: [UMB_ELEMENT_ENTITY_TYPE], + meta: { + rollbackNotificationMessage: '#rollback_elementRolledBack', + rollbackRepositoryAlias: UMB_ELEMENT_ROLLBACK_REPOSITORY_ALIAS, + detailRepositoryAlias: UMB_ELEMENT_DETAIL_REPOSITORY_ALIAS, + }, + conditions: [ + { + alias: UMB_ELEMENT_USER_PERMISSION_CONDITION_ALIAS, + allOf: [UMB_USER_PERMISSION_ELEMENT_ROLLBACK], + }, + { + alias: UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS, + }, + ], + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/constants.ts index 9c35ab3ee734..41a409dec1f0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/constants.ts @@ -1,2 +1 @@ -export * from './modal/constants.js'; export * from './repository/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/entity-action/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/entity-action/manifests.ts index 32ffa75cea14..655f979a39f0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/entity-action/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/entity-action/manifests.ts @@ -1,24 +1,27 @@ +import { + UMB_ELEMENT_DETAIL_REPOSITORY_ALIAS, + UMB_ELEMENT_USER_PERMISSION_CONDITION_ALIAS, + UMB_ELEMENT_ROLLBACK_REPOSITORY_ALIAS, + UMB_USER_PERMISSION_ELEMENT_ROLLBACK, +} from '../../constants.js'; import { UMB_ELEMENT_ENTITY_TYPE } from '../../entity.js'; -import { UMB_USER_PERMISSION_ELEMENT_ROLLBACK } 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', + kind: 'contentRollback', alias: 'Umb.EntityAction.Element.Rollback', - name: 'Element Rollback Entity Action', - weight: 450, - api: () => import('./rollback.action.js'), + name: 'Rollback Element Entity Action', forEntityTypes: [UMB_ELEMENT_ENTITY_TYPE], meta: { - icon: 'icon-history', - label: '#actions_rollback', - additionalOptions: true, + rollbackNotificationMessage: '#rollback_elementRolledBack', + rollbackRepositoryAlias: UMB_ELEMENT_ROLLBACK_REPOSITORY_ALIAS, + detailRepositoryAlias: UMB_ELEMENT_DETAIL_REPOSITORY_ALIAS, }, conditions: [ { - alias: 'Umb.Condition.UserPermission.Element', + alias: UMB_ELEMENT_USER_PERMISSION_CONDITION_ALIAS, allOf: [UMB_USER_PERMISSION_ELEMENT_ROLLBACK], }, { diff --git a/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/entity-action/rollback.action.ts b/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/entity-action/rollback.action.ts deleted file mode 100644 index b5245b56ab93..000000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/entity-action/rollback.action.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { UMB_ELEMENT_ROLLBACK_MODAL } from '../constants.js'; -import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; -import { umbOpenModal } from '@umbraco-cms/backoffice/modal'; -import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification'; -import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api'; - -export class UmbRollbackElementEntityAction extends UmbEntityActionBase { - #localize = new UmbLocalizationController(this); - - override async execute() { - try { - await umbOpenModal(this, UMB_ELEMENT_ROLLBACK_MODAL, {}); - const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT); - if (!notificationContext) { - throw new Error('Notification context not found'); - } - notificationContext.peek('positive', { - data: { message: this.#localize.term('rollback_elementRolledBack') }, - }); - } catch { - // User cancelled the modal - } - } -} - -export { UmbRollbackElementEntityAction as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/manifests.ts index bd1037decba0..6a59974ce235 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/manifests.ts @@ -1,9 +1,9 @@ +import { manifests as auditLogActionManifests } from './audit-log-action/manifests.js'; import { manifests as entityActionManifests } from './entity-action/manifests.js'; -import { manifests as modalManifests } from './modal/manifests.js'; import { manifests as repositoryManifests } from './repository/manifests.js'; export const manifests: Array = [ + ...auditLogActionManifests, ...entityActionManifests, - ...modalManifests, ...repositoryManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/modal/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/modal/constants.ts deleted file mode 100644 index cd70c646d06b..000000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/modal/constants.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { UmbElementRollbackModalData, UmbElementRollbackModalValue } from './types.js'; -import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; - -export const UMB_ELEMENT_ROLLBACK_MODAL_ALIAS = 'Umb.Modal.Element.Rollback'; - -export const UMB_ELEMENT_ROLLBACK_MODAL = new UmbModalToken( - UMB_ELEMENT_ROLLBACK_MODAL_ALIAS, - { - modal: { - type: 'sidebar', - size: 'full', - }, - }, -); diff --git a/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/modal/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/modal/manifests.ts deleted file mode 100644 index 35ddd94b8d49..000000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/modal/manifests.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { UMB_ELEMENT_ROLLBACK_MODAL_ALIAS } from './constants.js'; - -export const manifests: Array = [ - { - type: 'modal', - alias: UMB_ELEMENT_ROLLBACK_MODAL_ALIAS, - name: 'Element Rollback Modal', - element: () => import('./rollback-modal.element.js'), - }, -]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/modal/rollback-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/modal/rollback-modal.element.ts deleted file mode 100644 index bed3516ed949..000000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/modal/rollback-modal.element.ts +++ /dev/null @@ -1,593 +0,0 @@ -import { UmbElementRollbackRepository } from '../repository/rollback.repository.js'; -import { UmbElementDetailRepository } from '../../repository/index.js'; -import { UMB_ELEMENT_ENTITY_TYPE } from '../../entity.js'; -import type { UmbElementDetailModel } from '../../types.js'; -import type { UmbElementRollbackModalData, UmbElementRollbackModalValue } from './types.js'; -import { css, customElement, html, nothing, repeat, state, unsafeHTML } from '@umbraco-cms/backoffice/external/lit'; -import { diffWords } from '@umbraco-cms/backoffice/utils'; -import { UmbEntityUpdatedEvent, UmbRequestReloadStructureForEntityEvent } from '@umbraco-cms/backoffice/entity-action'; -import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { UmbUserItemRepository } from '@umbraco-cms/backoffice/user'; -import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; -import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; -import { UMB_APP_LANGUAGE_CONTEXT, UmbLanguageItemRepository } from '@umbraco-cms/backoffice/language'; -import { UMB_ENTITY_CONTEXT } from '@umbraco-cms/backoffice/entity'; -import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property'; -import type { UmbDiffChange } from '@umbraco-cms/backoffice/utils'; -import type { UUISelectEvent, UUISelectOption } from '@umbraco-cms/backoffice/external/uui'; - -import '../../modals/shared/element-variant-language-picker.element.js'; - -type ElementVersion = { - id: string; - date: string; - user: string; - isCurrentlyPublishedVersion: boolean; - preventCleanup: boolean; -}; - -@customElement('umb-element-rollback-modal') -export class UmbElementRollbackModalElement extends UmbModalBaseElement< - UmbElementRollbackModalData, - UmbElementRollbackModalValue -> { - @state() - private _versions: ElementVersion[] = []; - - @state() - private _selectedVersion?: { - date: string; - name: string; - user: string; - id: string; - properties: { - alias: string; - value: string; - }[]; - }; - - @state() - private _selectedCulture: string | null = null; - - @state() - private _isInvariant = true; - - @state() - private _availableVariants: UUISelectOption[] = []; - - @state() - private _diffs: Array<{ alias: string; diff: UmbDiffChange[] }> = []; - - @state() - private _showDiff = false; - - #rollbackRepository = new UmbElementRollbackRepository(this); - #userItemRepository = new UmbUserItemRepository(this); - - #localizeDateOptions: Intl.DateTimeFormatOptions = { - day: 'numeric', - month: 'long', - hour: 'numeric', - minute: '2-digit', - }; - - #currentElement: UmbElementDetailModel | undefined; - #currentAppCulture: string | undefined; - #currentDatasetCulture: string | undefined; - - constructor() { - super(); - - this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, (instance) => { - this.#currentDatasetCulture = instance?.getVariantId().culture ?? undefined; - this.#selectCulture(); - }); - - this.consumeContext(UMB_APP_LANGUAGE_CONTEXT, (instance) => { - this.#currentAppCulture = instance?.getAppCulture(); - this.#selectCulture(); - }); - - this.consumeContext(UMB_ENTITY_CONTEXT, async (instance) => { - if (!instance) return; - if (instance.getEntityType() !== UMB_ELEMENT_ENTITY_TYPE) { - throw new Error(`Entity type is not ${UMB_ELEMENT_ENTITY_TYPE}`); - } - - const unique = instance.getUnique(); - - if (!unique) { - throw new Error('Element unique is not set'); - } - - const { data } = await new UmbElementDetailRepository(this).requestByUnique(unique); - if (!data) return; - - this.#currentElement = data; - const itemVariants = this.#currentElement?.variants ?? []; - - this._isInvariant = itemVariants.length === 1 && new UmbVariantId(itemVariants[0].culture).isInvariant(); - this.#selectCulture(); - - const cultures = itemVariants.map((x) => x.culture).filter((x) => x !== null) as string[]; - const { data: languageItems } = await new UmbLanguageItemRepository(this).requestItems(cultures); - - if (languageItems) { - this._availableVariants = languageItems.map((language) => { - return { - name: language.name, - value: language.unique, - selected: language.unique === this._selectedCulture, - }; - }); - } else { - this._availableVariants = []; - } - - this.#requestVersions(); - }); - } - - #selectCulture() { - const contextCulture = this.#currentDatasetCulture ?? this.#currentAppCulture ?? null; - this._selectedCulture = this._isInvariant ? null : contextCulture; - } - - async #requestVersions() { - if (!this.#currentElement?.unique) { - throw new Error('Element unique is not set'); - } - - const { data } = await this.#rollbackRepository.requestVersionsByElementId( - this.#currentElement?.unique, - this._selectedCulture ?? undefined, - ); - if (!data) return; - - const tempItems: ElementVersion[] = []; - - const uniqueUserIds = [...new Set(data?.items.map((item) => item.user.id))]; - - const { data: userItems } = await this.#userItemRepository.requestItems(uniqueUserIds); - - data?.items.forEach((item: any) => { - if (item.isCurrentDraftVersion) return; - - tempItems.push({ - date: item.versionDate, - user: - userItems?.find((user) => user.unique === item.user.id)?.name || this.localize.term('general_unknownUser'), - isCurrentlyPublishedVersion: item.isCurrentPublishedVersion, - id: item.id, - preventCleanup: item.preventCleanup, - }); - }); - - this._versions = tempItems; - const id = tempItems.find((item) => item.isCurrentlyPublishedVersion)?.id; - - if (id) { - this.#selectVersion(id); - } - } - - async #selectVersion(id: string) { - const version = this._versions.find((item) => item.id === id); - - if (!version) { - this._selectedVersion = undefined; - this._diffs = []; - return; - } - - const { data } = await this.#rollbackRepository.requestVersionById(id); - - if (!data) { - this._selectedVersion = undefined; - this._diffs = []; - return; - } - - this._selectedVersion = { - date: version.date, - user: version.user, - name: data.variants.find((x) => x.culture === this._selectedCulture)?.name || data.variants[0].name, - id: data.id, - properties: data.values - .filter((x) => x.culture === this._selectedCulture || !x.culture) // When invariant, culture is undefined or null. - .map((value: any) => { - return { - alias: value.alias, - value: value.value, - }; - }), - }; - - if (this._showDiff) { - await this.#setDiffs(); - } - } - - async #onRollback() { - if (!this._selectedVersion) return; - - const id = this._selectedVersion.id; - const culture = this._selectedCulture ?? undefined; - - const { error } = await this.#rollbackRepository.rollback(id, culture); - if (error) return; - - const unique = this.#currentElement?.unique; - const entityType = this.#currentElement?.entityType; - - if (!unique || !entityType) { - throw new Error('Element unique or entity type is not set'); - } - - const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); - if (!actionEventContext) { - throw new Error('Action event context not found'); - } - - const reloadStructureEvent = new UmbRequestReloadStructureForEntityEvent({ unique, entityType }); - actionEventContext.dispatchEvent(reloadStructureEvent); - - const entityUpdatedEvent = new UmbEntityUpdatedEvent({ unique, entityType }); - actionEventContext.dispatchEvent(entityUpdatedEvent); - - this.value = {}; - - this.modalContext?.submit(); - } - - #onCancel() { - this.modalContext?.reject(); - } - - #onVersionClicked(id: string) { - this.#selectVersion(id); - } - - #onPreventCleanup(event: Event, id: string, preventCleanup: boolean) { - event.preventDefault(); - event.stopImmediatePropagation(); - this.#rollbackRepository.setPreventCleanup(id, preventCleanup); - - const version = this._versions.find((item) => item.id === id); - if (!version) return; - - version.preventCleanup = preventCleanup; - this.requestUpdate('_versions'); - } - - #onChangeCulture(event: UUISelectEvent) { - const value = event.target.value; - - this._selectedCulture = value.toString(); - this.#requestVersions(); - } - - #onToggleDiff(event: Event) { - const checkbox = event.target as HTMLInputElement; - this._showDiff = checkbox.checked; - - // If we have toggled on and have a selected version, immediately show the diff. - if (this._showDiff && this._selectedVersion) { - this.#setDiffs(); - } - } - - #trimQuotes(str: string): string { - return str.replace(/^['"]|['"]$/g, ''); - } - - #getPlainValue(alias: string): string { - if (alias === 'name') { - return this._selectedVersion?.name ?? ''; - } - const prop = this._selectedVersion?.properties.find((x) => x.alias === alias); - return prop ? this.#trimQuotes(JSON.stringify(prop.value)) : ''; - } - - #renderCultureSelect() { - return html` - - `; - } - - #renderVersions() { - if (!this._versions.length) { - return html`No versions available`; - } - - return html` - ${repeat( - this._versions, - (item) => item.id, - (item) => { - return html` -
this.#onVersionClicked(item.id)} - role="button" - tabindex="0" - @keydown=${() => {}} - class="rollback-item ${this._selectedVersion?.id === item.id ? 'active' : ''}"> -
-

- -

-

${item.user}

-

${item.isCurrentlyPublishedVersion ? this.localize.term('rollback_currentPublishedVersion') : ''}

-
- this.#onPreventCleanup(event, item.id, !item.preventCleanup)} - label=${item.preventCleanup - ? this.localize.term('contentTypeEditor_historyCleanupEnableCleanup') - : this.localize.term('contentTypeEditor_historyCleanupPreventCleanup')}> -
- `; - }, - )}
`; - } - - async #setDiffs() { - if (!this._selectedVersion) return; - - const currentPropertyValues = this.#currentElement?.values.filter( - (x) => x.culture === this._selectedCulture || !x.culture, - ); // When invariant, culture is undefined or null. - - if (!currentPropertyValues) { - throw new Error('Current property values are not set'); - } - - const currentName = this.#currentElement?.variants.find((x) => x.culture === this._selectedCulture)?.name; - - if (!currentName) { - throw new Error('Current name is not set'); - } - - const diffs: Array<{ alias: string; diff: UmbDiffChange[] }> = []; - - const nameDiff = diffWords(currentName, this._selectedVersion.name); - diffs.push({ alias: 'name', diff: nameDiff }); - - this._selectedVersion.properties.forEach((item) => { - const draftValue = currentPropertyValues.find((x) => x.alias === item.alias); - - if (!draftValue) return; - - const draftValueString = this.#trimQuotes(JSON.stringify(draftValue.value)); - const versionValueString = this.#trimQuotes(JSON.stringify(item.value)); - - const diff = diffWords(draftValueString, versionValueString); - diffs.push({ alias: item.alias, diff }); - }); - - this._diffs = [...diffs]; - } - - #renderSelectedVersion() { - if (!this._selectedVersion) - return html` - No selected version - `; - - // Build list of property aliases to display (name + all properties) - const propertyAliases = ['name', ...this._selectedVersion.properties.map((p) => p.alias)]; - - return html` - - - ${this._showDiff ? html`

${unsafeHTML(this.localize.term('rollback_diffHelp'))}

` : nothing} - - - - - - ${this.localize.term('general_alias')} - ${this.localize.term('general_value')} - - ${repeat( - propertyAliases, - (alias) => alias, - (alias) => { - const diff = this._diffs.find((x) => x?.alias === alias); - return html` - - ${alias} - - ${this._showDiff && diff - ? diff.diff.map((part) => - part.added - ? html`${part.value}` - : part.removed - ? html`${part.value}` - : part.value, - ) - : this.#getPlainValue(alias)} - - - `; - }, - )} - -
- `; - } - - get currentVersionHeader() { - return ( - this.localize.date(this._selectedVersion?.date ?? new Date(), this.#localizeDateOptions) + - ' - ' + - this._selectedVersion?.user - ); - } - - override render() { - return html` - -
-
- ${this._availableVariants.length - ? html` - - ${this.#renderCultureSelect()} - - ` - : nothing} - ${this.#renderVersions()} -
- ${this.#renderSelectedVersion()} -
- - - - -
- `; - } - - static override styles = [ - UmbTextStyles, - css` - :host { - color: var(--uui-color-text); - } - - #language-box { - margin-bottom: var(--uui-size-space-2); - } - - #language-select { - width: 100%; - } - - uui-table { - --uui-table-cell-padding: var(--uui-size-space-1) var(--uui-size-space-4); - margin-top: var(--uui-size-space-5); - } - uui-table-head-cell:first-child { - border-top-left-radius: var(--uui-border-radius); - } - uui-table-head-cell:last-child { - border-top-right-radius: var(--uui-border-radius); - } - uui-table-head-cell { - background-color: var(--uui-color-surface-alt); - } - uui-table-head-cell:last-child, - uui-table-cell:last-child { - border-right: 1px solid var(--uui-color-border); - } - uui-table-head-cell, - uui-table-cell { - border-top: 1px solid var(--uui-color-border); - border-left: 1px solid var(--uui-color-border); - } - uui-table-row:last-child uui-table-cell { - border-bottom: 1px solid var(--uui-color-border); - } - uui-table-row:last-child uui-table-cell:last-child { - border-bottom-right-radius: var(--uui-border-radius); - } - uui-table-row:last-child uui-table-cell:first-child { - border-bottom-left-radius: var(--uui-border-radius); - } - - .diff-added, - ins { - background-color: #00c43e63; - } - .diff-removed, - del { - background-color: #ff35356a; - } - .rollback-item { - position: relative; - display: flex; - justify-content: space-between; - align-items: center; - padding: var(--uui-size-space-5); - cursor: pointer; - } - .rollback-item::after { - content: ''; - position: absolute; - inset: 2px; - display: block; - border: 2px solid transparent; - pointer-events: none; - } - .rollback-item.active::after, - .rollback-item:hover::after { - border-color: var(--uui-color-selected); - } - .rollback-item:not(.active):hover::after { - opacity: 0.5; - } - .rollback-item p { - margin: 0; - opacity: 0.5; - } - p.rollback-item-date { - opacity: 1; - } - .rollback-item uui-button { - white-space: nowrap; - } - - #main { - display: flex; - gap: var(--uui-size-space-5); - width: 100%; - height: 100%; - } - - #versions-box { - --uui-box-default-padding: 0; - } - - #box-left { - max-width: 500px; - flex: 1; - overflow: auto; - height: 100%; - } - - #box-right { - flex: 1; - overflow: auto; - height: 100%; - } - `, - ]; -} - -export default UmbElementRollbackModalElement; - -declare global { - interface HTMLElementTagNameMap { - 'umb-element-rollback-modal': UmbElementRollbackModalElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/modal/types.ts b/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/modal/types.ts deleted file mode 100644 index e32a2cc7adc3..000000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/modal/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface UmbElementRollbackModalData {} -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface UmbElementRollbackModalValue {} diff --git a/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/repository/rollback.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/repository/rollback.repository.ts index b475258d692b..905b2a05c6cc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/repository/rollback.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/repository/rollback.repository.ts @@ -1,29 +1,111 @@ import { UmbElementRollbackServerDataSource } from './rollback.server.data-source.js'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { + UmbContentRollbackRepository, + UmbContentRollbackVersionDetailModel, + UmbContentRollbackVersionItemModel, +} from '@umbraco-cms/backoffice/content'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; -export class UmbElementRollbackRepository extends UmbControllerBase implements UmbApi { +/** + * Repository for the element rollback feature. + * @class UmbElementRollbackRepository + * @augments {UmbControllerBase} + * @implements {UmbContentRollbackRepository} + */ +export class UmbElementRollbackRepository extends UmbControllerBase implements UmbContentRollbackRepository { #dataSource: UmbElementRollbackServerDataSource; + /** + * Creates an instance of UmbElementRollbackRepository. + * @param {UmbControllerHost} host - The controller host for this controller to be appended to + * @memberof UmbElementRollbackRepository + */ constructor(host: UmbControllerHost) { super(host); this.#dataSource = new UmbElementRollbackServerDataSource(this); } - async requestVersionsByElementId(id: string, culture?: string) { - return await this.#dataSource.getVersionsByElementId(id, culture); + /** + * Request the available versions for an element entity. + * @param {string} id - The unique ID of the element + * @param {string} [culture] - Optional culture to filter versions by + * @returns {Promise<{ data?: { items: Array; total: number }; error?: unknown }>} The list of versions, or an error + * @memberof UmbElementRollbackRepository + */ + async requestVersionsByEntityId( + id: string, + culture?: string, + ): Promise<{ data?: { items: Array; total: number }; error?: unknown }> { + const { data, error } = await this.#dataSource.getVersionsByElementId(id, culture); + + if (data) { + return { + data: { + items: data.items.map((item) => ({ + id: item.id, + versionDate: item.versionDate, + user: { id: item.user.id }, + isCurrentDraftVersion: item.isCurrentDraftVersion, + isCurrentPublishedVersion: item.isCurrentPublishedVersion, + preventCleanup: item.preventCleanup, + })), + total: data.total, + }, + }; + } + + return { error }; } - async requestVersionById(id: string) { - return await this.#dataSource.getVersionById(id); + /** + * Request the details of a specific version. + * @param {string} id - The unique ID of the version + * @returns {Promise<{ data?: UmbContentRollbackVersionDetailModel; error?: unknown }>} The version details, or an error + * @memberof UmbElementRollbackRepository + */ + async requestVersionById(id: string): Promise<{ data?: UmbContentRollbackVersionDetailModel; error?: unknown }> { + const { data, error } = await this.#dataSource.getVersionById(id); + + if (data) { + return { + data: { + id: data.id, + variants: data.variants.map((v) => ({ + culture: v.culture ?? null, + name: v.name, + })), + values: data.values.map((v) => ({ + culture: v.culture ?? null, + alias: v.alias, + value: v.value, + })), + }, + }; + } + + return { error }; } + /** + * Toggle whether a specific version is excluded from automatic content version cleanup. + * @param {string} versionId - The unique ID of the version + * @param {boolean} preventCleanup - `true` to prevent cleanup, `false` to allow it + * @returns {*} The result of the operation + * @memberof UmbElementRollbackRepository + */ async setPreventCleanup(versionId: string, preventCleanup: boolean) { return await this.#dataSource.setPreventCleanup(versionId, preventCleanup); } + /** + * Roll the element back to a specific version. + * @param {string} versionId - The unique ID of the version to roll back to + * @param {string} [culture] - Optional culture to roll back + * @returns {*} The result of the operation + * @memberof UmbElementRollbackRepository + */ async rollback(versionId: string, culture?: string) { return await this.#dataSource.rollback(versionId, culture); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/repository/rollback.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/repository/rollback.server.data-source.ts index 37529f84f330..c79ffd1986eb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/repository/rollback.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/elements/rollback/repository/rollback.server.data-source.ts @@ -3,9 +3,8 @@ import { ElementVersionService } from '@umbraco-cms/backoffice/external/backend- import { tryExecute } from '@umbraco-cms/backoffice/resources'; /** - * A data source for the Rollback that fetches data from the server + * A data source for the element rollback feature that fetches data from the server. * @class UmbElementRollbackServerDataSource - * @implements {RepositoryDetailDataSource} */ export class UmbElementRollbackServerDataSource { #host: UmbControllerHost; @@ -20,10 +19,10 @@ export class UmbElementRollbackServerDataSource { } /** - * Get a list of versions for a element - * @param id - * @param culture - * @returns {*} + * Get a list of versions for an element. + * @param {string} id - The unique ID of the element + * @param {string} [culture] - Optional culture to filter versions by + * @returns {*} The list of versions * @memberof UmbElementRollbackServerDataSource */ getVersionsByElementId(id: string, culture?: string) { @@ -31,15 +30,22 @@ export class UmbElementRollbackServerDataSource { } /** - * Get a specific version by id - * @param versionId - * @returns {*} + * Get a specific version by id. + * @param {string} versionId - The unique ID of the version + * @returns {*} The version data * @memberof UmbElementRollbackServerDataSource */ getVersionById(versionId: string) { return tryExecute(this.#host, ElementVersionService.getElementVersionById({ path: { id: versionId } })); } + /** + * Toggle whether a specific version is excluded from automatic content version cleanup. + * @param {string} versionId - The unique ID of the version + * @param {boolean} preventCleanup - `true` to prevent cleanup, `false` to allow it + * @returns {*} The result of the operation + * @memberof UmbElementRollbackServerDataSource + */ setPreventCleanup(versionId: string, preventCleanup: boolean) { return tryExecute( this.#host, @@ -50,6 +56,13 @@ export class UmbElementRollbackServerDataSource { ); } + /** + * Roll the element back to a specific version. + * @param {string} versionId - The unique ID of the version to roll back to + * @param {string} [culture] - Optional culture to roll back + * @returns {*} The result of the operation + * @memberof UmbElementRollbackServerDataSource + */ rollback(versionId: string, culture?: string) { return tryExecute( this.#host,