Merge pull request #1554 from umbraco/feature/document-user-permission

Feature: Document User Permission
This commit is contained in:
Lee Kelleher
2024-04-10 14:44:13 +01:00
committed by GitHub
28 changed files with 441 additions and 117 deletions

View File

@@ -1,3 +1,4 @@
import { UmbEntityContext } from '../../entity/entity.context.js';
import type { UmbEntityAction } from '@umbraco-cms/backoffice/entity-action';
import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit';
import { html, nothing, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit';
@@ -6,7 +7,7 @@ import { UMB_SECTION_SIDEBAR_CONTEXT } from '@umbraco-cms/backoffice/section';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type { ManifestEntityActionDefaultKind } from '@umbraco-cms/backoffice/extension-registry';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { createExtensionApi } from '@umbraco-cms/backoffice/extension-api';
import { UmbExtensionsManifestInitializer, createExtensionApi } from '@umbraco-cms/backoffice/extension-api';
@customElement('umb-entity-actions-bundle')
export class UmbEntityActionsBundleElement extends UmbLitElement {
@@ -30,6 +31,8 @@ export class UmbEntityActionsBundleElement extends UmbLitElement {
#sectionSidebarContext?: UmbSectionSidebarContext;
#entityContext = new UmbEntityContext(this);
constructor() {
super();
@@ -40,26 +43,35 @@ export class UmbEntityActionsBundleElement extends UmbLitElement {
protected updated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
if (_changedProperties.has('entityType') && _changedProperties.has('unique')) {
this.#entityContext.setEntityType(this.entityType);
this.#entityContext.setUnique(this.unique);
this.#observeEntityActions();
}
}
#observeEntityActions() {
this.observe(
umbExtensionsRegistry.byTypeAndFilter('entityAction', (ext) => ext.forEntityTypes.includes(this.entityType!)),
new UmbExtensionsManifestInitializer(
this,
umbExtensionsRegistry,
'entityAction',
(ext) => ext.forEntityTypes.includes(this.entityType!),
async (actions) => {
this._numberOfActions = actions.length;
this._firstActionManifest =
this._numberOfActions > 0 ? (actions[0] as ManifestEntityActionDefaultKind) : undefined;
if (!this._firstActionManifest) return;
this._firstActionApi = await createExtensionApi(this, this._firstActionManifest, [
{ unique: this.unique, entityType: this.entityType, meta: this._firstActionManifest.meta },
]);
this._numberOfActions > 0 ? (actions[0].manifest as ManifestEntityActionDefaultKind) : undefined;
this.#createFirstActionApi();
},
'umbEntityActionsObserver',
);
}
async #createFirstActionApi() {
if (!this._firstActionManifest) return;
this._firstActionApi = await createExtensionApi(this, this._firstActionManifest, [
{ unique: this.unique, entityType: this.entityType, meta: this._firstActionManifest.meta },
]);
}
#openContextMenu() {
if (!this.entityType) throw new Error('Entity type is not defined');
if (this.unique === undefined) throw new Error('Unique is not defined');
@@ -73,7 +85,7 @@ export class UmbEntityActionsBundleElement extends UmbLitElement {
render() {
if (this._numberOfActions === 0) return nothing;
return html`<uui-action-bar slot="actions"> ${this.#renderFirstAction()} ${this.#renderMore()} </uui-action-bar>`;
return html`<uui-action-bar slot="actions">${this.#renderMore()} ${this.#renderFirstAction()} </uui-action-bar>`;
}
#renderMore() {

View File

@@ -3,26 +3,26 @@ import type { CollectionBulkActionPermissionConditionConfig } from '../../collec
import type { UmbSectionUserPermissionConditionConfig } from '../../section/conditions/index.js';
import type { SectionAliasConditionConfig } from './section-alias.condition.js';
import type { SwitchConditionConfig } from './switch.condition.js';
import type { UserPermissionConditionConfig } from '@umbraco-cms/backoffice/user-permission';
import type { BlockWorkspaceHasSettingsConditionConfig } from '@umbraco-cms/backoffice/block';
import type {
WorkspaceAliasConditionConfig,
WorkspaceEntityTypeConditionConfig,
} from '@umbraco-cms/backoffice/workspace';
import type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api';
import type { UmbDocumentUserPermissionConditionConfig } from '@umbraco-cms/backoffice/document';
/* TODO: in theory should't the core package import from other packages.
Are there any other way we can do this?
Niels: Sadly I don't see any other solutions currently. But are very open for ideas :-) now that I think about it maybe there is some ability to extend a global type, similar to the 'declare global' trick we use on Elements.
*/
export type ConditionTypes =
| BlockWorkspaceHasSettingsConditionConfig
| CollectionAliasConditionConfig
| CollectionBulkActionPermissionConditionConfig
| SectionAliasConditionConfig
| WorkspaceAliasConditionConfig
| BlockWorkspaceHasSettingsConditionConfig
| WorkspaceEntityTypeConditionConfig
| SwitchConditionConfig
| UserPermissionConditionConfig
| UmbDocumentUserPermissionConditionConfig
| UmbSectionUserPermissionConditionConfig
| WorkspaceAliasConditionConfig
| WorkspaceEntityTypeConditionConfig
| UmbConditionConfigBase;

View File

@@ -21,4 +21,5 @@ export class UmbReloadTreeItemChildrenEntityAction extends UmbEntityActionBase<M
);
}
}
export default UmbReloadTreeItemChildrenEntityAction;
export { UmbReloadTreeItemChildrenEntityAction as api };

View File

@@ -22,9 +22,7 @@ export class UmbSubmitWorkspaceAction extends UmbWorkspaceActionBase<UmbSubmitta
(unique) => {
// We can't save if we don't have a unique
if (unique === undefined) {
this._isDisabled.setValue(true);
} else {
this._isDisabled.setValue(false);
this.disable();
}
},
'saveWorkspaceActionUniqueObserver',

View File

@@ -37,4 +37,20 @@ export abstract class UmbWorkspaceActionBase<ArgsMetaType = never>
public execute(): Promise<void> {
return Promise.resolve();
}
/**
* Disables the action.
* @memberof UmbWorkspaceActionBase
*/
public disable(): void {
this._isDisabled.setValue(true);
}
/**
* Enables the action.
* @memberof UmbWorkspaceActionBase
*/
public enable(): void {
this._isDisabled.setValue(false);
}
}

View File

@@ -1,4 +1,5 @@
import { UMB_DOCUMENT_ENTITY_TYPE, UMB_DOCUMENT_ROOT_ENTITY_TYPE } from '../../entity.js';
import { UMB_USER_PERMISSION_DOCUMENT_CREATE } from '../../user-permissions/index.js';
import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
const entityActions: Array<ManifestTypes> = [
@@ -14,6 +15,12 @@ const entityActions: Array<ManifestTypes> = [
icon: 'icon-add',
label: '#actions_create',
},
conditions: [
{
alias: 'Umb.Condition.UserPermission.Document',
allOf: [UMB_USER_PERMISSION_DOCUMENT_CREATE],
},
],
},
];

View File

@@ -1,4 +1,5 @@
import { UMB_DOCUMENT_ENTITY_TYPE } from '../../entity.js';
import { UMB_USER_PERMISSION_DOCUMENT_CULTURE_AND_HOSTNAMES } from '../../user-permissions/index.js';
import { UmbDocumentCultureAndHostnamesEntityAction } from './culture-and-hostnames.action.js';
import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
@@ -15,6 +16,12 @@ const entityActions: Array<ManifestTypes> = [
icon: 'icon-home',
label: '#actions_assigndomain',
},
conditions: [
{
alias: 'Umb.Condition.UserPermission.Document',
allOf: [UMB_USER_PERMISSION_DOCUMENT_CULTURE_AND_HOSTNAMES],
},
],
},
];

View File

@@ -1,6 +1,16 @@
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_DOCUMENT_PICKER_MODAL } from '../modals/index.js';
import {
UMB_USER_PERMISSION_DOCUMENT_CREATE_BLUEPRINT,
UMB_USER_PERMISSION_DOCUMENT_DELETE,
UMB_USER_PERMISSION_DOCUMENT_DUPLICATE,
UMB_USER_PERMISSION_DOCUMENT_MOVE,
UMB_USER_PERMISSION_DOCUMENT_NOTIFICATIONS,
UMB_USER_PERMISSION_DOCUMENT_PERMISSIONS,
UMB_USER_PERMISSION_DOCUMENT_PUBLISH,
UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH,
} from '../user-permissions/constants.js';
import { manifests as createManifests } from './create/manifests.js';
import { manifests as publicAccessManifests } from './public-access/manifests.js';
import { manifests as cultureAndHostnamesManifests } from './culture-and-hostnames/manifests.js';
@@ -18,6 +28,12 @@ const entityActions: Array<ManifestEntityAction> = [
itemRepositoryAlias: UMB_DOCUMENT_ITEM_REPOSITORY_ALIAS,
detailRepositoryAlias: UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS,
},
conditions: [
{
alias: 'Umb.Condition.UserPermission.Document',
allOf: [UMB_USER_PERMISSION_DOCUMENT_DELETE],
},
],
},
{
type: 'entityAction',
@@ -31,6 +47,12 @@ const entityActions: Array<ManifestEntityAction> = [
icon: 'icon-blueprint',
label: '#actions_createblueprint',
},
conditions: [
{
alias: 'Umb.Condition.UserPermission.Document',
allOf: [UMB_USER_PERMISSION_DOCUMENT_CREATE_BLUEPRINT],
},
],
},
{
type: 'entityAction',
@@ -44,6 +66,12 @@ const entityActions: Array<ManifestEntityAction> = [
itemRepositoryAlias: UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS,
pickerModelAlias: UMB_DOCUMENT_PICKER_MODAL,
},
conditions: [
{
alias: 'Umb.Condition.UserPermission.Document',
allOf: [UMB_USER_PERMISSION_DOCUMENT_MOVE],
},
],
},
{
type: 'entityAction',
@@ -57,6 +85,12 @@ const entityActions: Array<ManifestEntityAction> = [
itemRepositoryAlias: UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS,
pickerModal: UMB_DOCUMENT_PICKER_MODAL,
},
conditions: [
{
alias: 'Umb.Condition.UserPermission.Document',
allOf: [UMB_USER_PERMISSION_DOCUMENT_DUPLICATE],
},
],
},
{
type: 'entityAction',
@@ -70,6 +104,12 @@ const entityActions: Array<ManifestEntityAction> = [
icon: 'icon-globe',
label: '#actions_publish',
},
conditions: [
{
alias: 'Umb.Condition.UserPermission.Document',
allOf: [UMB_USER_PERMISSION_DOCUMENT_PUBLISH],
},
],
},
{
type: 'entityAction',
@@ -83,6 +123,12 @@ const entityActions: Array<ManifestEntityAction> = [
icon: 'icon-globe',
label: '#actions_unpublish',
},
conditions: [
{
alias: 'Umb.Condition.UserPermission.Document',
allOf: [UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH],
},
],
},
{
type: 'entityAction',
@@ -96,6 +142,12 @@ const entityActions: Array<ManifestEntityAction> = [
icon: 'icon-name-badge',
label: '#actions_setPermissions',
},
conditions: [
{
alias: 'Umb.Condition.UserPermission.Document',
allOf: [UMB_USER_PERMISSION_DOCUMENT_PERMISSIONS],
},
],
},
{
type: 'entityAction',
@@ -109,6 +161,12 @@ const entityActions: Array<ManifestEntityAction> = [
icon: 'icon-megaphone',
label: '#actions_notify',
},
conditions: [
{
alias: 'Umb.Condition.UserPermission.Document',
allOf: [UMB_USER_PERMISSION_DOCUMENT_NOTIFICATIONS],
},
],
},
];

View File

@@ -1,4 +1,5 @@
import { UMB_DOCUMENT_ENTITY_TYPE } from '../../entity.js';
import { UMB_USER_PERMISSION_DOCUMENT_PUBLIC_ACCESS } from '../../user-permissions/index.js';
import { UmbDocumentPublicAccessEntityAction } from './public-access.action.js';
import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
@@ -15,6 +16,12 @@ const entityActions: Array<ManifestTypes> = [
icon: 'icon-lock',
label: '#actions_protect',
},
conditions: [
{
alias: 'Umb.Condition.UserPermission.Document',
allOf: [UMB_USER_PERMISSION_DOCUMENT_PUBLIC_ACCESS],
},
],
},
];

View File

@@ -1,6 +1,7 @@
import { UMB_DOCUMENT_ENTITY_TYPE, UMB_DOCUMENT_ROOT_ENTITY_TYPE } from '../../entity.js';
import { UMB_DOCUMENT_ITEM_REPOSITORY_ALIAS } from '../../repository/index.js';
import { UMB_DOCUMENT_TREE_REPOSITORY_ALIAS } from '../../tree/index.js';
import { UMB_USER_PERMISSION_DOCUMENT_SORT } from '../../user-permissions/index.js';
import { UMB_SORT_CHILDREN_OF_DOCUMENT_REPOSITORY_ALIAS } from './repository/constants.js';
import { manifests as repositoryManifests } from './repository/manifests.js';
import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
@@ -18,5 +19,11 @@ export const manifests: Array<ManifestTypes> = [
sortChildrenOfRepositoryAlias: UMB_SORT_CHILDREN_OF_DOCUMENT_REPOSITORY_ALIAS,
treeRepositoryAlias: UMB_DOCUMENT_TREE_REPOSITORY_ALIAS,
},
conditions: [
{
alias: 'Umb.Condition.UserPermission.Document',
allOf: [UMB_USER_PERMISSION_DOCUMENT_SORT],
},
],
},
];

View File

@@ -3,6 +3,7 @@ import type { UmbVariantModel, UmbVariantOptionModel, UmbVariantPublishModel } f
import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';
import { DocumentVariantStateModel as UmbDocumentVariantState } from '@umbraco-cms/backoffice/external/backend-api';
export { UmbDocumentVariantState };
export type { UmbDocumentUserPermissionConditionConfig } from './user-permissions/document-user-permission.condition.js';
export interface UmbDocumentDetailModel {
documentType: {

View File

@@ -1,16 +1,15 @@
export const UMB_USER_PERMISSION_DOCUMENT_CREATE = 'Umb.UserPermission.Document.Create';
export const UMB_USER_PERMISSION_DOCUMENT_READ = 'Umb.UserPermission.Document.Read';
export const UMB_USER_PERMISSION_DOCUMENT_UPDATE = 'Umb.UserPermission.Document.Update';
export const UMB_USER_PERMISSION_DOCUMENT_DELETE = 'Umb.UserPermission.Document.Delete';
export const UMB_USER_PERMISSION_DOCUMENT_CREATE_BLUEPRINT = 'Umb.UserPermission.Document.CreateBlueprint';
export const UMB_USER_PERMISSION_DOCUMENT_NOTIFICATIONS = 'Umb.UserPermission.Document.Notifications';
export const UMB_USER_PERMISSION_DOCUMENT_PUBLISH = 'Umb.UserPermission.Document.Publish';
export const UMB_USER_PERMISSION_DOCUMENT_PERMISSIONS = 'Umb.UserPermission.Document.Permissions';
export const UMB_USER_PERMISSION_DOCUMENT_SEND_FOR_APPROVAL = 'Umb.UserPermission.Document.SendForApproval';
export const UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH = 'Umb.UserPermission.Document.Unpublish';
export const UMB_USER_PERMISSION_DOCUMENT_DUPLICATE = 'Umb.UserPermission.Document.Duplicate';
export const UMB_USER_PERMISSION_DOCUMENT_MOVE = 'Umb.UserPermission.Document.Move';
export const UMB_USER_PERMISSION_DOCUMENT_SORT = 'Umb.UserPermission.Document.Sort';
export const UMB_USER_PERMISSION_DOCUMENT_CULTURE_AND_HOSTNAMES = 'Umb.UserPermission.Document.CultureAndHostnames';
export const UMB_USER_PERMISSION_DOCUMENT_PUBLIC_ACCESS = 'Umb.UserPermission.Document.PublicAccess';
export const UMB_USER_PERMISSION_DOCUMENT_ROLLBACK = 'Umb.UserPermission.Document.Rollback';
export const UMB_USER_PERMISSION_DOCUMENT_CREATE = 'Umb.Document.Create';
export const UMB_USER_PERMISSION_DOCUMENT_READ = 'Umb.Document.Read';
export const UMB_USER_PERMISSION_DOCUMENT_UPDATE = 'Umb.Document.Update';
export const UMB_USER_PERMISSION_DOCUMENT_DELETE = 'Umb.Document.Delete';
export const UMB_USER_PERMISSION_DOCUMENT_CREATE_BLUEPRINT = 'Umb.Document.CreateBlueprint';
export const UMB_USER_PERMISSION_DOCUMENT_NOTIFICATIONS = 'Umb.Document.Notifications';
export const UMB_USER_PERMISSION_DOCUMENT_PUBLISH = 'Umb.Document.Publish';
export const UMB_USER_PERMISSION_DOCUMENT_PERMISSIONS = 'Umb.Document.Permissions';
export const UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH = 'Umb.Document.Unpublish';
export const UMB_USER_PERMISSION_DOCUMENT_DUPLICATE = 'Umb.Document.Duplicate';
export const UMB_USER_PERMISSION_DOCUMENT_MOVE = 'Umb.Document.Move';
export const UMB_USER_PERMISSION_DOCUMENT_SORT = 'Umb.Document.Sort';
export const UMB_USER_PERMISSION_DOCUMENT_CULTURE_AND_HOSTNAMES = 'Umb.Document.CultureAndHostnames';
export const UMB_USER_PERMISSION_DOCUMENT_PUBLIC_ACCESS = 'Umb.Document.PublicAccess';
export const UMB_USER_PERMISSION_DOCUMENT_ROLLBACK = 'Umb.Document.Rollback';

View File

@@ -0,0 +1,137 @@
import { UMB_ENTITY_CONTEXT } from '@umbraco-cms/backoffice/entity';
import { UMB_CURRENT_USER_CONTEXT } from '../../../user/current-user/current-user.context.js';
import { isDocumentUserPermission } from './utils.js';
import { observeMultiple } from '@umbraco-cms/backoffice/observable-api';
import { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry';
import type {
ManifestCondition,
UmbConditionConfigBase,
UmbConditionControllerArguments,
UmbExtensionCondition,
} from '@umbraco-cms/backoffice/extension-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { DocumentPermissionPresentationModel } from '@umbraco-cms/backoffice/external/backend-api';
export class UmbDocumentUserPermissionCondition
extends UmbConditionBase<UmbDocumentUserPermissionConditionConfig>
implements UmbExtensionCondition
{
#entityType: string | undefined;
#unique: string | null | undefined;
#documentPermissions: Array<DocumentPermissionPresentationModel> = [];
#fallbackPermissions: string[] = [];
constructor(
host: UmbControllerHost,
args: UmbConditionControllerArguments<UmbDocumentUserPermissionConditionConfig>,
) {
super(host, args);
this.consumeContext(UMB_CURRENT_USER_CONTEXT, (context) => {
this.observe(
context.currentUser,
(currentUser) => {
this.#documentPermissions = currentUser?.permissions?.filter(isDocumentUserPermission) || [];
this.#fallbackPermissions = currentUser?.fallbackPermissions || [];
this.#checkPermissions();
},
'umbUserPermissionConditionObserver',
);
});
this.consumeContext(UMB_ENTITY_CONTEXT, (context) => {
if (!context) return;
this.observe(
observeMultiple([context.entityType, context.unique]),
([entityType, unique]) => {
this.#entityType = entityType;
this.#unique = unique;
this.#checkPermissions();
},
'umbUserPermissionEntityContextObserver',
);
});
}
#checkPermissions() {
if (!this.#entityType) return;
if (this.#unique === undefined) return;
const hasDocumentPermissions = this.#documentPermissions.length > 0;
// if there is no permissions for any documents we use the fallback permissions
if (!hasDocumentPermissions) {
this.#check(this.#fallbackPermissions);
return;
}
/* If there are document permission we check if there are permissions for the current document
If there aren't we use the fallback permissions */
if (hasDocumentPermissions) {
const permissionsForCurrentDocument = this.#documentPermissions.find(
(permission) => permission.document.id === this.#unique,
);
// no permissions for the current document - use the fallback permissions
if (!permissionsForCurrentDocument) {
this.#check(this.#fallbackPermissions);
return;
}
// we found permissions for the current document - check them
this.#check(permissionsForCurrentDocument.verbs);
}
}
#check(verbs: Array<string>) {
/* we default to true se we don't require both allOf and oneOf to be defined
but they can be combined for more complex scenarios */
let allOfPermitted = true;
let oneOfPermitted = true;
// check if all of the verbs are present
if (this.config.allOf?.length) {
allOfPermitted = this.config.allOf.every((verb) => verbs.includes(verb));
}
// check if at least one of the verbs is present
if (this.config.oneOf?.length) {
oneOfPermitted = this.config.oneOf.some((verb) => verbs.includes(verb));
}
// if neither allOf or oneOf is defined we default to false
if (!allOfPermitted && !oneOfPermitted) {
allOfPermitted = false;
oneOfPermitted = false;
}
this.permitted = allOfPermitted && oneOfPermitted;
}
}
export type UmbDocumentUserPermissionConditionConfig =
UmbConditionConfigBase<'Umb.Condition.UserPermission.Document'> & {
/**
* The user must have all of the permissions in this array for the condition to be met.
*
* @example
* ["Umb.Document.Save", "Umb.Document.Publish"]
*/
allOf?: Array<string>;
/**
* The user must have at least one of the permissions in this array for the condition to be met.
*
* @example
* ["Umb.Document.Save", "Umb.Document.Publish"]
*/
oneOf?: Array<string>;
};
export const manifest: ManifestCondition = {
type: 'condition',
name: 'Document User Permission Condition',
alias: 'Umb.Condition.UserPermission.Document',
api: UmbDocumentUserPermissionCondition,
};

View File

@@ -66,7 +66,7 @@ export class UmbInputDocumentGranularUserPermissionElement extends FormControlMi
this.dispatchEvent(new UmbChangeEvent());
}
#addGranularPermission() {
async #addGranularPermission() {
this.#documentPickerModalContext = this.#modalManagerContext?.open(this, UMB_DOCUMENT_PICKER_MODAL, {
data: {
hideTreeRoot: true,
@@ -83,17 +83,24 @@ export class UmbInputDocumentGranularUserPermissionElement extends FormControlMi
if (!unique) return;
const documentItem = await this.#requestDocumentItem(unique);
const result = await this.#selectEntityUserPermissionsForDocument(documentItem);
this.#documentPickerModalContext?.reject();
const permissionItem: UmbDocumentUserPermissionModel = {
$type: 'DocumentPermissionPresentationModel',
document: { id: unique },
verbs: result,
};
this.#selectEntityUserPermissionsForDocument(documentItem).then(
(result) => {
this.#documentPickerModalContext?.reject();
this.permissions = [...this._permissions, permissionItem];
this.dispatchEvent(new UmbChangeEvent());
const permissionItem: UmbDocumentUserPermissionModel = {
$type: 'DocumentPermissionPresentationModel',
document: { id: unique },
verbs: result,
};
this.permissions = [...this._permissions, permissionItem];
this.dispatchEvent(new UmbChangeEvent());
},
() => {
this.#documentPickerModalContext?.reject();
},
);
});
}
@@ -127,7 +134,7 @@ export class UmbInputDocumentGranularUserPermissionElement extends FormControlMi
const value = await this.#entityUserPermissionModalContext?.onSubmit();
return value?.allowedVerbs;
} catch (error) {
return allowedVerbs;
throw new Error();
}
}

View File

@@ -17,6 +17,7 @@ import {
UMB_USER_PERMISSION_DOCUMENT_ROLLBACK,
} from './constants.js';
import { manifests as repositoryManifests } from './repository/manifests.js';
import { manifest as conditionManifest } from './document-user-permission.condition.js';
import type {
ManifestGranularUserPermission,
ManifestEntityUserPermission,
@@ -211,4 +212,4 @@ export const granularPermissions: Array<ManifestGranularUserPermission> = [
},
];
export const manifests = [...repositoryManifests, ...permissions, ...granularPermissions];
export const manifests = [...repositoryManifests, ...permissions, ...granularPermissions, conditionManifest];

View File

@@ -0,0 +1,10 @@
import type {
DocumentPermissionPresentationModel,
UnknownTypePermissionPresentationModel,
} from '@umbraco-cms/backoffice/external/backend-api';
export function isDocumentUserPermission(
permission: DocumentPermissionPresentationModel | UnknownTypePermissionPresentationModel,
): permission is DocumentPermissionPresentationModel {
return (permission as DocumentPermissionPresentationModel).$type === 'DocumentPermissionPresentationModel';
}

View File

@@ -7,3 +7,5 @@ export class UmbDocumentPublishWithDescendantsWorkspaceAction extends UmbWorkspa
return workspaceContext.publishWithDescendants();
}
}
export { UmbDocumentPublishWithDescendantsWorkspaceAction as api };

View File

@@ -1,13 +1,31 @@
import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '../document-workspace.context-token.js';
import { UmbDocumentUserPermissionCondition } from '../../user-permissions/document-user-permission.condition.js';
import { UMB_USER_PERMISSION_DOCUMENT_UPDATE } from '../../user-permissions/index.js';
import { UmbWorkspaceActionBase } from '@umbraco-cms/backoffice/workspace';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export class UmbDocumentSaveAndPreviewWorkspaceAction extends UmbWorkspaceActionBase {
constructor(host: UmbControllerHost, args: any) {
super(host, args);
/* The action is disabled by default because the onChange callback
will first be triggered when the condition is changed to permitted */
this.disable();
const condition = new UmbDocumentUserPermissionCondition(host, {
host,
config: {
alias: 'Umb.Condition.UserPermission.Document',
allOf: [UMB_USER_PERMISSION_DOCUMENT_UPDATE],
},
onChange: () => {
condition.permitted ? this.enable() : this.disable();
},
});
}
async execute() {
const workspaceContext = await this.getContext(UMB_DOCUMENT_WORKSPACE_CONTEXT);
//const document = workspaceContext.getData();
// TODO: handle errors
//if (!document) return;
//this.workspaceContext.repository?.saveAndPreview();
//Remember to return a promise.
alert('Save and preview');
}
}
export { UmbDocumentSaveAndPreviewWorkspaceAction as api };

View File

@@ -1,9 +1,36 @@
import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '../document-workspace.context-token.js';
import {
UMB_USER_PERMISSION_DOCUMENT_PUBLISH,
UMB_USER_PERMISSION_DOCUMENT_UPDATE,
} from '../../user-permissions/constants.js';
import { UmbDocumentUserPermissionCondition } from '../../user-permissions/document-user-permission.condition.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbWorkspaceActionBase } from '@umbraco-cms/backoffice/workspace';
export class UmbDocumentSaveAndPublishWorkspaceAction extends UmbWorkspaceActionBase {
constructor(host: UmbControllerHost, args: any) {
super(host, args);
/* The action is disabled by default because the onChange callback
will first be triggered when the condition is changed to permitted */
this.disable();
const condition = new UmbDocumentUserPermissionCondition(host, {
host,
config: {
alias: 'Umb.Condition.UserPermission.Document',
allOf: [UMB_USER_PERMISSION_DOCUMENT_UPDATE, UMB_USER_PERMISSION_DOCUMENT_PUBLISH],
},
onChange: () => {
condition.permitted ? this.enable() : this.disable();
},
});
}
async execute() {
const workspaceContext = await this.getContext(UMB_DOCUMENT_WORKSPACE_CONTEXT);
return workspaceContext.saveAndPublish();
}
}
export { UmbDocumentSaveAndPublishWorkspaceAction as api };

View File

@@ -7,3 +7,5 @@ export class UmbDocumentSaveAndScheduleWorkspaceAction extends UmbWorkspaceActio
return workspaceContext.schedule();
}
}
export { UmbDocumentSaveAndScheduleWorkspaceAction as api };

View File

@@ -0,0 +1,34 @@
import {
UMB_USER_PERMISSION_DOCUMENT_CREATE,
UMB_USER_PERMISSION_DOCUMENT_UPDATE,
} from '../../user-permissions/constants.js';
import { UmbDocumentUserPermissionCondition } from '../../user-permissions/document-user-permission.condition.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace';
export class UmbDocumentSaveWorkspaceAction extends UmbSubmitWorkspaceAction {
constructor(host: UmbControllerHost, args: any) {
super(host, args);
/* The action is disabled by default because the onChange callback
will first be triggered when the condition is changed to permitted */
this.disable();
// TODO: this check is not sufficient. It will show the save button if a use
// has only create options. The best solution would be to split the two buttons into two separate actions
// with a condition on isNew to show/hide them
// The server will throw a permission error if this scenario happens
const condition = new UmbDocumentUserPermissionCondition(host, {
host,
config: {
alias: 'Umb.Condition.UserPermission.Document',
oneOf: [UMB_USER_PERMISSION_DOCUMENT_CREATE, UMB_USER_PERMISSION_DOCUMENT_UPDATE],
},
onChange: () => {
condition.permitted ? this.enable() : this.disable();
},
});
}
}
export { UmbDocumentSaveWorkspaceAction as api };

View File

@@ -7,3 +7,5 @@ export class UmbDocumentUnpublishWorkspaceAction extends UmbWorkspaceActionBase
return workspaceContext.unpublish();
}
}
export { UmbDocumentUnpublishWorkspaceAction as api };

View File

@@ -166,8 +166,6 @@ export class UmbDocumentWorkspaceContext
component: () => import('./document-workspace-editor.element.js'),
setup: (_component, info) => {
const unique = info.match.params.unique;
this.#entityContext.setEntityType(UMB_DOCUMENT_ENTITY_TYPE);
this.#entityContext.setUnique(unique);
this.load(unique);
},
},
@@ -193,6 +191,8 @@ export class UmbDocumentWorkspaceContext
const { data, asObservable } = (await this.#getDataPromise) as GetDataType;
if (data) {
this.#entityContext.setEntityType(UMB_DOCUMENT_ENTITY_TYPE);
this.#entityContext.setUnique(unique);
this.setIsNew(false);
this.#persistedData.setValue(data);
this.#currentData.setValue(data);
@@ -220,6 +220,8 @@ export class UmbDocumentWorkspaceContext
const { data } = await this.#getDataPromise;
if (!data) return undefined;
this.#entityContext.setEntityType(UMB_DOCUMENT_ENTITY_TYPE);
this.#entityContext.setUnique(data.unique);
this.setIsNew(true);
this.#persistedData.setValue(undefined);
this.#currentData.setValue(data);

View File

@@ -1,9 +1,9 @@
import { UMB_DOCUMENT_ENTITY_TYPE } from '../entity.js';
import { UmbDocumentSaveAndScheduleWorkspaceAction } from './actions/save-and-schedule.action.js';
import { UmbDocumentUnpublishWorkspaceAction } from './actions/unpublish.action.js';
import { UmbDocumentSaveAndPublishWorkspaceAction } from './actions/save-and-publish.action.js';
import { UmbDocumentPublishWithDescendantsWorkspaceAction } from './actions/publish-with-descendants.action.js';
import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace';
import {
UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH,
UMB_USER_PERMISSION_DOCUMENT_UPDATE,
UMB_USER_PERMISSION_DOCUMENT_PUBLISH,
} from '../user-permissions/index.js';
import type {
ManifestWorkspaces,
ManifestWorkspaceActions,
@@ -90,7 +90,7 @@ const workspaceActions: Array<ManifestWorkspaceActions> = [
alias: 'Umb.WorkspaceAction.Document.SaveAndPublish',
name: 'Save And Publish Document Workspace Action',
weight: 70,
api: UmbDocumentSaveAndPublishWorkspaceAction,
api: () => import('./actions/save-and-publish.action.js'),
meta: {
label: '#buttons_saveAndPublish',
look: 'primary',
@@ -109,7 +109,7 @@ const workspaceActions: Array<ManifestWorkspaceActions> = [
alias: 'Umb.WorkspaceAction.Document.Save',
name: 'Save Document Workspace Action',
weight: 80,
api: UmbSubmitWorkspaceAction,
api: () => import('./actions/save.action.js'),
meta: {
label: '#buttons_save',
look: 'secondary',
@@ -122,13 +122,13 @@ const workspaceActions: Array<ManifestWorkspaceActions> = [
},
],
},
/*
{
type: 'workspaceAction',
kind: 'default',
alias: 'Umb.WorkspaceAction.Document.SaveAndPreview',
name: 'Save And Preview Document Workspace Action',
weight: 90,
api: UmbDocumentSaveAndPreviewWorkspaceAction,
api: () => import('./actions/save-and-preview.action.js'),
meta: {
label: 'Save And Preview',
},
@@ -139,7 +139,6 @@ const workspaceActions: Array<ManifestWorkspaceActions> = [
},
],
},
*/
];
const workspaceActionMenuItems: Array<ManifestWorkspaceActionMenuItem> = [
@@ -149,12 +148,18 @@ const workspaceActionMenuItems: Array<ManifestWorkspaceActionMenuItem> = [
alias: 'Umb.Document.WorkspaceActionMenuItem.Unpublish',
name: 'Unpublish',
weight: 0,
api: UmbDocumentUnpublishWorkspaceAction,
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],
},
],
},
{
type: 'workspaceActionMenuItem',
@@ -162,12 +167,18 @@ const workspaceActionMenuItems: Array<ManifestWorkspaceActionMenuItem> = [
alias: 'Umb.Document.WorkspaceActionMenuItem.PublishWithDescendants',
name: 'Publish with descendants',
weight: 10,
api: UmbDocumentPublishWithDescendantsWorkspaceAction,
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],
},
],
},
{
type: 'workspaceActionMenuItem',
@@ -175,12 +186,18 @@ const workspaceActionMenuItems: Array<ManifestWorkspaceActionMenuItem> = [
alias: 'Umb.Document.WorkspaceActionMenuItem.SchedulePublishing',
name: 'Schedule publishing',
weight: 20,
api: UmbDocumentSaveAndScheduleWorkspaceAction,
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],
},
],
},
];

View File

@@ -1 +0,0 @@
export * from './user-permission.condition.js';

View File

@@ -1,45 +0,0 @@
import { UMB_CURRENT_USER_CONTEXT } from '../../current-user/current-user.context.js';
import { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry';
import type {
ManifestCondition,
UmbConditionConfigBase,
UmbConditionControllerArguments,
UmbExtensionCondition,
} from '@umbraco-cms/backoffice/extension-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export class UmbUserPermissionCondition
extends UmbConditionBase<UserPermissionConditionConfig>
implements UmbExtensionCondition
{
constructor(host: UmbControllerHost, args: UmbConditionControllerArguments<UserPermissionConditionConfig>) {
super(host, args);
this.consumeContext(UMB_CURRENT_USER_CONTEXT, (context) => {
this.observe(
context.currentUser,
(currentUser) => {
//this.permitted = currentUser?.permissions?.includes(this.config.match) || false;
},
'umbUserPermissionConditionObserver',
);
});
}
}
export type UserPermissionConditionConfig = UmbConditionConfigBase<'Umb.Condition.UserPermission'> & {
/**
*
*
* @example
* "Umb.UserPermission.Document.Create"
*/
match: string;
};
export const manifest: ManifestCondition = {
type: 'condition',
name: 'User Permission Condition',
alias: 'Umb.Condition.UserPermission',
api: UmbUserPermissionCondition,
};

View File

@@ -1,4 +1,3 @@
export * from './components/index.js';
export * from './conditions/index.js';
export type { UmbUserPermissionModel } from './types.js';

View File

@@ -1,4 +1,3 @@
import { manifest as userPermissionConditionManifest } from './conditions/user-permission.condition.js';
import { manifests as userPermissionModalManifests } from './modals/manifests.js';
export const manifests = [userPermissionConditionManifest, ...userPermissionModalManifests];
export const manifests = [...userPermissionModalManifests];