import localeZh from '@angular/common/locales/zh'
import { ShareUserComponent } from './components/common/input/share-user/share-user.component'
import { IfOwnerDirective } from './directives/if-owner.directive'
+import { IfObjectPermissionsDirective } from './directives/if-object-permissions.directive'
registerLocaleData(localeBe)
registerLocaleData(localeCs)
MailRuleEditDialogComponent,
ShareUserComponent,
IfOwnerDirective,
+ IfObjectPermissionsDirective,
],
imports: [
BrowserModule,
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text>
<app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive" novalidate></app-input-check>
- <div *ifOwner="object.owner">
+ <div *ifOwner="object?.owner">
<h5 i18n>Permissions</h5>
<div formGroupName="set_permissions">
<app-share-user type="view" formControlName="view"></app-share-user>
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text>
<app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></app-input-check>
- <div *ifOwner="object.owner">
+ <div *ifOwner="object?.owner">
<h5 i18n>Permissions</h5>
<div formGroupName="set_permissions">
<app-share-user type="view" formControlName="view"></app-share-user>
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text>
<app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></app-input-check>
- <div *ifOwner="object.owner">
+ <div *ifOwner="object?.owner">
<h5 i18n>Permissions</h5>
<div formGroupName="set_permissions">
<app-share-user type="view" formControlName="view"></app-share-user>
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text>
<app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></app-input-check>
- <div *ifOwner="object.owner">
+ <div *ifOwner="object?.owner">
<h5 i18n>Permissions</h5>
<div formGroupName="set_permissions">
<app-share-user type="view" formControlName="view"></app-share-user>
<div class="input-group" [class.is-invalid]="error">
<input class="form-control" [class.is-invalid]="error" [placeholder]="placeholder" [id]="inputId" maxlength="10"
(dateSelect)="onChange(value)" (change)="onChange(value)" (keypress)="onKeyPress($event)" (paste)="onPaste($event)"
- name="dp" [(ngModel)]="value" ngbDatepicker #datePicker="ngbDatepicker" #datePickerContent="ngModel">
- <button class="btn btn-outline-secondary calendar" (click)="datePicker.toggle()" type="button">
+ name="dp" [(ngModel)]="value" ngbDatepicker #datePicker="ngbDatepicker" #datePickerContent="ngModel" [disabled]="disabled">
+ <button class="btn btn-outline-secondary calendar" (click)="datePicker.toggle()" type="button" [disabled]="disabled">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-calendar" viewBox="0 0 16 16">
<path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z"/>
</svg>
<div class="mb-3">
<label class="form-label" [for]="inputId">{{title}}</label>
<div class="input-group" [class.is-invalid]="error">
- <input type="number" class="form-control" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [class.is-invalid]="error">
- <button *ngIf="showAdd" class="btn btn-outline-secondary" type="button" id="button-addon1" (click)="nextAsn()" [disabled]="value">+1</button>
+ <input type="number" class="form-control" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [class.is-invalid]="error" [disabled]="disabled">
+ <button *ngIf="showAdd" class="btn btn-outline-secondary" type="button" id="button-addon1" (click)="nextAsn()" [disabled]="disabled">+1</button>
</div>
<div class="invalid-feedback">
{{error}}
(clear)="clearLastSearchTerm()"
(blur)="onBlur()">
</ng-select>
- <button *ngIf="allowCreateNew" class="btn btn-outline-secondary" type="button" (click)="addItem()">
+ <button *ngIf="allowCreateNew" class="btn btn-outline-secondary" type="button" (click)="addItem()" [disabled]="disabled">
<svg class="buttonicon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#plus" />
</svg>
<div class="input-group flex-nowrap">
<ng-select name="tags" [items]="tags" bindLabel="name" bindValue="id" [(ngModel)]="value"
+ [disabled]="disabled"
[multiple]="true"
[closeOnSelect]="false"
[clearSearchOnAdd]="true"
</ng-template>
</ng-select>
- <button *ngIf="allowCreate" class="btn btn-outline-secondary" type="button" (click)="createTag()">
+ <button *ngIf="allowCreate" class="btn btn-outline-secondary" type="button" (click)="createTag()" [disabled]="disabled">
<svg class="buttonicon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#plus" />
</svg>
}
removeTag(id) {
+ if (this.disabled) return
let index = this.value.indexOf(id)
if (index > -1) {
let oldValue = this.value
<div class="mb-3">
<label class="form-label" [for]="inputId">{{title}}</label>
- <input #inputField type="text" class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)">
+ <input #inputField type="text" class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [disabled]="disabled">
<small *ngIf="hint" class="form-text text-muted" [innerHTML]="hint | safeHtml"></small>
<div class="invalid-feedback">
{{error}}
<div class="input-group-text" i18n>of {{previewNumPages}}</div>
</div>
- <button type="button" class="btn btn-sm btn-outline-danger me-2 ms-auto" (click)="delete()" *ifPermissions="{ action: PermissionAction.Delete, type: PermissionType.Document }">
+ <button *ifOwner="document?.owner" type="button" class="btn btn-sm btn-outline-danger me-2 ms-auto" (click)="delete()">
<svg class="buttonicon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#trash" />
</svg><span class="d-none d-lg-inline ps-1" i18n>Delete</span>
</div>
- <button type="button" class="btn btn-sm btn-outline-primary me-2" (click)="redoOcr()">
+ <button *ifOwner="document?.owner" type="button" class="btn btn-sm btn-outline-primary me-2" (click)="redoOcr()">
<svg class="buttonicon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#arrow-counterclockwise" />
</svg><span class="d-none d-lg-inline ps-1" i18n>Redo OCR</span>
<div [ngbNavOutlet]="nav" class="mt-2"></div>
- <button type="button" class="btn btn-outline-secondary" (click)="discard()" i18n [disabled]="networkActive || !(isDirty$ | async)">Discard</button>
- <button type="button" class="btn btn-outline-primary" (click)="saveEditNext()" *ngIf="hasNext()" i18n [disabled]="networkActive || !(isDirty$ | async) || error">Save & next</button>
- <button type="submit" class="btn btn-primary" *ifPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }" i18n [disabled]="networkActive || !(isDirty$ | async) || error">Save</button>
+ <ng-container action="PermissionAction.Change" *ifObjectPermissions="document">
+ <button type="button" class="btn btn-outline-secondary" (click)="discard()" i18n [disabled]="networkActive || !(isDirty$ | async)">Discard</button>
+ <button type="button" class="btn btn-outline-primary" (click)="saveEditNext()" *ngIf="hasNext()" i18n [disabled]="networkActive || !(isDirty$ | async) || error">Save & next</button>
+ <button type="submit" class="btn btn-primary" *ifPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }" i18n [disabled]="networkActive || !(isDirty$ | async) || error">Save</button>
+ </ng-container>
</form>
</div>
.map((p) => p[0]),
}
this.documentForm.patchValue(doc)
+ if (!this.userCanEdit) this.documentForm.disable()
}
createDocumentType(newName: string) {
})
)
}
+
+ get userCanEdit(): boolean {
+ return (
+ !this.document ||
+ this.permissionsService.currentUserHasObjectPermissions(
+ PermissionAction.Change,
+ this.document
+ )
+ )
+ }
}
--- /dev/null
+import {
+ Directive,
+ Input,
+ OnChanges,
+ OnInit,
+ TemplateRef,
+ ViewContainerRef,
+} from '@angular/core'
+import { ObjectWithPermissions } from '../data/object-with-permissions'
+import {
+ PermissionAction,
+ PermissionsService,
+} from '../services/permissions.service'
+
+@Directive({
+ selector: '[ifObjectPermissions]',
+})
+export class IfObjectPermissionsDirective implements OnInit, OnChanges {
+ // The role the user must have
+ @Input()
+ ifObjectPermissions: ObjectWithPermissions
+
+ @Input()
+ action: PermissionAction
+
+ /**
+ * @param {ViewContainerRef} viewContainerRef -- The location where we need to render the templateRef
+ * @param {TemplateRef<any>} templateRef -- The templateRef to be potentially rendered
+ * @param {PermissionsService} permissionsService -- Will give us access to the permissions a user has
+ */
+ constructor(
+ private viewContainerRef: ViewContainerRef,
+ private templateRef: TemplateRef<any>,
+ private permissionsService: PermissionsService
+ ) {}
+
+ public ngOnInit(): void {
+ if (
+ !this.ifObjectPermissions ||
+ this.permissionsService.currentUserHasObjectPermissions(
+ this.action,
+ this.ifObjectPermissions
+ )
+ ) {
+ this.viewContainerRef.createEmbeddedView(this.templateRef)
+ } else {
+ this.viewContainerRef.clear()
+ }
+ }
+
+ public ngOnChanges(): void {
+ this.ngOnInit()
+ }
+}
ViewContainerRef,
} from '@angular/core'
import { PaperlessUser } from '../data/paperless-user'
-import { SettingsService } from '../services/settings.service'
+import { PermissionsService } from '../services/permissions.service'
@Directive({
selector: '[ifOwner]',
constructor(
private viewContainerRef: ViewContainerRef,
private templateRef: TemplateRef<any>,
- private settings: SettingsService
+ private permissionsService: PermissionsService
) {}
public ngOnInit(): void {
- if (!this.ifOwner || this.ifOwner?.id === this.settings.currentUser.id) {
+ if (
+ !this.ifOwner ||
+ this.permissionsService.currentUserIsOwner(this.ifOwner)
+ ) {
this.viewContainerRef.createEmbeddedView(this.templateRef)
} else {
this.viewContainerRef.clear()
import { Injectable } from '@angular/core'
+import { ObjectWithPermissions } from '../data/object-with-permissions'
+import { PaperlessUser } from '../data/paperless-user'
export enum PermissionAction {
Add = 'add',
})
export class PermissionsService {
private permissions: string[]
+ private currentUser: PaperlessUser
- public initialize(permissions: string[]) {
+ public initialize(permissions: string[], currentUser: PaperlessUser) {
this.permissions = permissions
+ this.currentUser = currentUser
}
public currentUserCan(permission: PaperlessPermission): boolean {
return this.permissions.includes(this.getPermissionCode(permission))
}
+ public currentUserIsOwner(owner: PaperlessUser): boolean {
+ return owner?.id === this.currentUser.id
+ }
+
+ public currentUserHasObjectPermissions(
+ action: string,
+ object: ObjectWithPermissions
+ ): boolean {
+ return (object.permissions[action] as Array<number>)?.includes(
+ this.currentUser.id
+ )
+ }
+
public getPermissionCode(permission: PaperlessPermission): string {
return permission.type.replace('%s', permission.action)
}
id: uisettings['user_id'],
username: uisettings['username'],
}
- this.permissionsService.initialize(uisettings.permissions)
+ this.permissionsService.initialize(
+ uisettings.permissions,
+ this.currentUser
+ )
})
)
}
.paperless-input-tags {
.ng-select.ng-select-multiple .ng-select-container .ng-value-container .ng-value {
background-color: transparent;
+ border-color: transparent;
}
.ng-select.ng-select-multiple .ng-select-container .ng-value-container {